Author: antoine
Date: Sun Mar 23 16:18:29 2014
New Revision: 1580520
URL: http://svn.apache.org/r1580520
Log:
junit task should support parallel/threads option
PR 55925
Modified:
ant/core/trunk/CONTRIBUTORS
ant/core/trunk/WHATSNEW
ant/core/trunk/build.xml
ant/core/trunk/contributors.xml
ant/core/trunk/manual/Tasks/junit.html
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java
Modified: ant/core/trunk/CONTRIBUTORS
URL:
http://svn.apache.org/viewvc/ant/core/trunk/CONTRIBUTORS?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
--- ant/core/trunk/CONTRIBUTORS (original)
+++ ant/core/trunk/CONTRIBUTORS Sun Mar 23 16:18:29 2014
@@ -183,6 +183,7 @@ Jim Allers
Joerg Wassmer
Joey Richey
Johann Herunter
+John Elion
John Sisson
Jon Dickinson
Jon S. Stevens
Modified: ant/core/trunk/WHATSNEW
URL:
http://svn.apache.org/viewvc/ant/core/trunk/WHATSNEW?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
--- ant/core/trunk/WHATSNEW (original)
+++ ant/core/trunk/WHATSNEW Sun Mar 23 16:18:29 2014
@@ -144,6 +144,9 @@ Other changes:
when enabled.
GitHub Pull Request #1
+* <junit> has now a threads attribute allowing to run the tests in several
threads.
+ Bugzilla Report 55925
+
Changes from Ant 1.9.2 TO Ant 1.9.3
===================================
Modified: ant/core/trunk/build.xml
URL:
http://svn.apache.org/viewvc/ant/core/trunk/build.xml?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
--- ant/core/trunk/build.xml (original)
+++ ant/core/trunk/build.xml Sun Mar 23 16:18:29 2014
@@ -111,6 +111,12 @@
<property name="test.haltonfailure" value="false"/>
<property name="junit.fork" value="true"/>
<property name="junit.forkmode" value="once"/>
+ <condition property="junit.threads" value="2" else="0">
+ <and>
+ <equals arg1="${junit.fork}" arg2="true"/>
+ <equals arg1="${junit.forkmode}" arg2="perTest"/>
+ </and>
+</condition>
<property name="expandproperty.files"
value="**/version.txt,**/defaultManifest.mf"/>
<property name="junit.collector.dir" value="${build.dir}/failingTests"/>
@@ -1659,6 +1665,7 @@ ${antunit.reports}
haltonfailure="${test.haltonfailure}"
fork="${junit.fork}"
forkmode="${junit.forkmode}"
+ threads="${junit.threads}"
failureproperty="junit.failed"
errorproperty="junit.failed"
filtertrace="${junit.filtertrace}">
Modified: ant/core/trunk/contributors.xml
URL:
http://svn.apache.org/viewvc/ant/core/trunk/contributors.xml?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
--- ant/core/trunk/contributors.xml (original)
+++ ant/core/trunk/contributors.xml Sun Mar 23 16:18:29 2014
@@ -757,6 +757,10 @@
</name>
<name>
<first>John</first>
+ <last>Elion</last>
+ </name>
+ <name>
+ <first>John</first>
<last>Sisson</last>
</name>
<name>
Modified: ant/core/trunk/manual/Tasks/junit.html
URL:
http://svn.apache.org/viewvc/ant/core/trunk/manual/Tasks/junit.html?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
--- ant/core/trunk/manual/Tasks/junit.html (original)
+++ ant/core/trunk/manual/Tasks/junit.html Sun Mar 23 16:18:29 2014
@@ -247,7 +247,16 @@ elements</a>).</p>
<em>since Ant 1.8.2</em> - <strong>Ant 1.7.0 to 1.8.1 behave as
if this attribute was true by default.</strong></td>
<td align="center" valign="top">No</td>
- </tr>
+ </tr>
+ <tr>
+ <td valign="top">threads</td>
+ <td valign="top">a number of threads to run the tests in.<br/>
+ When this attribute is specified the tests will be split arbitrarily
among the threads.<br/>
+ requires that the tests be forked with the <code>perTest</code>
+ option to be operative.<br/>
+ <em>since Ant 1.9.4</em></td>
+ <td align="center" valign="top">No</td>
+ </tr>
</table>
<p>By using the <code>errorproperty</code> and <code>failureproperty</code>
Modified:
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
URL:
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
---
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
(original)
+++
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
Sun Mar 23 16:18:29 2014
@@ -38,4 +38,6 @@ public class Constants {
static final String TERMINATED_SUCCESSFULLY = "terminated successfully";
static final String LOG_FAILED_TESTS="logfailedtests=";
static final String SKIP_NON_TESTS = "skipNonTests=";
+ /** @since Ant 1.9.4 */
+ static final String THREADID="threadid=";
}
Modified:
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
URL:
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
---
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
(original)
+++
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
Sun Mar 23 16:18:29 2014
@@ -172,11 +172,15 @@ public class JUnitTask extends Task {
/** A boolean on whether to get the forked path for ant classes */
private boolean forkedPathChecked = false;
+ /* set when a test fails/errs with haltonfailure/haltonerror and >1 thread
to stop other threads */
+ private volatile BuildException caughtBuildException = null;
+
// Attributes for basetest
private boolean haltOnError = false;
private boolean haltOnFail = false;
private boolean filterTrace = true;
private boolean fork = false;
+ private int threads = 1;
private String failureProperty;
private String errorProperty;
@@ -320,6 +324,22 @@ public class JUnitTask extends Task {
}
/**
+ * Set the number of test threads to be used for parallel test
+ * execution. The default is 1, which is the same behavior as
+ * before parallel test execution was possible.
+ *
+ * <p>This attribute will be ignored if tests run in the same VM
+ * as Ant.</p>
+ *
+ * @since Ant 1.9.4
+ */
+ public void setThreads(int threads) {
+ if (threads >= 0) {
+ this.threads = threads;
+ }
+ }
+
+ /**
* If true, print one-line statistics for each test, or "withOutAndErr"
* to also show standard output and error.
*
@@ -798,6 +818,10 @@ public class JUnitTask extends Task {
setupJUnitDelegate();
List testLists = new ArrayList();
+ /* parallel test execution is only supported for multi-process
execution */
+ int threads = ((!fork) || (forkMode.getValue().equals(ForkMode.ONCE))
+ ? 1
+ : this.threads);
boolean forkPerTest = forkMode.getValue().equals(ForkMode.PER_TEST);
if (forkPerTest || forkMode.getValue().equals(ForkMode.ONCE)) {
@@ -813,29 +837,172 @@ public class JUnitTask extends Task {
}
try {
- Iterator iter = testLists.iterator();
- while (iter.hasNext()) {
- List l = (List) iter.next();
- if (l.size() == 1) {
- execute((JUnitTest) l.get(0));
- } else {
- execute(l);
- }
- }
+ /* prior to parallel the code in 'oneJunitThread' used to be here.
*/
+ runTestsInThreads(testLists, threads);
} finally {
cleanup();
}
}
+ /*
+ * When the list of tests is established, an array of threads is created
to pick the
+ * tests off the list one at a time and execute them until the list is
empty. Tests are
+ * not assigned to threads until the thread is available.
+ *
+ * This class is the runnable thread subroutine that takes care of passing
the shared
+ * list iterator and the handle back to the main class to the test
execution subroutine
+ * code 'runTestsInThreads'. One object is created for each thread and
each one gets
+ * a unique thread id that can be useful for tracing test starts and stops.
+ *
+ * Because the threads are picking tests off the same list, it is the list
*iterator*
+ * that must be shared, not the list itself - and the iterator must have a
thread-safe
+ * ability to pop the list - hence the synchronized 'getNextTest'.
+ */
+ private class JunitTestThread implements Runnable {
+
+ JunitTestThread(JUnitTask master, Iterator iterator, int id) {
+ this.masterTask = master;
+ this.iterator = iterator;
+ this.id = id;
+ }
+
+ public void run() {
+ try {
+ masterTask.oneJunitThread(iterator, id);
+ } catch (BuildException b) {
+ /* saved to rethrow in main thread to be like single-threaded
case */
+ caughtBuildException = b;
+ }
+ }
+
+ private JUnitTask masterTask;
+ private Iterator iterator;
+ private int id;
+ }
+
+ /*
+ * Because the threads are picking tests off the same list, it is the list
*iterator*
+ * that must be shared, not the list itself - and the iterator must have a
thread-safe
+ * ability to pop the list - hence the synchronized 'getNextTest'. We
can't have two
+ * threads get the same test, or two threads simultaneously pop the list
so that a test
+ * gets skipped!
+ */
+ private List getNextTest(Iterator iter) {
+ synchronized(iter) {
+ if (iter.hasNext()) {
+ return (List) iter.next();
+ }
+ return null;
+ }
+ }
+
+ /*
+ * This code loops keeps executing the next test or test bunch (depending
on fork mode)
+ * on the list of test cases until none are left. Basically this body of
code used to
+ * be in the execute routine above; now, several copies (one for each test
thread) execute
+ * simultaneously. The while loop was modified to call the new
thread-safe atomic list
+ * popping subroutine and the logging messages were added.
+ *
+ * If one thread aborts due to a BuildException (haltOnError,
haltOnFailure, or any other
+ * fatal reason, no new tests/batches will be started but the running
threads will be
+ * permitted to complete. Additional tests may start in already-running
batch-test threads.
+ */
+ private void oneJunitThread(Iterator iter, int threadId) {
+
+ List l;
+ log("Starting test thread " + threadId, Project.MSG_VERBOSE);
+ while ((caughtBuildException == null) && ((l = getNextTest(iter)) !=
null)) {
+ log("Running test " + l.get(0).toString() + "(" + l.size() + ") in
thread " + threadId, Project.MSG_VERBOSE);
+ if (l.size() == 1) {
+ execute((JUnitTest) l.get(0), threadId);
+ } else {
+ execute(l, threadId);
+ }
+ }
+ log("Ending test thread " + threadId, Project.MSG_VERBOSE);
+ }
+
+
+ private void runTestsInThreads(List testList, int numThreads) {
+
+ Iterator iter = testList.iterator();
+
+ if (numThreads == 1) {
+ /* with just one thread just run the test - don't create any
threads */
+ oneJunitThread(iter, 0);
+ }
+ else {
+ Thread threads[] = new Thread[numThreads];
+ int i;
+ boolean exceptionOccurred;
+
+ /* Need to split apart tests, which are still grouped in batches */
+ /* is there a simpler Java mechanism to do this? */
+ /* I assume we don't want to do this with "per batch" forking. */
+ List newlist = new ArrayList();
+ if (forkMode.getValue().equals(ForkMode.PER_TEST)) {
+ Iterator i1 = testList.iterator();
+ while (i1.hasNext()) {
+ List l = (List) i1.next();
+ if (l.size() == 1) {
+ newlist.add(l);
+ } else {
+ Iterator i2 = l.iterator();
+ while (i2.hasNext()) {
+ List tmpSingleton = new ArrayList();
+ tmpSingleton.add(i2.next());
+ newlist.add(tmpSingleton);
+ }
+ }
+ }
+ } else {
+ newlist = testList;
+ }
+ iter = newlist.iterator();
+
+ /* create 1 thread using the passthrough class, and let each
thread start */
+ for (i = 0; i < numThreads; i++) {
+ threads[i] = new Thread(new JunitTestThread(this, iter, i+1));
+ threads[i].start();
+ }
+
+ /* wait for all of the threads to complete. Not sure if the
exception can actually occur in this use case. */
+ do {
+ exceptionOccurred = false;
+
+ try {
+ for (i = 0; i < numThreads; i++) {
+ threads[i].join();
+ }
+ }
+ catch (InterruptedException e) {
+ exceptionOccurred = true;
+ }
+ } while (exceptionOccurred);
+
+ /* an exception occurred in one of the threads - usually a
haltOnError/Failure.
+ throw the exception again so it behaves like the single-thread
case */
+ if (caughtBuildException != null) {
+ throw new BuildException(caughtBuildException);
+ }
+
+ /* all threads are completed - that's all there is to do. */
+ /* control will flow back to the test cleanup call and then
execute is done. */
+ }
+ }
+
/**
* Run the tests.
* @param arg one JUnitTest
+ * @param thread Identifies which thread is test running in (0 for
single-threaded runs)
* @throws BuildException in case of test failures or errors
*/
- protected void execute(JUnitTest arg) throws BuildException {
+ protected void execute(JUnitTest arg, int thread) throws BuildException {
validateTestName(arg.getName());
JUnitTest test = (JUnitTest) arg.clone();
+ test.setThread(thread);
+
// set the default values if not specified
//@todo should be moved to the test class instead.
if (test.getTodir() == null) {
@@ -859,6 +1026,15 @@ public class JUnitTask extends Task {
}
/**
+ * Run the tests.
+ * @param arg one JUnitTest
+ * @throws BuildException in case of test failures or errors
+ */
+ protected void execute(JUnitTest arg) throws BuildException {
+ execute(arg, 0);
+ }
+
+ /**
* Throws a <code>BuildException</code> if the given test name is invalid.
* Validity is defined as not <code>null</code>, not empty, and not the
* string "null".
@@ -875,9 +1051,10 @@ public class JUnitTask extends Task {
/**
* Execute a list of tests in a single forked Java VM.
* @param testList the list of tests to execute.
+ * @param thread Identifies which thread is test running in (0 for
single-threaded runs)
* @throws BuildException on error.
*/
- protected void execute(List testList) throws BuildException {
+ protected void execute(List testList, int thread) throws BuildException {
JUnitTest test = null;
// Create a temporary file to pass the test cases to run to
// the runner (one test case per line)
@@ -894,6 +1071,7 @@ public class JUnitTask extends Task {
Iterator iter = testList.iterator();
while (iter.hasNext()) {
test = (JUnitTest) iter.next();
+ test.setThread(thread);
printDual(writer, logWriter, test.getName());
if (test.getMethods() != null) {
printDual(writer, logWriter, ":" +
test.getMethodsString().replace(',', '+'));
@@ -936,6 +1114,15 @@ public class JUnitTask extends Task {
}
/**
+ * Execute a list of tests in a single forked Java VM.
+ * @param testList the list of tests to execute.
+ * @throws BuildException on error.
+ */
+ protected void execute(List testList) throws BuildException {
+ execute(testList, 0);
+ }
+
+ /**
* Execute a testcase by forking a new JVM. The command will block
* until it finishes. To know if the process was destroyed or not
* or whether the forked Java VM exited abnormally, use the
@@ -991,6 +1178,8 @@ public class JUnitTask extends Task {
+ String.valueOf(outputToFormatters));
cmd.createArgument().setValue(Constants.LOG_FAILED_TESTS
+ String.valueOf(logFailedTests));
+ cmd.createArgument().setValue(Constants.THREADID
+ + String.valueOf(test.getThread()));
// #31885
cmd.createArgument().setValue(Constants.LOGTESTLISTENEREVENTS
@@ -1900,8 +2089,10 @@ public class JUnitTask extends Task {
while (testList.hasMoreElements()) {
JUnitTest test = (JUnitTest) testList.nextElement();
if (test.shouldRun(getProject())) {
- if (runIndividual || !test.getFork()) {
- execute(test);
+ /* with multi-threaded runs need to defer execution of even */
+ /* individual tests so the threads can pick tests off the
queue. */
+ if ((runIndividual || !test.getFork()) && (threads == 1)) {
+ execute(test, 0);
} else {
ForkedTestConfiguration c =
new ForkedTestConfiguration(test);
Modified:
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
URL:
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
---
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
(original)
+++
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
Sun Mar 23 16:18:29 2014
@@ -69,6 +69,8 @@ public class JUnitTest extends BaseTest
private long runTime;
+ private int antThreadID;
+
// Snapshot of the system properties
private Properties props = null;
@@ -93,9 +95,9 @@ public class JUnitTest extends BaseTest
*/
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure,
boolean filtertrace) {
- this(name, haltOnError, haltOnFailure, filtertrace, null);
- }
-
+ this(name, haltOnError, haltOnFailure, filtertrace, null, 0);
+ }
+
/**
* Constructor with options.
* @param name the name of the test.
@@ -107,12 +109,28 @@ public class JUnitTest extends BaseTest
*/
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure,
boolean filtertrace, String[] methods) {
+ this(name, haltOnError, haltOnFailure, filtertrace, methods, 0);
+ }
+
+ /**
+ * Constructor with options.
+ * @param name the name of the test.
+ * @param haltOnError if true halt the tests if there is an error.
+ * @param haltOnFailure if true halt the tests if there is a failure.
+ * @param filtertrace if true filter stack traces.
+ * @param methods if non-null run only these test methods
+ * @param thread Ant thread ID in which test is currently running
+ * @since 1.9.4
+ */
+ public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure,
+ boolean filtertrace, String[] methods, int thread) {
this.name = name;
this.haltOnError = haltOnError;
this.haltOnFail = haltOnFailure;
this.filtertrace = filtertrace;
this.methodsSpecified = methods != null;
this.methods = methodsSpecified ? (String[]) methods.clone() : null;
+ this.antThreadID = thread;
}
/**
@@ -149,6 +167,17 @@ public class JUnitTest extends BaseTest
}
/**
+ * Set the thread id
+ * @param thread the Ant id of the thread running this test
+ * (this is not the system process or thread id)
+ * (this will be 0 in single-threaded mode).
+ * @since Ant 1.9.4
+ */
+ public void setThread(int thread) {
+ this.antThreadID = thread;
+ }
+
+ /**
* Set the name of the output file.
* @param value the name of the output file to use.
*/
@@ -348,6 +377,14 @@ public class JUnitTest extends BaseTest
}
/**
+ * Get the Ant id of the thread running the test.
+ * @return the thread id
+ */
+ public int getThread() {
+ return antThreadID;
+ }
+
+ /**
* Get the name of the output file
*
* @return the name of the output file.
Modified:
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
URL:
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
---
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
(original)
+++
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
Sun Mar 23 16:18:29 2014
@@ -899,7 +899,7 @@ public class JUnitTestRunner implements
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");
@@ -955,6 +955,8 @@ public class JUnitTestRunner implements
} 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()) );
}
}
@@ -995,6 +997,7 @@ public class JUnitTestRunner implements
t.setOutfile(st.nextToken());
t.setProperties(props);
t.setSkipNonTests(skipNonTests);
+ t.setThread(antThreadID);
code = launch(t, testMethodNames, haltError, stackfilter,
haltFail,
showOut, outputToFormat,
logTestListenerEvents);
@@ -1021,6 +1024,7 @@ public class JUnitTestRunner implements
}
} else {
JUnitTest t = new JUnitTest(args[0]);
+ t.setThread(antThreadID);
t.setProperties(props);
t.setSkipNonTests(skipNonTests);
returnCode = launch(
Modified:
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java
URL:
http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java?rev=1580520&r1=1580519&r2=1580520&view=diff
==============================================================================
---
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java
(original)
+++
ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java
Sun Mar 23 16:18:29 2014
@@ -53,6 +53,27 @@ public class SummaryJUnitResultFormatter
*/
public SummaryJUnitResultFormatter() {
}
+
+ /**
+ * Insures that a line of log output is written and flushed as a single
+ * operation, to prevent lines from being spliced into other lines.
+ * (Hopefully this solves the issue of run on lines -
+ * [junit] Tests Run: 2 Failures: 2 [junit] Tests run: 5...
+ * synchronized doesn't seem to be to harsh a penalty since it only
+ * occurs twice per test - at the beginning and end. Note that message
+ * construction occurs outside the locked block.
+ *
+ * @param b data to be written as an unbroken block
+ */
+ private synchronized void writeOutputLine(byte[] b) {
+ try {
+ out.write(b);
+ out.flush();
+ } catch (IOException ioex) {
+ throw new BuildException("Unable to write summary output", ioex);
+ }
+ }
+
/**
* The testsuite started.
* @param suite the testsuite.
@@ -60,15 +81,16 @@ public class SummaryJUnitResultFormatter
public void startTestSuite(JUnitTest suite) {
String newLine = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer("Running ");
- sb.append(suite.getName());
- sb.append(newLine);
+ int antThreadID = suite.getThread();
- try {
- out.write(sb.toString().getBytes());
- out.flush();
- } catch (IOException ioex) {
- throw new BuildException("Unable to write summary output", ioex);
+ sb.append(suite.getName());
+ /* only write thread id in multi-thread mode so default old way
doesn't change output */
+ if (antThreadID > 0) {
+ sb.append(" in thread ");
+ sb.append(antThreadID);
}
+ sb.append(newLine);
+ writeOutputLine(sb.toString().getBytes());
}
/**
* Empty
@@ -149,6 +171,17 @@ public class SummaryJUnitResultFormatter
sb.append(", Time elapsed: ");
sb.append(nf.format(suite.getRunTime() / ONE_SECOND));
sb.append(" sec");
+
+ /* class name needed with multi-threaded execution because
+ results line may not appear immediately below start line.
+ only write thread id, class name in multi-thread mode so
+ the line still looks as much like the old line as possible. */
+ if (suite.getThread() > 0) {
+ sb.append(", Thread: ");
+ sb.append(suite.getThread());
+ sb.append(", Class: ");
+ sb.append(suite.getName());
+ }
sb.append(newLine);
if (withOutAndErr) {
@@ -164,10 +197,7 @@ public class SummaryJUnitResultFormatter
}
try {
- out.write(sb.toString().getBytes());
- out.flush();
- } catch (IOException ioex) {
- throw new BuildException("Unable to write summary output", ioex);
+ writeOutputLine(sb.toString().getBytes());
} finally {
if (out != System.out && out != System.err) {
try {