diff --git a/build.xml b/build.xml
index 928607286..c966d6f0c 100644
--- a/build.xml
+++ b/build.xml
@@ -497,6 +497,8 @@
<jvmarg>
attributes, for example:
would run the test in a VM without JIT. @@ -93,6 +94,23 @@ would run the test in a VM without JIT.<junit fork="yes"> <jvmarg value="-Djava.compiler=NONE"/> + ... </junit>
<jvmarg>
allows all attributes described in Command line arguments.
Use nested <sysproperty>
elements to specify system
+properties required by the class. These properties will be made available
+to the VM during the execution of the test (either ANT's VM or the forked VM).
+The attributes for this element are the same as for environment variables.
+
+
+would run the test in ANT's VM and make the+<junit fork="no"> + <sysproperty key="basedir" value="${basedir}"/> + ... +</junit> +
basedir
property
+available to the test.
+
+
The results of the tests can be printed in different diff --git a/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java index 85dbcf197..1d757bdcc 100644 --- a/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java +++ b/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -58,21 +58,42 @@ import org.apache.tools.ant.BuildException; /** * Destroys a process running for too long. - * + * For example: + *
+ * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000); + * Execute exec = new Execute(myloghandler, watchdog); + * exec.setCommandLine(mycmdline); + * int exitvalue = exec.execute(); + * if (exitvalue != SUCCESS && watchdog.killedProcess()){ + * // it was killed on purpose by the watchdog + * } + *+ * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute */ public class ExecuteWatchdog implements Runnable { - + + /** the process to execute and watch for duration */ private Process process; + + /** timeout duration. Once the process running time exceeds this it should be killed */ private int timeout; - private boolean watch = true; + + /** say whether or not the watchog is currently monitoring a process */ + private boolean watch = false; + + /** exception that might be thrown during the process execution */ private Exception caught = null; + /** say whether or not the process was killed due to running overtime */ + private boolean killedProcess = false; /** - * Creates a new watchdog. + * Creates a new watchdog with a given timeout. * - * @param timeout the timeout for the process. + * @param timeout the timeout for the process in milliseconds. It must be greather than 0. */ public ExecuteWatchdog(int timeout) { if (timeout < 1) { @@ -81,11 +102,11 @@ public class ExecuteWatchdog implements Runnable { this.timeout = timeout; } - /** - * Watches the given process and terminates it, if it runs for to long. - * - * @param process the process to watch. + * Watches the given process and terminates it, if it runs for too long. + * All information from the previous run are reset. + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being monitored. */ public synchronized void start(Process process) { if (process == null) { @@ -94,21 +115,21 @@ public class ExecuteWatchdog implements Runnable { if (this.process != null) { throw new IllegalStateException("Already running."); } - watch = true; + this.caught = null; + this.killedProcess = false; + this.watch = true; this.process = process; final Thread thread = new Thread(this, "WATCHDOG"); thread.setDaemon(true); thread.start(); } - /** - * Stops the watcher. + * Stops the watcher. It will notify all threads possibly waiting on this object. */ public synchronized void stop() { watch = false; notifyAll(); - process = null; } @@ -126,20 +147,56 @@ public class ExecuteWatchdog implements Runnable { wait(until - now); } catch (InterruptedException e) {} } + // if we are here, either someone stopped the watchdog or we are on timeout + // if watch is true, it means its a timeout if (watch) { + killedProcess = true; process.destroy(); } - stop(); } catch(Exception e) { caught = e; + } finally { + cleanUp(); } } - + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() { + watch = false; + process = null; + } + + /** + * This method will rethrow the exception that was possibly caught during the + * run of the process. It will only remains valid once the process has been + * terminated either by 'error', timeout or manual intervention. Information + * will be discarded once a new process is ran. + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ public void checkException() throws BuildException { if (caught != null) { throw new BuildException("Exception in ExecuteWatchdog.run: " + caught.getMessage(), caught); } } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * @return true if the process is still running, otherwise false. + */ + public boolean isWatching(){ + return watch; + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * @return true if the process was killed otherwise false. + */ + public boolean killedProcess(){ + return killedProcess; + } } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 000000000..324982fb8 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,216 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *
true
if and only if this enumeration object
+ * contains at least one more element to provide;
+ * false
otherwise.
+ */
+ public boolean hasMoreElements() {
+ return (pos < array.length);
+ }
+
+ /**
+ * Returns the next element of this enumeration if this enumeration
+ * object has at least one more element to provide.
+ *
+ * @return the next element of this enumeration.
+ * @throws NoSuchElementException if no more elements exist.
+ */
+ public Object nextElement() throws NoSuchElementException {
+ if (hasMoreElements()) {
+ Object o = array[pos];
+ pos++;
+ return o;
+ }
+ throw new NoSuchElementException();
+ }
+}
+/**
+ * Convenient enumeration over an array of enumeration. For example:
+ * + * Enumeration e1 = v1.elements(); + * while (e1.hasMoreElements()){ + * // do something + * } + * Enumeration e2 = v2.elements(); + * while (e2.hasMoreElements()){ + * // do the same thing + * } + *+ * can be written as: + *
+ * Enumeration[] enums = { v1.elements(), v2.elements() }; + * Enumeration e = Enumerations.fromCompound(enums); + * while (e.hasMoreElements()){ + * // do something + * } + *+ * Note that the enumeration will skip null elements in the array. The following is + * thus possible: + *
+ * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array + * Enumeration e = Enumerations.fromCompound(enums); + * while (e.hasMoreElements()){ + * // do something + * } + *+ * @author Stephane Bailliez + */ + class CompoundEnumeration implements Enumeration { + + /** enumeration array */ + private Enumeration[] enumArray; + + /** index in the enums array */ + private int index = 0; + + public CompoundEnumeration(Enumeration[] enumarray) { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return
true
if and only if this enumeration object
+ * contains at least one more element to provide;
+ * false
otherwise.
+ */
+ public boolean hasMoreElements() {
+ while (index < enumArray.length) {
+ if (enumArray[index] != null && enumArray[index].hasMoreElements()) {
+ return true;
+ }
+ index++;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the next element of this enumeration if this enumeration
+ * object has at least one more element to provide.
+ *
+ * @return the next element of this enumeration.
+ * @throws NoSuchElementException if no more elements exist.
+ */
+ public Object nextElement() throws NoSuchElementException {
+ if ( hasMoreElements() ) {
+ return enumArray[index].nextElement();
+ }
+ throw new NoSuchElementException();
+ }
+}
+
+
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
index 191ec900e..7f679aec0 100644
--- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
@@ -58,16 +58,19 @@ import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
-import org.apache.tools.ant.taskdefs.*;
+import org.apache.tools.ant.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.LogStreamHandler;
+import org.apache.tools.ant.taskdefs.ExecuteWatchdog;
+import org.apache.tools.ant.taskdefs.LogOutputStream;
import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Reference;
+
import java.io.File;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;
@@ -82,8 +85,8 @@ import java.util.Vector;
* To spawn a new Java VM to prevent interferences between
* different testcases, you need to enable fork
.
*
- * @author Thomas Haas
- * @author Stefan Bodewig
+ * @author Thomas Haas
+ * @author Stefan Bodewig
* @author Stephane Bailliez
*/
public class JUnitTask extends Task {
@@ -97,6 +100,12 @@ public class JUnitTask extends Task {
private Integer timeout = null;
private boolean summary = false;
+ /**
+ * Tells this task to halt when there is an error in a test.
+ * this property is applied on all BatchTest (batchtest) and JUnitTest (test)
+ * however it can possibly be overridden by their own properties.
+ * @param value true if it should halt, otherwise false
+ */
public void setHaltonerror(boolean value) {
Enumeration enum = allTests();
while (enum.hasMoreElements()) {
@@ -105,6 +114,12 @@ public class JUnitTask extends Task {
}
}
+ /**
+ * Tells this task to halt when there is a failure in a test.
+ * this property is applied on all BatchTest (batchtest) and JUnitTest (test)
+ * however it can possibly be overridden by their own properties.
+ * @param value true if it should halt, otherwise false
+ */
public void setHaltonfailure(boolean value) {
Enumeration enum = allTests();
while (enum.hasMoreElements()) {
@@ -113,10 +128,49 @@ public class JUnitTask extends Task {
}
}
+ /**
+ * Tells whether a JVM should be forked for each testcase. It avoids interference
+ * between testcases and possibly avoids hanging the build.
+ * this property is applied on all BatchTest (batchtest) and JUnitTest (test)
+ * however it can possibly be overridden by their own properties.
+ * @param value true if a JVM should be forked, otherwise false
+ * @see #setTimeout(Integer)
+ * @see #haltOntimeout(boolean)
+ */
+ public void setFork(boolean value) {
+ Enumeration enum = allTests();
+ while (enum.hasMoreElements()) {
+ BaseTest test = (BaseTest) enum.nextElement();
+ test.setFork(value);
+ }
+ }
+
+ /**
+ * Tells whether the task should print a short summary of the task.
+ * @param value true to print a summary, false otherwise.
+ * @see SummaryJUnitResultFormatter
+ */
public void setPrintsummary(boolean value) {
summary = value;
}
+ /**
+ * Set the timeout value (in milliseconds). If the test is running for more than this
+ * value, the test will be canceled. (works only when in 'fork' mode).
+ * @param value the maximum time (in milliseconds) allowed before declaring the test
+ * as 'timed-out'
+ * @see #setFork(boolean)
+ * @see #haltOnTimeout(boolean)
+ */
+ public void setTimeout(Integer value) {
+ timeout = value;
+ }
+
+ /**
+ * Set the maximum memory to be used by all forked JVMs.
+ * @param max the value as defined by -mx or -Xmx
+ * in the java command line options.
+ */
public void setMaxmemory(String max) {
if (Project.getJavaVersion().startsWith("1.1")) {
createJvmarg().setValue("-mx"+max);
@@ -125,51 +179,73 @@ public class JUnitTask extends Task {
}
}
- public void setTimeout(Integer value) {
- timeout = value;
- }
-
- public void setFork(boolean value) {
- Enumeration enum = allTests();
- while (enum.hasMoreElements()) {
- BaseTest test = (BaseTest) enum.nextElement();
- test.setFork(value);
- }
- }
-
+ /**
+ * Set a new VM to execute the testcase. Default is java. Ignored if no JVM is forked.
+ * @param value the new VM to use instead of java
+ * @see #setFork(boolean)
+ */
public void setJvm(String value) {
commandline.setVm(value);
}
+ /**
+ * Create a new JVM argument. Ignored if no JVM is forked.
+ * @return create a new JVM argument so that any argument can be passed to the JVM.
+ * @see #setFork(boolean)
+ */
public Commandline.Argument createJvmarg() {
return commandline.createVmArgument();
}
+ /**
+ * The directory to invoke the VM in. Ignored if no JVM is forked.
+ * @param dir the directory to invoke the JVM from.
+ * @see #setFork(boolean)
+ */
+ public void setDir(File dir) {
+ this.dir = dir;
+ }
+
+ /**
+ * Add a nested sysproperty element. This might be useful to tranfer
+ * Ant properties to the testcases when JVM forking is not enabled.
+ */
+ public void addSysproperty(Environment.Variable sysp) {
+ commandline.addSysproperty(sysp);
+ }
+
+ /**
+ * create a classpath to use for forked jvm
+ */
public Path createClasspath() {
return commandline.createClasspath(project).createPath();
}
+ /**
+ * Add a new single testcase.
+ * @param test a new single testcase
+ * @see JUnitTest
+ */
public void addTest(JUnitTest test) {
tests.addElement(test);
}
+ /**
+ * Create a new set of testcases (also called ..batchtest) and add it to the list.
+ * @return a new instance of a batch test.
+ * @see BatchTest
+ */
public BatchTest createBatchTest() {
BatchTest test = new BatchTest(project);
batchTests.addElement(test);
return test;
}
- public void addFormatter(FormatterElement fe) {
- formatters.addElement(fe);
- }
-
/**
- * The directory to invoke the VM in.
- *
- *
Ignored if fork=false.
+ * Add a new formatter to all tests of this task.
*/
- public void setDir(File dir) {
- this.dir = dir;
+ public void addFormatter(FormatterElement fe) {
+ formatters.addElement(fe);
}
/**
@@ -183,201 +259,222 @@ public class JUnitTask extends Task {
* Runs the testcase.
*/
public void execute() throws BuildException {
- boolean errorOccurred = false;
- boolean failureOccurred = false;
-
- Vector runTests = (Vector) tests.clone();
-
- Enumeration list = batchTests.elements();
- while (list.hasMoreElements()) {
- BatchTest test = (BatchTest)list.nextElement();
- Enumeration list2 = test.elements();
- while (list2.hasMoreElements()) {
- runTests.addElement(list2.nextElement());
+ Enumeration list = getIndividualTests();
+ try {
+ while (list.hasMoreElements()) {
+ JUnitTest test = (JUnitTest)list.nextElement();
+ if ( test.shouldRun(project)) {
+ execute(test);
+ }
}
+ } finally {
+ //@todo here we should run test aggregation (SBa)
}
+ }
- list = runTests.elements();
- while (list.hasMoreElements()) {
- JUnitTest test = (JUnitTest)list.nextElement();
+ protected void execute(JUnitTest test) throws BuildException {
+ // set the default values if not specified
+ //@todo should be moved to the test class (?) (SBa)
+ if (test.getTodir() == null) {
+ test.setTodir(project.resolveFile("."));
+ }
- if (!test.shouldRun(project)) {
- continue;
- }
+ if (test.getOutfile() == null) {
+ test.setOutfile( "TEST-" + test.getName() );
+ }
- if (test.getTodir() == null){
- test.setTodir(project.resolveFile("."));
+ // execute the test and get the return code
+ int exitValue = JUnitTestRunner.ERRORS;
+ boolean wasKilled = false;
+ if (!test.getFork()) {
+ exitValue = executeInVM(test);
+ } else {
+ ExecuteWatchdog watchdog = createWatchdog();
+ exitValue = executeAsForked(test, watchdog);
+ // null watchdog means no timeout, you'd better not check with null
+ if (watchdog != null) {
+ //info will be used in later version do nothing for now
+ //wasKilled = watchdog.killedProcess();
}
+ }
- if (test.getOutfile() == null) {
- test.setOutfile( "TEST-" + test.getName() );
- }
+ // if there is an error/failure and that it should halt, stop everything otherwise
+ // just log a statement
+ boolean errorOccurredHere = exitValue == JUnitTestRunner.ERRORS;
+ boolean failureOccurredHere = exitValue != JUnitTestRunner.SUCCESS;
+ if (errorOccurredHere && test.getHaltonerror()
+ || failureOccurredHere && test.getHaltonfailure()) {
+ throw new BuildException("Test "+test.getName()+" failed",
+ location);
+ } else if (errorOccurredHere || failureOccurredHere) {
+ log("TEST "+test.getName()+" FAILED", Project.MSG_ERR);
+ }
+ }
- int exitValue = JUnitTestRunner.ERRORS;
-
- if (!test.getFork()) {
+ /**
+ * Execute a testcase by forking a new JVM. The command will block until
+ * it finishes. To know if the process was destroyed or not, use the
+ * killedProcess() method of the watchdog class.
+ * @param test the testcase to execute.
+ * @param watchdog the watchdog in charge of cancelling the test if it
+ * exceeds a certain amount of time. Can be null, in this case
+ * the test could probably hang forever.
+ */
+ private int executeAsForked(JUnitTest test, ExecuteWatchdog watchdog) throws BuildException {
+ CommandlineJava cmd = (CommandlineJava) commandline.clone();
+
+ cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner");
+ cmd.createArgument().setValue(test.getName());
+ cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror());
+ cmd.createArgument().setValue("haltOnFailure=" + test.getHaltonfailure());
+ if (summary) {
+ log("Running " + test.getName(), Project.MSG_INFO);
+ cmd.createArgument().setValue("formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter");
+ }
- if (dir != null) {
- log("dir attribute ignored if running in the same VM",
- Project.MSG_WARN);
- }
+ StringBuffer formatterArg = new StringBuffer(128);
+ final FormatterElement[] feArray = mergeFormatters(test);
+ for (int i = 0; i < feArray.length; i++) {
+ FormatterElement fe = feArray[i];
+ formatterArg.append("formatter=");
+ formatterArg.append(fe.getClassname());
+ File outFile = getOutput(fe,test);
+ if (outFile != null) {
+ formatterArg.append(",");
+ formatterArg.append( outFile );
+ }
+ cmd.createArgument().setValue(formatterArg.toString());
+ formatterArg.setLength(0);
+ }
- JUnitTestRunner runner = null;
-
- Path classpath = commandline.getClasspath();
- if (classpath != null) {
- log("Using CLASSPATH " + classpath, Project.MSG_VERBOSE);
- AntClassLoader l = new AntClassLoader(project, classpath,
- false);
- // make sure the test will be accepted as a TestCase
- l.addSystemPackageRoot("junit");
- // will cause trouble in JDK 1.1 if omitted
- l.addSystemPackageRoot("org.apache.tools.ant");
- runner = new JUnitTestRunner(test, test.getHaltonerror(),
- test.getHaltonfailure(), l);
- } else {
- runner = new JUnitTestRunner(test, test.getHaltonerror(),
- test.getHaltonfailure());
- }
+ Execute execute = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN), watchdog);
+ execute.setCommandline(cmd.getCommandline());
+ if (dir != null) {
+ execute.setWorkingDirectory(dir);
+ execute.setAntRun(project);
+ }
- if (summary) {
- log("Running " + test.getName(), Project.MSG_INFO);
-
- SummaryJUnitResultFormatter f =
- new SummaryJUnitResultFormatter();
- f.setOutput(new LogOutputStream(this, Project.MSG_INFO));
- runner.addFormatter(f);
- }
+ log("Executing: "+cmd.toString(), Project.MSG_VERBOSE);
+ try {
+ return execute.execute();
+ } catch (IOException e) {
+ throw new BuildException("Process fork failed.", e, location);
+ }
+ }
- for (int i=0; i