@@ -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;
@@ -319,6 +323,22 @@ public class JUnitTask extends Task {
this.forkMode = mode;
}
/**
* 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) {
@@ -858,6 +1025,15 @@ public class JUnitTask extends Task {
actOnTestResult(result, test, "Test " + test.getName());
}
/**
* 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
@@ -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(',', '+'));
@@ -935,6 +1113,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
@@ -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);