/* * 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 * * 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 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.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><code> * public static junit.framework.Test suite() * </code></pre> * * <p> If no such method exists, all public methods starting with * "test" and taking no argument will be run. * * <p> Summary output is generated at the end. * * @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. * @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 ? (String[]) 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)); final int size = formatters.size(); for (int i = 0; i < size; i++) { res.addListener(wrapListener((TestListener) formatters.elementAt(i))); } 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()); } else { testClass = Class.forName(junitTest.getName(), true, 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 (int i = 0; i < methods.length; i++) { testSuite.addTest( TestSuite.createTest(testClass, methods[i])); } 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 final int formatterSize = formatters.size(); for (int i = 0; i < formatterSize; i++) { ((TestListener) formatters.elementAt(i)) .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 msg) { if (logTestListenerEvents) { final PrintStream out = savedOut != null ? savedOut : System.out; out.flush(); final StringTokenizer msgLines = new StringTokenizer(String.valueOf(msg), "\r\n", false); while (msgLines.hasMoreTokens()) { out.println(JUnitTask.TESTLISTENER_PREFIX + msgLines.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) { final int size = formatters.size(); for (int i = 0; i < size; i++) { final JUnitResultFormatter formatter = (JUnitResultFormatter) formatters.get(i); formatter.setSystemOutput(out); formatter.setSystemError(err); } } private void fireStartTestSuite() { final int size = formatters.size(); for (int i = 0; i < size; i++) { ((JUnitResultFormatter) formatters.elementAt(i)) .startTestSuite(junitTest); } } private void fireEndTestSuite() { final int size = formatters.size(); for (int i = 0; i < size; i++) { ((JUnitResultFormatter) formatters.elementAt(i)) .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. * * Parameters: testcaseclassname plus parameters in the format * key=value, none of which is required. * * <table cols="4" border="1"> * <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; int antThreadID = 0; /* Ant id of thread running this unit test, 0 in single-threaded mode */ 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 (int i = 1; i < args.length; i++) { if (args[i].startsWith(Constants.METHOD_NAMES)) { try { final String methodsList = args[i].substring(Constants.METHOD_NAMES.length()); methods = JUnitTest.parseTestMethodNamesList(methodsList); } catch (final IllegalArgumentException ex) { System.err.println("Invalid specification of test method names: " + args[i]); System.exit(ERRORS); } } else if (args[i].startsWith(Constants.HALT_ON_ERROR)) { haltError = Project.toBoolean(args[i].substring(Constants.HALT_ON_ERROR.length())); } else if (args[i].startsWith(Constants.HALT_ON_FAILURE)) { haltFail = Project.toBoolean(args[i].substring(Constants.HALT_ON_FAILURE.length())); } else if (args[i].startsWith(Constants.FILTERTRACE)) { stackfilter = Project.toBoolean(args[i].substring(Constants.FILTERTRACE.length())); } else if (args[i].startsWith(Constants.CRASHFILE)) { crashFile = args[i].substring(Constants.CRASHFILE.length()); registerTestCase(Constants.BEFORE_FIRST_TEST); } else if (args[i].startsWith(Constants.FORMATTER)) { try { createAndStoreFormatter(args[i].substring(Constants.FORMATTER.length())); } catch (final BuildException be) { System.err.println(be.getMessage()); System.exit(ERRORS); } } else if (args[i].startsWith(Constants.PROPSFILE)) { final InputStream in = Files.newInputStream(Paths.get(args[i] .substring(Constants.PROPSFILE.length()))); props.load(in); in.close(); } else if (args[i].startsWith(Constants.SHOWOUTPUT)) { showOut = Project.toBoolean(args[i].substring(Constants.SHOWOUTPUT.length())); } else if (args[i].startsWith(Constants.LOGTESTLISTENEREVENTS)) { logTestListenerEvents = Project.toBoolean( args[i].substring(Constants.LOGTESTLISTENEREVENTS.length())); } else if (args[i].startsWith(Constants.OUTPUT_TO_FORMATTERS)) { outputToFormat = Project.toBoolean( args[i].substring(Constants.OUTPUT_TO_FORMATTERS.length())); } else if (args[i].startsWith(Constants.LOG_FAILED_TESTS)) { logFailedTests = Project.toBoolean( args[i].substring(Constants.LOG_FAILED_TESTS.length())); } else if (args[i].startsWith(Constants.SKIP_NON_TESTS)) { skipNonTests = Project.toBoolean( args[i].substring(Constants.SKIP_NON_TESTS.length())); } else if (args[i].startsWith(Constants.THREADID)) { antThreadID = Integer.parseInt(args[i].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 java.io.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)); } }); final int size = fromCmdLine.size(); for (int i = 0; i < size; i++) { final FormatterElement fe = fromCmdLine.elementAt(i); 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>)? */ 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 (int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++) { if (line.indexOf(DEFAULT_TRACE_FILTERS[i]) != -1) { 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) { FileWriter out = null; try { out = new FileWriter(crashFile); out.write(Constants.TERMINATED_SUCCESSFULLY + "\n"); out.flush(); } finally { FileUtils.close(out); } } } private static void registerTestCase(final String testCase) { if (crashFile != null) { try { FileWriter out = null; try { out = new FileWriter(crashFile); out.write(testCase + "\n"); out.flush(); } finally { FileUtils.close(out); } } 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 msg = t.getMessage(); final AssertionFailedError failure = msg != null ? new AssertionFailedError(msg) : 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); } 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 }; } } // JUnitTestRunner