diff --git a/java/java-runtime/src/com/intellij/rt/execution/junit/ComparisonFailureData.java b/java/java-runtime/src/com/intellij/rt/execution/junit/ComparisonFailureData.java new file mode 100644 index 000000000000..872fe864f76e --- /dev/null +++ b/java/java-runtime/src/com/intellij/rt/execution/junit/ComparisonFailureData.java @@ -0,0 +1,109 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.rt.execution.junit; + +import junit.framework.ComparisonFailure; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class ComparisonFailureData { + private final String myExpected; + private final String myActual; + private final String myFilePath; + + private static Map EXPECTED = new HashMap(); + private static Map ACTUAL = new HashMap(); + + static { + try { + init(ComparisonFailure.class); + init(org.junit.ComparisonFailure.class); + } + catch (Throwable e) { + } + } + + private static void init(Class exceptionClass) throws NoSuchFieldException { + final Field expectedField = exceptionClass.getDeclaredField("fExpected"); + expectedField.setAccessible(true); + EXPECTED.put(exceptionClass, expectedField); + + final Field actualField = exceptionClass.getDeclaredField("fActual"); + actualField.setAccessible(true); + ACTUAL.put(exceptionClass, actualField); + } + + public ComparisonFailureData(String expected, String actual) { + this(expected, actual, null); + } + + public ComparisonFailureData(String expected, String actual, String filePath) { + myExpected = expected; + myActual = actual; + myFilePath = filePath; + } + + public String getFilePath() { + return myFilePath; + } + + public String getExpected() { + return myExpected; + } + + public String getActual() { + return myActual; + } + + public static ComparisonFailureData create(Throwable assertion) { + if (assertion instanceof FileComparisonFailure) { + final FileComparisonFailure comparisonFailure = (FileComparisonFailure)assertion; + return new ComparisonFailureData(comparisonFailure.getExpected(), comparisonFailure.getActual(), comparisonFailure.getFilePath()); + } + try { + return new ComparisonFailureData(getExpected(assertion), getActual(assertion)); + } + catch (Throwable e) { + return null; + } + } + + public static String getActual(Throwable assertion) throws IllegalAccessException, NoSuchFieldException { + return get(assertion, ACTUAL, "fActual"); + } + + public static String getExpected(Throwable assertion) throws IllegalAccessException, NoSuchFieldException { + return get(assertion, EXPECTED, "fExpected"); + } + + private static String get(final Throwable assertion, final Map staticMap, final String fieldName) throws IllegalAccessException, NoSuchFieldException { + String actual; + if (assertion instanceof ComparisonFailure) { + actual = (String)((Field)staticMap.get(ComparisonFailure.class)).get(assertion); + } + else if (assertion instanceof org.junit.ComparisonFailure) { + actual = (String)((Field)staticMap.get(org.junit.ComparisonFailure.class)).get(assertion); + } + else { + Field field = assertion.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + actual = (String)field.get(assertion); + } + return actual; + } +} diff --git a/java/java-runtime/src/com/intellij/rt/execution/junit/FileComparisonFailure.java b/java/java-runtime/src/com/intellij/rt/execution/junit/FileComparisonFailure.java index 0a66a53106cb..788e872067f2 100644 --- a/java/java-runtime/src/com/intellij/rt/execution/junit/FileComparisonFailure.java +++ b/java/java-runtime/src/com/intellij/rt/execution/junit/FileComparisonFailure.java @@ -31,6 +31,18 @@ public class FileComparisonFailure extends ComparisonFailure implements KnownExc myFilePath = filePath; } + public String getFilePath() { + return myFilePath; + } + + public String getExpected() { + return myExpected; + } + + public String getActual() { + return myActual; + } + public PacketFactory getPacketFactory() { return new MyPacketFactory(this, myExpected, myActual, myFilePath); } diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index c2d4fcbae11d..3609f656e9cb 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -290,3 +290,4 @@ disable.graph.inference=false comment.by.line.bulk.lines.trigger=100 scene.builder.start.executable=true +junit_sm_runner=false diff --git a/plugins/junit/junit.iml b/plugins/junit/junit.iml index 8f4c38643198..04538142d4df 100644 --- a/plugins/junit/junit.iml +++ b/plugins/junit/junit.iml @@ -18,6 +18,7 @@ + diff --git a/plugins/junit/src/com/intellij/execution/junit/TestObject.java b/plugins/junit/src/com/intellij/execution/junit/TestObject.java index 04f2dfcc0579..7b54bda50b0a 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestObject.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestObject.java @@ -35,6 +35,11 @@ import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ProgramRunner; import com.intellij.execution.testframework.*; +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; +import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; +import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView; +import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.util.JavaParametersUtil; import com.intellij.execution.util.ProgramParametersUtil; @@ -53,6 +58,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; @@ -62,6 +68,7 @@ import com.intellij.rt.execution.junit.IDEAJUnitListener; import com.intellij.rt.execution.junit.JUnitStarter; import com.intellij.util.Function; import com.intellij.util.PathUtil; +import jetbrains.buildServer.messages.serviceMessages.ServiceMessageTypes; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -76,6 +83,7 @@ public abstract class TestObject implements JavaCommandLine { protected static final Logger LOG = Logger.getInstance("#com.intellij.execution.junit.TestObject"); private static final String MESSAGE = ExecutionBundle.message("configuration.not.speficied.message"); + private static final String JUNIT_TEST_FRAMEWORK_NAME = "JUnit"; protected JavaParameters myJavaParameters; private final Project myProject; @@ -198,6 +206,7 @@ public abstract class TestObject implements JavaCommandLine { myJavaParameters.getClassPath().add(JavaSdkUtil.getIdeaRtJarPath()); myJavaParameters.getClassPath().add(PathUtil.getJarPathForClass(JUnitStarter.class)); + myJavaParameters.getClassPath().add(PathUtil.getJarPathForClass(ServiceMessageTypes.class)); myJavaParameters.getProgramParametersList().add(JUnitStarter.IDE_VERSION + JUnitStarter.VERSION); for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) { ext.updateJavaParameters(myConfiguration, myJavaParameters, getRunnerSettings()); @@ -248,9 +257,16 @@ public abstract class TestObject implements JavaCommandLine { @Override public ExecutionResult execute(final Executor executor, @NotNull final ProgramRunner runner) throws ExecutionException { + final boolean smRunner = Registry.is("junit_sm_runner", false); + if (smRunner) { + myJavaParameters.getVMParametersList().add("-Didea.junit.sm_runner"); + } final JUnitProcessHandler handler = createHandler(executor); final RunnerSettings runnerSettings = getRunnerSettings(); JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(myConfiguration, handler, runnerSettings); + if (smRunner) { + return useSmRunner(executor, handler); + } final TestProxy unboundOutputRoot = new TestProxy(new RootTestInfo()); final JUnitConsoleProperties consoleProperties = new JUnitConsoleProperties(myConfiguration, executor); final JUnitTreeConsoleView consoleView = new JUnitTreeConsoleView(consoleProperties, runnerSettings, getConfigurationSettings(), unboundOutputRoot); @@ -349,6 +365,42 @@ public abstract class TestObject implements JavaCommandLine { return result; } + private ExecutionResult useSmRunner(Executor executor, JUnitProcessHandler handler) { + TestConsoleProperties testConsoleProperties = new SMTRunnerConsoleProperties( + new RuntimeConfigurationProducer.DelegatingRuntimeConfiguration( + (JUnitConfiguration)myEnvironment.getRunProfile()), + JUNIT_TEST_FRAMEWORK_NAME, + executor + ); + + testConsoleProperties.setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false); + + BaseTestsOutputConsoleView smtConsoleView = SMTestRunnerConnectionUtil.createConsoleWithCustomLocator( + JUNIT_TEST_FRAMEWORK_NAME, + testConsoleProperties, + myEnvironment.getRunnerSettings(), + myEnvironment.getConfigurationSettings(), null); + + + Disposer.register(myProject, smtConsoleView); + + final ConsoleView consoleView = smtConsoleView; + consoleView.attachToProcess(handler); + + final RerunFailedTestsAction rerunFailedTestsAction = new RerunFailedTestsAction(consoleView); + rerunFailedTestsAction.init(testConsoleProperties, myEnvironment); + rerunFailedTestsAction.setModelProvider(new Getter() { + @Override + public TestFrameworkRunningModel get() { + return ((SMTRunnerConsoleView)consoleView).getResultsViewer(); + } + }); + + final DefaultExecutionResult result = new DefaultExecutionResult(consoleView, handler); + result.setRestartActions(rerunFailedTestsAction); + return result; + } + protected void notifyByBalloon(JUnitRunningModel model, boolean started, JUnitConsoleProperties consoleProperties) { TestsUIUtil.notifyByBalloon(myProject, started, model != null ? model.getRoot() : null, consoleProperties); } diff --git a/plugins/junit_rt/junit_rt.iml b/plugins/junit_rt/junit_rt.iml index e24b63784ebd..37653b53cb5a 100644 --- a/plugins/junit_rt/junit_rt.iml +++ b/plugins/junit_rt/junit_rt.iml @@ -9,6 +9,7 @@ + diff --git a/plugins/junit_rt/src/com/intellij/junit4/JUnit4IdeaTestRunner.java b/plugins/junit_rt/src/com/intellij/junit4/JUnit4IdeaTestRunner.java index c4efb6e3c1f9..810ece92ec88 100644 --- a/plugins/junit_rt/src/com/intellij/junit4/JUnit4IdeaTestRunner.java +++ b/plugins/junit_rt/src/com/intellij/junit4/JUnit4IdeaTestRunner.java @@ -15,10 +15,7 @@ */ package com.intellij.junit4; -import com.intellij.rt.execution.junit.IDEAJUnitListener; -import com.intellij.rt.execution.junit.IdeaTestRunner; -import com.intellij.rt.execution.junit.TimeSender; -import com.intellij.rt.execution.junit.TreeSender; +import com.intellij.rt.execution.junit.*; import com.intellij.rt.execution.junit.segments.OutputObjectRegistry; import com.intellij.rt.execution.junit.segments.SegmentedOutputStream; import org.junit.internal.requests.ClassRequest; @@ -140,8 +137,12 @@ public class JUnit4IdeaTestRunner implements IdeaTestRunner { public void setStreams(SegmentedOutputStream segmentedOut, SegmentedOutputStream segmentedErr, int lastIdx) { - myRegistry = new JUnit4OutputObjectRegistry(segmentedOut, lastIdx); - myTestsListener = new JUnit4TestResultsSender(myRegistry); + if (JUnitStarter.SM_RUNNER) { + myTestsListener = new SMTestSender(); + } else { + myRegistry = new JUnit4OutputObjectRegistry(segmentedOut, lastIdx); + myTestsListener = new JUnit4TestResultsSender(myRegistry); + } } public Object getTestToStart(String[] args) { diff --git a/plugins/junit_rt/src/com/intellij/junit4/SMTestSender.java b/plugins/junit_rt/src/com/intellij/junit4/SMTestSender.java index 7e22e3757ede..81844f425ac0 100644 --- a/plugins/junit_rt/src/com/intellij/junit4/SMTestSender.java +++ b/plugins/junit_rt/src/com/intellij/junit4/SMTestSender.java @@ -20,16 +20,29 @@ */ package com.intellij.junit4; +import com.intellij.rt.execution.junit.*; +import jetbrains.buildServer.messages.serviceMessages.ServiceMessage; +import jetbrains.buildServer.messages.serviceMessages.ServiceMessageTypes; +import junit.framework.ComparisonFailure; +import org.junit.Ignore; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + class SMTestSender extends RunListener { + private static final String JUNIT_FRAMEWORK_COMPARISON_NAME = ComparisonFailure.class.getName(); + private static final String ORG_JUNIT_COMPARISON_NAME = "org.junit.ComparisonFailure"; + private String myCurrentClassName; public void testRunStarted(Description description) throws Exception { - //System.out.println("##teamcity[testSuiteStarted name =\'" + description.toString() + "\']"); + System.out.println("##teamcity[testSuiteStarted name =\'" + description.toString() + "\']"); } public void testRunFinished(Result result) throws Exception { @@ -55,20 +68,108 @@ class SMTestSender extends RunListener { } public void testFailure(Failure failure) throws Exception { - System.out.println("##teamcity[testFailed name=\'" + - JUnit4ReflectionUtil.getMethodName(failure.getDescription()) + - "\' message=\'" + - failure.getMessage() + - "\' details=\'" + - failure.getTrace().replaceAll("\n", "n").replaceAll("\r", "r") + - "\']"); + final String failureMessage = failure.getMessage(); + final String trace = failure.getTrace(); + final Map attrs = new HashMap(); + attrs.put("name", JUnit4ReflectionUtil.getMethodName(failure.getDescription())); + attrs.put("message", failureMessage); + final ComparisonFailureData notification = createExceptionNotification(failure.getException()); + if (notification != null) { + attrs.put("expected", notification.getExpected()); + attrs.put("actual", notification.getActual()); + + final int failureIdx = trace.indexOf(failureMessage); + attrs.put("details", failureIdx > -1 ? trace.substring(failureIdx + failureMessage.length()) : trace); + } else { + attrs.put("details", trace); + } + + System.out.println(ServiceMessage.asString(ServiceMessageTypes.TEST_FAILED, attrs)); } public void testAssumptionFailure(Failure failure) { - + prepareIgnoreMessage(failure.getDescription(), false); } - public void testIgnored(Description description) throws Exception { - System.out.println("##teamcity[testIgnored name=\'" + JUnit4ReflectionUtil.getMethodName(description) + "\']"); + public synchronized void testIgnored(Description description) throws Exception { + prepareIgnoreMessage(description, true); + } + + private static void prepareIgnoreMessage(Description description, boolean commentMessage) { + Map attrs = new HashMap(); + if (commentMessage) { + try { + final Ignore ignoredAnnotation = (Ignore)description.getAnnotation(Ignore.class); + if (ignoredAnnotation != null) { + final String val = ignoredAnnotation.value(); + if (val != null) { + attrs.put("message", val); + } + } + } + catch (NoSuchMethodError ignored) { + //junit < 4.4 + } + } + attrs.put("name", JUnit4ReflectionUtil.getMethodName(description)); + System.out.println(ServiceMessage.asString(ServiceMessageTypes.TEST_IGNORED, attrs)); + } + + private static boolean isComparisonFailure(Throwable throwable) { + if (throwable == null) return false; + return isComparisonFailure(throwable.getClass()); + } + + private static boolean isComparisonFailure(Class aClass) { + if (aClass == null) return false; + final String throwableClassName = aClass.getName(); + if (throwableClassName.equals(JUNIT_FRAMEWORK_COMPARISON_NAME) || throwableClassName.equals(ORG_JUNIT_COMPARISON_NAME)) return true; + return isComparisonFailure(aClass.getSuperclass()); + } + + private static ComparisonFailureData createExceptionNotification(Throwable assertion) { + if (isComparisonFailure(assertion)) { + return ComparisonFailureData.create(assertion); + } + final Throwable cause = assertion.getCause(); + if (isComparisonFailure(cause)) { + try { + return ComparisonFailureData.create(assertion); + } + catch (Throwable ignore) { + } + } + + final String message = assertion.getMessage(); + if (message != null) { + ComparisonFailureData notification = createExceptionNotification(message, "\nExpected: is \"(.*)\"\n\\s*got: \"(.*)\"\n"); + if (notification == null) { + notification = createExceptionNotification(message, "\nExpected: is \"(.*)\"\n\\s*but: was \"(.*)\""); + } + if (notification == null) { + notification = createExceptionNotification(message, "\nExpected: (.*)\n\\s*got: (.*)"); + } + if (notification == null) { + notification = createExceptionNotification(message, "\\s*expected same:<(.*)> was not:<(.*)>"); + } + if (notification == null) { + notification = createExceptionNotification(message, "\\s*expected:<(.*)> but was:<(.*)>"); + } + if (notification == null) { + notification = createExceptionNotification(message, "\nExpected: \"(.*)\"\n\\s*but: was \"(.*)\""); + } + if (notification != null) { + return notification; + } + } + return null; + } + + private static ComparisonFailureData createExceptionNotification(String message, final String regex) { + final Matcher matcher = Pattern.compile(regex, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(message); + if (matcher.matches()) { + return new ComparisonFailureData(matcher.group(1).replaceAll("\\\\n", "\n"), matcher.group(2).replaceAll("\\\\n", "\n")); + } + return null; } } \ No newline at end of file diff --git a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java index 9bdfd692aca4..2097d54a52bd 100644 --- a/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java +++ b/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java @@ -36,6 +36,7 @@ public class JUnitStarter { private static final String SOCKET = "-socket"; private static String ourForkMode; private static String ourCommandFileName; + public static boolean SM_RUNNER = System.getProperty("idea.junit.sm_runner") != null; public static void main(String[] args) throws IOException { SegmentedOutputStream out = new SegmentedOutputStream(System.out); @@ -192,7 +193,7 @@ public class JUnitStarter { } IdeaTestRunner testRunner = (IdeaTestRunner)getAgentClass(isJUnit4).newInstance(); testRunner.setStreams(out, err, 0); - return testRunner.startRunnerWithArgs(args, listeners, true); + return testRunner.startRunnerWithArgs(args, listeners, !SM_RUNNER); } catch (Exception e) { e.printStackTrace(System.err);