| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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 org.apache.tools.ant.taskdefs.optional.junit; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.util.Enumeration; |
| import java.util.Properties; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| |
| import junit.framework.AssertionFailedError; |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import junit.framework.TestFailure; |
| import junit.framework.TestListener; |
| import junit.framework.TestResult; |
| import junit.framework.TestSuite; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.types.Permissions; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.StringUtils; |
| import org.apache.tools.ant.util.TeeOutputStream; |
| |
| /** |
| * Simple Testrunner for JUnit that runs all tests of a testsuite. |
| * |
| * <p>This TestRunner expects a name of a TestCase class as its |
| * argument. If this class provides a static suite() method it will be |
| * called and the resulting Test will be run. So, the signature should be |
| * <pre> |
| * public static junit.framework.Test suite() |
| * </pre> |
| * |
| * <p>If no such method exists, all public methods starting with |
| * "test" and taking no argument will be run.</p> |
| * |
| * <p>Summary output is generated at the end.</p> |
| * |
| * @since Ant 1.2 |
| */ |
| |
| public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestRunnerMirror { |
| private static final String JUNIT_4_TEST_ADAPTER = "junit.framework.JUnit4TestAdapter"; |
| |
| private static final String[] DEFAULT_TRACE_FILTERS = new String[] {"junit.framework.TestCase", |
| "junit.framework.TestResult", |
| "junit.framework.TestSuite", |
| "junit.framework.Assert.", // don't filter AssertionFailure |
| "junit.swingui.TestRunner", |
| "junit.awtui.TestRunner", |
| "junit.textui.TestRunner", |
| "java.lang.reflect.Method.invoke(", |
| "sun.reflect.", |
| "org.apache.tools.ant.", |
| // JUnit 4 support: |
| "org.junit.", |
| "junit.framework.JUnit4TestAdapter", |
| " more"}; |
| |
| /** |
| * Do we filter junit.*.* stack frames out of failure and error exceptions. |
| */ |
| private static boolean filtertrace = true; |
| |
| /** Running more than one test suite? */ |
| private static boolean multipleTests = false; |
| |
| /** |
| * The file used to indicate that the build crashed. |
| * File will be empty in case the build did not crash. |
| */ |
| private static String crashFile = null; |
| |
| /** |
| * Holds the registered formatters. |
| */ |
| private final Vector<JUnitTaskMirror.JUnitResultFormatterMirror> formatters = new Vector<>(); |
| |
| /** |
| * Collects TestResults. |
| */ |
| private IgnoredTestResult res; |
| |
| /** |
| * Do we send output to System.out/.err in addition to the formatters? |
| */ |
| private boolean showOutput = false; |
| |
| private boolean outputToFormatters = true; |
| |
| /** |
| * The permissions set for the test to run. |
| */ |
| private Permissions perm = null; |
| |
| /** |
| * Do we stop on errors. |
| */ |
| private boolean haltOnError = false; |
| |
| /** |
| * Do we stop on test failures. |
| */ |
| private boolean haltOnFailure = false; |
| |
| /** |
| * Returncode |
| */ |
| private int retCode = SUCCESS; |
| |
| /** |
| * The TestSuite we are currently running. |
| */ |
| private final JUnitTest junitTest; |
| |
| /** output written during the test */ |
| private PrintStream systemError; |
| |
| /** Error output during the test */ |
| private PrintStream systemOut; |
| |
| /** is this runner running in forked mode? */ |
| private boolean forked = false; |
| |
| /** ClassLoader passed in in non-forked mode. */ |
| private final ClassLoader loader; |
| |
| /** Do we print TestListener events? */ |
| private boolean logTestListenerEvents = false; |
| |
| /** Turned on if we are using JUnit 4 for this test suite. see #38811 */ |
| private boolean junit4; |
| |
| /** Names of test methods to execute */ |
| private String[] methods = null; |
| |
| /** |
| * Constructor for fork=true or when the user hasn't specified a |
| * classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure) { |
| this(test, haltOnError, filtertrace, haltOnFailure, false); |
| } |
| |
| /** |
| * Constructor for fork=true or when the user hasn't specified a |
| * classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param showOutput whether to send output to System.out/.err as well as formatters. |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput) { |
| this(test, haltOnError, filtertrace, haltOnFailure, showOutput, false); |
| } |
| |
| /** |
| * Constructor for fork=true or when the user hasn't specified a |
| * classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param showOutput whether to send output to System.out/.err as well as formatters. |
| * @param logTestListenerEvents whether to print TestListener events. |
| * @since Ant 1.7 |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput, final boolean logTestListenerEvents) { |
| this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, |
| logTestListenerEvents, null); |
| } |
| |
| /** |
| * Constructor for fork=true or when the user hasn't specified a |
| * classpath. |
| * @param test the test to run. |
| * @param methods names of methods of the test to be executed. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param showOutput whether to send output to System.out/.err as well as formatters. |
| * @param logTestListenerEvents whether to print TestListener events. |
| * @since 1.8.2 |
| */ |
| public JUnitTestRunner(final JUnitTest test, final String[] methods, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput, final boolean logTestListenerEvents) { |
| this(test, methods, haltOnError, filtertrace, haltOnFailure, showOutput, |
| logTestListenerEvents, null); |
| } |
| |
| /** |
| * Constructor to use when the user has specified a classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param loader the classloader to use running the test. |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final ClassLoader loader) { |
| this(test, haltOnError, filtertrace, haltOnFailure, false, loader); |
| } |
| |
| /** |
| * Constructor to use when the user has specified a classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param showOutput whether to send output to System.out/.err as well as formatters. |
| * @param loader the classloader to use running the test. |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput, final ClassLoader loader) { |
| this(test, haltOnError, filtertrace, haltOnFailure, showOutput, |
| false, loader); |
| } |
| |
| /** |
| * Constructor to use when the user has specified a classpath. |
| * @param test the test to run. |
| * @param haltOnError whether to stop the run if an error is found. |
| * @param filtertrace whether to filter junit.*.* stack frames out of exceptions |
| * @param haltOnFailure whether to stop the run if failure is found. |
| * @param showOutput whether to send output to System.out/.err as well as formatters. |
| * @param logTestListenerEvents whether to print TestListener events. |
| * @param loader the classloader to use running the test. |
| * @since Ant 1.7 |
| */ |
| public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput, final boolean logTestListenerEvents, |
| final ClassLoader loader) { |
| this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, |
| logTestListenerEvents, loader); |
| } |
| |
| |
| /** |
| * Constructor to use when the user has specified a classpath. |
| * @param test JUnitTest |
| * @param methods String[] |
| * @param haltOnError boolean |
| * @param filtertrace boolean |
| * @param haltOnFailure boolean |
| * @param showOutput boolean |
| * @param logTestListenerEvents boolean |
| * @param loader ClassLoader |
| * @since 1.8.2 |
| */ |
| public JUnitTestRunner(final JUnitTest test, final String[] methods, final boolean haltOnError, |
| final boolean filtertrace, final boolean haltOnFailure, |
| final boolean showOutput, final boolean logTestListenerEvents, |
| final ClassLoader loader) { |
| super(); |
| JUnitTestRunner.filtertrace = filtertrace; // TODO clumsy, should use instance field somehow |
| this.junitTest = test; |
| this.haltOnError = haltOnError; |
| this.haltOnFailure = haltOnFailure; |
| this.showOutput = showOutput; |
| this.logTestListenerEvents = logTestListenerEvents; |
| this.methods = methods != null ? methods.clone() : null; |
| this.loader = loader; |
| } |
| |
| private PrintStream savedOut = null; |
| private PrintStream savedErr = null; |
| |
| private PrintStream createEmptyStream() { |
| return new PrintStream( |
| new OutputStream() { |
| @Override |
| public void write(final int b) { |
| } |
| }); |
| } |
| |
| private PrintStream createTeePrint(final PrintStream ps1, final PrintStream ps2) { |
| return new PrintStream(new TeeOutputStream(ps1, ps2)); |
| } |
| |
| private void setupIOStreams(final ByteArrayOutputStream o, |
| final ByteArrayOutputStream e) { |
| systemOut = new PrintStream(o); |
| systemError = new PrintStream(e); |
| |
| if (forked) { |
| if (!outputToFormatters) { |
| if (!showOutput) { |
| savedOut = System.out; |
| savedErr = System.err; |
| System.setOut(createEmptyStream()); |
| System.setErr(createEmptyStream()); |
| } |
| } else { |
| savedOut = System.out; |
| savedErr = System.err; |
| if (!showOutput) { |
| System.setOut(systemOut); |
| System.setErr(systemError); |
| } else { |
| System.setOut(createTeePrint(savedOut, systemOut)); |
| System.setErr(createTeePrint(savedErr, systemError)); |
| } |
| perm = null; |
| } |
| } else { |
| if (perm != null) { |
| perm.setSecurityManager(); |
| } |
| } |
| } |
| |
| /** |
| * Run the test. |
| */ |
| @Override |
| public void run() { |
| res = new IgnoredTestResult(); |
| res.addListener(wrapListener(this)); |
| for (JUnitTaskMirror.JUnitResultFormatterMirror f : formatters) { |
| res.addListener(wrapListener((TestListener) f)); |
| } |
| |
| final ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); |
| final ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); |
| |
| setupIOStreams(outStrm, errStrm); |
| |
| Test suite = null; |
| Throwable exception = null; |
| boolean startTestSuiteSuccess = false; |
| |
| try { |
| |
| try { |
| Class<?> testClass; |
| if (loader == null) { |
| testClass = Class.forName(junitTest.getName(), false, |
| getClass().getClassLoader()); |
| } else { |
| testClass = Class.forName(junitTest.getName(), false, |
| loader); |
| } |
| |
| final boolean testMethodsSpecified = (methods != null); |
| |
| // check for a static suite method first, even when using |
| // JUnit 4 |
| Method suiteMethod = null; |
| if (!testMethodsSpecified) { |
| try { |
| // check if there is a suite method |
| suiteMethod = testClass.getMethod("suite"); |
| } catch (final NoSuchMethodException e) { |
| // no appropriate suite method found. We don't report any |
| // error here since it might be perfectly normal. |
| } |
| } |
| |
| if (suiteMethod != null) { |
| // if there is a suite method available, then try |
| // to extract the suite from it. If there is an error |
| // here it will be caught below and reported. |
| suite = (Test) suiteMethod.invoke(null); |
| |
| } else { |
| Class<?> junit4TestAdapterClass = null; |
| Class<?> junit4TestAdapterCacheClass = null; |
| boolean useSingleMethodAdapter = false; |
| |
| if (junit.framework.TestCase.class.isAssignableFrom(testClass)) { |
| // Do not use JUnit 4 API for running JUnit 3.x |
| // tests - it is not able to run individual test |
| // methods. |
| // |
| // Technical details: |
| // org.junit.runner.Request.method(Class, String).getRunner() |
| // would return a runner which always executes all |
| // test methods. The reason is that the Runner would be |
| // an instance of class |
| // org.junit.internal.runners.OldTestClassRunner |
| // that does not implement interface Filterable - so it |
| // is unable to filter out test methods not matching |
| // the requested name. |
| } else { |
| // Check for JDK 5 first. Will *not* help on JDK 1.4 |
| // if only junit-4.0.jar in CP because in that case |
| // linkage of whole task will already have failed! But |
| // will help if CP has junit-3.8.2.jar:junit-4.0.jar. |
| |
| // In that case first C.fN will fail with CNFE and we |
| // will avoid UnsupportedClassVersionError. |
| |
| try { |
| Class.forName("java.lang.annotation.Annotation"); |
| junit4TestAdapterCacheClass = Class.forName( |
| "org.apache.tools.ant.taskdefs.optional.junit.CustomJUnit4TestAdapterCache"); |
| if (loader == null) { |
| junit4TestAdapterClass = Class.forName(JUNIT_4_TEST_ADAPTER); |
| if (testMethodsSpecified) { |
| /* |
| * We cannot try to load the JUnit4TestAdapter |
| * before trying to load JUnit4TestMethodAdapter |
| * because it might fail with |
| * NoClassDefFoundException, instead of plain |
| * ClassNotFoundException. |
| */ |
| junit4TestAdapterClass = Class.forName( |
| "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter"); |
| useSingleMethodAdapter = true; |
| } |
| } else { |
| junit4TestAdapterClass = |
| Class.forName(JUNIT_4_TEST_ADAPTER, |
| true, loader); |
| if (testMethodsSpecified) { |
| junit4TestAdapterClass = Class.forName( |
| "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter", |
| true, loader); |
| useSingleMethodAdapter = true; |
| } |
| } |
| } catch (final ClassNotFoundException e) { |
| // OK, fall back to JUnit 3. |
| } |
| } |
| junit4 = junit4TestAdapterClass != null; |
| |
| if (junitTest.isSkipNonTests() |
| && !containsTests(testClass, junit4)) { |
| return; |
| } |
| |
| if (junit4) { |
| // Let's use it! |
| Class<?>[] formalParams; |
| Object[] actualParams; |
| if (useSingleMethodAdapter) { |
| formalParams = new Class[] {Class.class, String[].class}; |
| actualParams = new Object[] {testClass, methods}; |
| } else { |
| formalParams = new Class[] {Class.class, Class.forName( |
| "junit.framework.JUnit4TestAdapterCache")}; |
| actualParams = new Object[] {testClass, junit4TestAdapterCacheClass |
| .getMethod("getInstance").invoke(null)}; |
| } |
| suite = junit4TestAdapterClass.asSubclass(Test.class) |
| .getConstructor(formalParams).newInstance(actualParams); |
| } else { |
| // Use JUnit 3. |
| |
| // try to extract a test suite automatically this |
| // will generate warnings if the class is no |
| // suitable Test |
| if (!testMethodsSpecified) { |
| suite = new TestSuite(testClass); |
| } else if (methods.length == 1) { |
| suite = TestSuite.createTest(testClass, methods[0]); |
| } else { |
| final TestSuite testSuite = new TestSuite(testClass.getName()); |
| for (String method : methods) { |
| testSuite.addTest(TestSuite.createTest(testClass, method)); |
| } |
| suite = testSuite; |
| } |
| } |
| } |
| } catch (final Throwable e) { |
| retCode = ERRORS; |
| exception = e; |
| } |
| |
| final long start = System.currentTimeMillis(); |
| |
| fireStartTestSuite(); |
| startTestSuiteSuccess = true; |
| if (exception != null) { // had an exception constructing suite |
| for (JUnitTaskMirror.JUnitResultFormatterMirror f : formatters) { |
| ((TestListener) f).addError(null, exception); |
| } |
| junitTest.setCounts(1, 0, 1, 0); |
| junitTest.setRunTime(0); |
| } else { |
| try { |
| logTestListenerEvent("tests to run: " + suite.countTestCases()); |
| suite.run(res); |
| } finally { |
| if (junit4 || suite.getClass().getName().equals(JUNIT_4_TEST_ADAPTER)) { |
| final int[] cnts = findJUnit4FailureErrorCount(res); |
| junitTest.setCounts(res.runCount() + res.ignoredCount(), cnts[0], cnts[1], |
| res.ignoredCount() + res.skippedCount()); |
| } else { |
| junitTest.setCounts(res.runCount() + res.ignoredCount(), res.failureCount(), |
| res.errorCount(), res.ignoredCount() + res.skippedCount()); |
| } |
| junitTest.setRunTime(System.currentTimeMillis() - start); |
| } |
| } |
| } finally { |
| if (perm != null) { |
| perm.restoreSecurityManager(); |
| } |
| if (savedOut != null) { |
| System.setOut(savedOut); |
| } |
| if (savedErr != null) { |
| System.setErr(savedErr); |
| } |
| |
| systemError.close(); |
| systemError = null; |
| systemOut.close(); |
| systemOut = null; |
| if (startTestSuiteSuccess) { |
| String out, err; |
| try { |
| out = new String(outStrm.toByteArray()); |
| } catch (final OutOfMemoryError ex) { |
| out = "out of memory on output stream"; |
| } |
| try { |
| err = new String(errStrm.toByteArray()); |
| } catch (final OutOfMemoryError ex) { |
| err = "out of memory on error stream"; |
| } |
| sendOutAndErr(out, err); |
| } |
| } |
| fireEndTestSuite(); |
| |
| // junitTest has the correct counts for JUnit4, while res doesn't |
| if (retCode != SUCCESS || junitTest.errorCount() != 0) { |
| retCode = ERRORS; |
| } else if (junitTest.failureCount() != 0) { |
| retCode = FAILURES; |
| } |
| } |
| |
| private static boolean containsTests(final Class<?> testClass, final boolean isJUnit4) { |
| Class<? extends Annotation> testAnnotation = null; |
| Class<? extends Annotation> suiteAnnotation = null; |
| Class<? extends Annotation> runWithAnnotation = null; |
| |
| try { |
| testAnnotation = |
| Class.forName("org.junit.Test").asSubclass(Annotation.class); |
| } catch (final ClassNotFoundException e) { |
| if (isJUnit4) { |
| // odd - we think we're JUnit4 but don't support the test annotation. We therefore can't have any tests! |
| return false; |
| } |
| // else... we're a JUnit3 test and don't need the annotation |
| } |
| |
| try { |
| suiteAnnotation = Class.forName("org.junit.Suite.SuiteClasses") |
| .asSubclass(Annotation.class); |
| } catch (final ClassNotFoundException ex) { |
| // ignore - we don't have this annotation so make sure we don't check for it |
| } |
| try { |
| runWithAnnotation = Class.forName("org.junit.runner.RunWith") |
| .asSubclass(Annotation.class); |
| } catch (final ClassNotFoundException ex) { |
| // also ignore as this annotation doesn't exist so tests can't use it |
| } |
| |
| if (!isJUnit4 && !TestCase.class.isAssignableFrom(testClass)) { |
| //a test we think is JUnit3 but does not extend TestCase. Can't really be a test. |
| return false; |
| } |
| |
| // check if we have any inner classes that contain suitable test methods |
| for (final Class<?> innerClass : testClass.getDeclaredClasses()) { |
| if (containsTests(innerClass, isJUnit4) || containsTests(innerClass, !isJUnit4)) { |
| return true; |
| } |
| } |
| |
| if (Modifier.isAbstract(testClass.getModifiers()) |
| || Modifier.isInterface(testClass.getModifiers())) { |
| // can't instantiate class and no inner classes are tests either |
| return false; |
| } |
| |
| if (isJUnit4) { |
| if (suiteAnnotation != null && testClass.getAnnotation(suiteAnnotation) != null) { |
| // class is marked as a suite. Let JUnit try and work its magic on it. |
| return true; |
| } |
| if (runWithAnnotation != null && testClass.getAnnotation(runWithAnnotation) != null) { |
| /* Class is marked with @RunWith. If this class is badly written (no test methods, |
| * multiple constructors, private constructor etc) then the class is automatically |
| * run and fails in the IDEs I've tried... so I'm happy handing the class to JUnit |
| * to try and run, and let JUnit report a failure if a bad test case is provided. |
| * Trying to do anything else is likely to result in us filtering out cases that |
| * could be valid for future versions of JUnit so would just increase future |
| * maintenance work. |
| */ |
| return true; |
| } |
| } |
| |
| for (final Method m : testClass.getMethods()) { |
| if (isJUnit4) { |
| // check if suspected JUnit4 classes have methods with @Test annotation |
| if (m.getAnnotation(testAnnotation) != null) { |
| return true; |
| } |
| } else { |
| // check if JUnit3 class have public or protected no-args methods starting with |
| // names starting with test |
| if (m.getName().startsWith("test") |
| && m.getParameterTypes().length == 0 |
| && (Modifier.isProtected(m.getModifiers()) |
| || Modifier.isPublic(m.getModifiers()))) { |
| return true; |
| } |
| } |
| // check if JUnit3 or JUnit4 test have a public or protected, static, |
| // no-args 'suite' method |
| if ("suite".equals(m.getName()) && m.getParameterTypes().length == 0 |
| && (Modifier.isProtected(m.getModifiers()) |
| || Modifier.isPublic(m.getModifiers())) |
| && Modifier.isStatic(m.getModifiers())) { |
| return true; |
| } |
| } |
| |
| // no test methods found |
| return false; |
| } |
| |
| /** |
| * Returns what System.exit() would return in the standalone version. |
| * |
| * @return 2 if errors occurred, 1 if tests failed else 0. |
| */ |
| @Override |
| public int getRetCode() { |
| return retCode; |
| } |
| |
| /** |
| * Interface TestListener. |
| * |
| * <p>A new Test is started. |
| * @param t the test. |
| */ |
| @Override |
| public void startTest(final Test t) { |
| final String testName = JUnitVersionHelper.getTestCaseName(t); |
| logTestListenerEvent("startTest(" + testName + ")"); |
| } |
| |
| /** |
| * Interface TestListener. |
| * |
| * <p>A Test is finished. |
| * @param test the test. |
| */ |
| @Override |
| public void endTest(final Test test) { |
| final String testName = JUnitVersionHelper.getTestCaseName(test); |
| logTestListenerEvent("endTest(" + testName + ")"); |
| } |
| |
| private void logTestListenerEvent(String message) { |
| if (logTestListenerEvents) { |
| @SuppressWarnings("resource") |
| final PrintStream out = savedOut != null ? savedOut : System.out; |
| out.flush(); |
| final StringTokenizer messageLines = |
| new StringTokenizer(String.valueOf(message), "\r\n", false); |
| while (messageLines.hasMoreTokens()) { |
| out.println(JUnitTask.TESTLISTENER_PREFIX + messageLines.nextToken()); |
| } |
| out.flush(); |
| } |
| } |
| |
| /** |
| * Interface TestListener for JUnit <= 3.4. |
| * |
| * <p>A Test failed. |
| * @param test the test. |
| * @param t the exception thrown by the test. |
| */ |
| public void addFailure(final Test test, final Throwable t) { |
| final String testName = JUnitVersionHelper.getTestCaseName(test); |
| logTestListenerEvent("addFailure(" + testName + ", " + t.getMessage() + ")"); |
| if (haltOnFailure) { |
| res.stop(); |
| } |
| } |
| |
| /** |
| * Interface TestListener for JUnit > 3.4. |
| * |
| * <p>A Test failed. |
| * @param test the test. |
| * @param t the assertion thrown by the test. |
| */ |
| @Override |
| public void addFailure(final Test test, final AssertionFailedError t) { |
| addFailure(test, (Throwable) t); |
| } |
| |
| /** |
| * Interface TestListener. |
| * |
| * <p>An error occurred while running the test. |
| * @param test the test. |
| * @param t the error thrown by the test. |
| */ |
| @Override |
| public void addError(final Test test, final Throwable t) { |
| final String testName = JUnitVersionHelper.getTestCaseName(test); |
| logTestListenerEvent("addError(" + testName + ", " + t.getMessage() + ")"); |
| if (haltOnError) { |
| res.stop(); |
| } |
| } |
| |
| /** |
| * Permissions for the test run. |
| * @since Ant 1.6 |
| * @param permissions the permissions to use. |
| */ |
| @Override |
| public void setPermissions(final Permissions permissions) { |
| perm = permissions; |
| } |
| |
| /** |
| * Handle a string destined for standard output. |
| * @param output the string to output |
| */ |
| @Override |
| public void handleOutput(final String output) { |
| if (!logTestListenerEvents && output.startsWith(JUnitTask.TESTLISTENER_PREFIX)) { |
| // ignore |
| } else if (systemOut != null) { |
| systemOut.print(output); |
| } |
| } |
| |
| /** |
| * Handle input. |
| * @param buffer not used. |
| * @param offset not used. |
| * @param length not used. |
| * @return -1 always. |
| * @throws IOException never. |
| * @see org.apache.tools.ant.Task#handleInput(byte[], int, int) |
| * |
| * @since Ant 1.6 |
| */ |
| @Override |
| public int handleInput(final byte[] buffer, final int offset, final int length) |
| throws IOException { |
| return -1; |
| } |
| |
| /** {@inheritDoc}. */ |
| @Override |
| public void handleErrorOutput(final String output) { |
| if (systemError != null) { |
| systemError.print(output); |
| } |
| } |
| |
| /** {@inheritDoc}. */ |
| @Override |
| public void handleFlush(final String output) { |
| if (systemOut != null) { |
| systemOut.print(output); |
| } |
| } |
| |
| /** {@inheritDoc}. */ |
| @Override |
| public void handleErrorFlush(final String output) { |
| if (systemError != null) { |
| systemError.print(output); |
| } |
| } |
| |
| private void sendOutAndErr(final String out, final String err) { |
| for (JUnitTaskMirror.JUnitResultFormatterMirror f : formatters) { |
| final JUnitResultFormatter formatter = (JUnitResultFormatter) f; |
| formatter.setSystemOutput(out); |
| formatter.setSystemError(err); |
| } |
| } |
| |
| private void fireStartTestSuite() { |
| for (JUnitTaskMirror.JUnitResultFormatterMirror f : formatters) { |
| ((JUnitResultFormatter) f).startTestSuite(junitTest); |
| } |
| } |
| |
| private void fireEndTestSuite() { |
| for (JUnitTaskMirror.JUnitResultFormatterMirror f : formatters) { |
| ((JUnitResultFormatter) f).endTestSuite(junitTest); |
| } |
| } |
| |
| /** |
| * Add a formatter. |
| * @param f the formatter to add. |
| */ |
| public void addFormatter(final JUnitResultFormatter f) { |
| formatters.addElement(f); |
| } |
| |
| /** {@inheritDoc}. */ |
| @Override |
| public void addFormatter(final JUnitTaskMirror.JUnitResultFormatterMirror f) { |
| formatters.addElement(f); |
| } |
| |
| /** |
| * Entry point for standalone (forked) mode. |
| * <p> |
| * Parameters: testcaseclassname plus parameters in the format |
| * key=value, none of which is required. |
| * </p> |
| * <table border="1"> |
| * <caption>Test runner attributes</caption> |
| * <tr> |
| * <th>key</th><th>description</th><th>default value</th> |
| * </tr> |
| * <tr> |
| * <td>haltOnError</td><td>halt test on errors?</td><td>false</td> |
| * </tr> |
| * <tr> |
| * <td>haltOnFailure</td><td>halt test on failures?</td><td>false</td> |
| * </tr> |
| * <tr> |
| * <td>formatter</td><td>A JUnitResultFormatter given as |
| * classname,filename. If filename is omitted, System.out is |
| * assumed.</td><td>none</td> |
| * </tr> |
| * <tr> |
| * <td>showoutput</td><td>send output to System.err/.out as |
| * well as to the formatters?</td><td>false</td> |
| * </tr> |
| * <tr> |
| * <td>logtestlistenerevents</td><td>log TestListener events to |
| * System.out.</td><td>false</td> |
| * </tr> |
| * <tr> |
| * <td>methods</td><td>Comma-separated list of names of individual |
| * test methods to execute.</td><td>null</td> |
| * </tr> |
| * </table> |
| * @param args the command line arguments. |
| * @throws IOException on error. |
| */ |
| public static void main(final String[] args) throws IOException { |
| String[] methods = null; |
| boolean haltError = false; |
| boolean haltFail = false; |
| boolean stackfilter = true; |
| final Properties props = new Properties(); |
| boolean showOut = false; |
| boolean outputToFormat = true; |
| boolean logFailedTests = true; |
| boolean logTestListenerEvents = false; |
| boolean skipNonTests = false; |
| /* Ant id of thread running this unit test, 0 in single-threaded mode */ |
| int antThreadID = 0; |
| |
| if (args.length == 0) { |
| System.err.println("required argument TestClassName missing"); |
| System.exit(ERRORS); |
| } |
| |
| if (args[0].startsWith(Constants.TESTSFILE)) { |
| multipleTests = true; |
| args[0] = args[0].substring(Constants.TESTSFILE.length()); |
| } |
| |
| for (String arg : args) { |
| if (arg.startsWith(Constants.METHOD_NAMES)) { |
| try { |
| final String methodsList = arg.substring(Constants.METHOD_NAMES.length()); |
| methods = JUnitTest.parseTestMethodNamesList(methodsList); |
| } catch (final IllegalArgumentException ex) { |
| System.err.println("Invalid specification of test method names: " + arg); |
| System.exit(ERRORS); |
| } |
| } else if (arg.startsWith(Constants.HALT_ON_ERROR)) { |
| haltError = Project.toBoolean(arg.substring(Constants.HALT_ON_ERROR.length())); |
| } else if (arg.startsWith(Constants.HALT_ON_FAILURE)) { |
| haltFail = Project.toBoolean(arg.substring(Constants.HALT_ON_FAILURE.length())); |
| } else if (arg.startsWith(Constants.FILTERTRACE)) { |
| stackfilter = Project.toBoolean(arg.substring(Constants.FILTERTRACE.length())); |
| } else if (arg.startsWith(Constants.CRASHFILE)) { |
| crashFile = arg.substring(Constants.CRASHFILE.length()); |
| registerTestCase(Constants.BEFORE_FIRST_TEST); |
| } else if (arg.startsWith(Constants.FORMATTER)) { |
| try { |
| createAndStoreFormatter(arg.substring(Constants.FORMATTER.length())); |
| } catch (final BuildException be) { |
| System.err.println(be.getMessage()); |
| System.exit(ERRORS); |
| } |
| } else if (arg.startsWith(Constants.PROPSFILE)) { |
| final InputStream in = Files.newInputStream(Paths.get(arg |
| .substring(Constants.PROPSFILE.length()))); |
| props.load(in); |
| in.close(); |
| } else if (arg.startsWith(Constants.SHOWOUTPUT)) { |
| showOut = Project.toBoolean(arg.substring(Constants.SHOWOUTPUT.length())); |
| } else if (arg.startsWith(Constants.LOGTESTLISTENEREVENTS)) { |
| logTestListenerEvents = Project.toBoolean( |
| arg.substring(Constants.LOGTESTLISTENEREVENTS.length())); |
| } else if (arg.startsWith(Constants.OUTPUT_TO_FORMATTERS)) { |
| outputToFormat = Project.toBoolean( |
| arg.substring(Constants.OUTPUT_TO_FORMATTERS.length())); |
| } else if (arg.startsWith(Constants.LOG_FAILED_TESTS)) { |
| logFailedTests = Project.toBoolean( |
| arg.substring(Constants.LOG_FAILED_TESTS.length())); |
| } else if (arg.startsWith(Constants.SKIP_NON_TESTS)) { |
| skipNonTests = Project.toBoolean( |
| arg.substring(Constants.SKIP_NON_TESTS.length())); |
| } else if (arg.startsWith(Constants.THREADID)) { |
| antThreadID = Integer.parseInt(arg.substring(Constants.THREADID.length())); |
| } |
| } |
| |
| // Add/overlay system properties on the properties from the Ant project |
| System.getProperties().forEach(props::put); |
| |
| int returnCode = SUCCESS; |
| if (multipleTests) { |
| try (final BufferedReader reader = new BufferedReader(new FileReader(args[0]))) { |
| int code = 0; |
| boolean errorOccurred = false; |
| boolean failureOccurred = false; |
| String line = null; |
| while ((line = reader.readLine()) != null) { |
| final StringTokenizer st = new StringTokenizer(line, ","); |
| final String testListSpec = st.nextToken(); |
| final int colonIndex = testListSpec.indexOf(':'); |
| String testCaseName; |
| String[] testMethodNames; |
| if (colonIndex == -1) { |
| testCaseName = testListSpec; |
| testMethodNames = null; |
| } else { |
| testCaseName = testListSpec.substring(0, colonIndex); |
| testMethodNames = JUnitTest.parseTestMethodNamesList( |
| testListSpec |
| .substring(colonIndex + 1) |
| .replace('+', ',')); |
| } |
| final JUnitTest t = new JUnitTest(testCaseName); |
| t.setTodir(new File(st.nextToken())); |
| t.setOutfile(st.nextToken()); |
| t.setProperties(props); |
| t.setSkipNonTests(skipNonTests); |
| t.setThread(antThreadID); |
| code = launch(t, testMethodNames, haltError, stackfilter, haltFail, |
| showOut, outputToFormat, |
| logTestListenerEvents); |
| errorOccurred = (code == ERRORS); |
| failureOccurred = (code != SUCCESS); |
| if (errorOccurred || failureOccurred) { |
| if ((errorOccurred && haltError) || (failureOccurred && haltFail)) { |
| registerNonCrash(); |
| System.exit(code); |
| } else { |
| if (code > returnCode) { |
| returnCode = code; |
| } |
| if (logFailedTests) { |
| System.out.println("TEST " + t.getName() + " FAILED"); |
| } |
| } |
| } |
| } |
| } catch (final IOException e) { |
| e.printStackTrace(); //NOSONAR |
| } |
| } else { |
| final JUnitTest t = new JUnitTest(args[0]); |
| t.setThread(antThreadID); |
| t.setProperties(props); |
| t.setSkipNonTests(skipNonTests); |
| returnCode = launch(t, methods, haltError, stackfilter, haltFail, showOut, |
| outputToFormat, logTestListenerEvents); |
| } |
| |
| registerNonCrash(); |
| System.exit(returnCode); |
| } |
| |
| private static Vector<FormatterElement> fromCmdLine = new Vector<>(); |
| |
| private static void transferFormatters(final JUnitTestRunner runner, |
| final JUnitTest test) { |
| runner.addFormatter(new JUnitResultFormatter() { |
| |
| @Override |
| public void startTestSuite(final JUnitTest suite) throws BuildException { |
| } |
| |
| @Override |
| public void endTestSuite(final JUnitTest suite) throws BuildException { |
| } |
| |
| @Override |
| public void setOutput(final OutputStream out) { |
| } |
| |
| @Override |
| public void setSystemOutput(final String out) { |
| } |
| |
| @Override |
| public void setSystemError(final String err) { |
| } |
| |
| @Override |
| public void addError(final Test arg0, final Throwable arg1) { |
| } |
| |
| @Override |
| public void addFailure(final Test arg0, final AssertionFailedError arg1) { |
| } |
| |
| @Override |
| public void endTest(final Test arg0) { |
| } |
| |
| @Override |
| public void startTest(final Test arg0) { |
| registerTestCase(JUnitVersionHelper.getTestCaseName(arg0)); |
| } |
| }); |
| for (FormatterElement fe : fromCmdLine) { |
| if (multipleTests && fe.getUseFile()) { |
| final File destFile = new File(test.getTodir(), |
| test.getOutfile() + fe.getExtension()); |
| fe.setOutfile(destFile); |
| } |
| runner.addFormatter((JUnitResultFormatter) fe.createFormatter()); |
| } |
| } |
| |
| /** |
| * Line format is: formatter=<classname>(,<pathname>)? |
| * |
| * @param line String |
| */ |
| private static void createAndStoreFormatter(final String line) |
| throws BuildException { |
| final FormatterElement fe = new FormatterElement(); |
| final int pos = line.indexOf(','); |
| if (pos == -1) { |
| fe.setClassname(line); |
| fe.setUseFile(false); |
| } else { |
| fe.setClassname(line.substring(0, pos)); |
| fe.setUseFile(true); |
| if (!multipleTests) { |
| fe.setOutfile(new File(line.substring(pos + 1))); |
| } else { |
| final int fName = line.indexOf(IGNORED_FILE_NAME); |
| if (fName > -1) { |
| fe.setExtension(line.substring(fName |
| + IGNORED_FILE_NAME.length())); |
| } |
| } |
| } |
| fromCmdLine.addElement(fe); |
| } |
| |
| /** |
| * Returns a filtered stack trace. |
| * This is ripped out of junit.runner.BaseTestRunner. |
| * @param t the exception to filter. |
| * @return the filtered stack trace. |
| */ |
| public static String getFilteredTrace(final Throwable t) { |
| final String trace = StringUtils.getStackTrace(t); |
| return JUnitTestRunner.filterStack(trace); |
| } |
| |
| /** |
| * Filters stack frames from internal JUnit and Ant classes |
| * @param stack the stack trace to filter. |
| * @return the filtered stack. |
| */ |
| public static String filterStack(final String stack) { |
| if (!filtertrace) { |
| return stack; |
| } |
| final StringWriter sw = new StringWriter(); |
| final BufferedWriter pw = new BufferedWriter(sw); |
| final StringReader sr = new StringReader(stack); |
| final BufferedReader br = new BufferedReader(sr); |
| |
| String line; |
| try { |
| boolean firstLine = true; |
| while ((line = br.readLine()) != null) { |
| if (firstLine || !filterLine(line)) { |
| pw.write(line); |
| pw.newLine(); |
| } |
| firstLine = false; |
| } |
| } catch (final Exception e) { |
| return stack; // return the stack unfiltered |
| } finally { |
| FileUtils.close(pw); |
| } |
| return sw.toString(); |
| } |
| |
| private static boolean filterLine(final String line) { |
| for (String filter : DEFAULT_TRACE_FILTERS) { |
| if (line.contains(filter)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @since Ant 1.6.2 |
| */ |
| private static int launch(final JUnitTest t, final String[] methods, final boolean haltError, |
| final boolean stackfilter, final boolean haltFail, |
| final boolean showOut, final boolean outputToFormat, |
| final boolean logTestListenerEvents) { |
| final JUnitTestRunner runner = |
| new JUnitTestRunner(t, methods, haltError, stackfilter, haltFail, showOut, |
| logTestListenerEvents, null); |
| runner.forked = true; |
| runner.outputToFormatters = outputToFormat; |
| transferFormatters(runner, t); |
| |
| runner.run(); |
| return runner.getRetCode(); |
| } |
| |
| /** |
| * @since Ant 1.7 |
| */ |
| private static void registerNonCrash() |
| throws IOException { |
| if (crashFile != null) { |
| try (FileWriter out = new FileWriter(crashFile)) { |
| out.write(Constants.TERMINATED_SUCCESSFULLY + "\n"); |
| out.flush(); |
| } |
| } |
| } |
| |
| private static void registerTestCase(final String testCase) { |
| if (crashFile != null) { |
| try (FileWriter out = new FileWriter(crashFile)) { |
| out.write(testCase + "\n"); |
| out.flush(); |
| } catch (final IOException e) { |
| // ignored. |
| } |
| } |
| } |
| |
| /** |
| * Modifies a TestListener when running JUnit 4: treats AssertionFailedError |
| * as a failure not an error. |
| * |
| * @since Ant 1.7 |
| */ |
| private TestListenerWrapper wrapListener(final TestListener testListener) { |
| return new TestListenerWrapper(testListener) { |
| @Override |
| public void addError(final Test test, final Throwable t) { |
| if (junit4 && t instanceof AssertionFailedError) { |
| // JUnit 4 does not distinguish between errors and failures |
| // even in the JUnit 3 adapter. |
| // So we need to help it a bit to retain compatibility for JUnit 3 tests. |
| testListener.addFailure(test, (AssertionFailedError) t); |
| } else if (junit4 && t instanceof AssertionError) { |
| // Not strictly necessary but probably desirable. |
| // JUnit 4-specific test GUIs will show just "failures". |
| // But Ant's output shows "failures" vs. "errors". |
| // We would prefer to show "failure" for things that logically are. |
| final String message = t.getMessage(); |
| final AssertionFailedError failure = message != null |
| ? new AssertionFailedError(message) : new AssertionFailedError(); |
| failure.initCause(t.getCause()); |
| failure.setStackTrace(t.getStackTrace()); |
| testListener.addFailure(test, failure); |
| } else { |
| testListener.addError(test, t); |
| } |
| } |
| @Override |
| public void addFailure(final Test test, final AssertionFailedError t) { |
| testListener.addFailure(test, t); |
| } |
| @SuppressWarnings("unused") |
| public void addFailure(final Test test, final Throwable t) { // pre-3.4 |
| if (t instanceof AssertionFailedError) { |
| testListener.addFailure(test, (AssertionFailedError) t); |
| } else { |
| testListener.addError(test, t); |
| } |
| } |
| @Override |
| public void endTest(final Test test) { |
| testListener.endTest(test); |
| } |
| @Override |
| public void startTest(final Test test) { |
| testListener.startTest(test); |
| } |
| }; |
| } |
| |
| /** |
| * Use instead of TestResult.get{Failure,Error}Count on JUnit 4, |
| * since the adapter claims that all failures are errors. |
| * @since Ant 1.7 |
| */ |
| private int[] findJUnit4FailureErrorCount(final TestResult result) { |
| int failures = 0; |
| int errors = 0; |
| { |
| @SuppressWarnings("unchecked") |
| Enumeration<TestFailure> e = result.failures(); |
| while (e.hasMoreElements()) { |
| e.nextElement(); |
| failures++; |
| } |
| } |
| @SuppressWarnings("unchecked") |
| Enumeration<TestFailure> e = result.errors(); |
| while (e.hasMoreElements()) { |
| final Throwable t = |
| e.nextElement().thrownException(); |
| if (t instanceof AssertionFailedError |
| || t instanceof AssertionError) { |
| failures++; |
| } else { |
| errors++; |
| } |
| } |
| return new int[] {failures, errors}; |
| } |
| |
| } |