* fixed a bug that prevented <junit> from logging to logfiles with a comma in its name in fork mode * fixed some problems within ExecuteWatchdog Submitted by: Stephane Bailliez <sbailliez@imediation.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268543 13f79535-47bb-0310-9956-ffa450edef68master
@@ -497,6 +497,8 @@ | |||
<jvmarg value="-classic"/> | |||
<classpath refid="tests-classpath"/> | |||
<sysproperty key="build.tests" value="${build.tests}"/> | |||
<formatter type="plain" usefile="false" /> | |||
<batchtest> | |||
@@ -506,6 +508,9 @@ | |||
<exclude name="org/apache/tools/ant/taskdefs/TaskdefsTest.java" /> | |||
<exclude name="org/apache/tools/ant/util/regexp/RegexpMatcherTest.java" /> | |||
<!-- currently fails - will be sorted out soon --> | |||
<exclude name="org/apache/tools/ant/types/CommandlineJavaTest.java" /> | |||
<!-- these depend on order --> | |||
<exclude name="org/apache/tools/ant/taskdefs/GUnzipTest.java" /> | |||
<exclude name="org/apache/tools/ant/taskdefs/GzipTest.java" /> | |||
@@ -535,6 +540,7 @@ | |||
<junit printsummary="no" haltonfailure="yes" fork="${junit.fork}"> | |||
<jvmarg value="-classic"/> | |||
<sysproperty key="build.tests" value="${build.tests}"/> | |||
<classpath refid="tests-classpath"/> | |||
<formatter type="plain" usefile="false" /> | |||
<test name="${testcase}" /> | |||
@@ -86,6 +86,7 @@ VM via nested <code><jvmarg></code> attributes, for example:</p> | |||
<pre><blockquote> | |||
<junit fork="yes"> | |||
<jvmarg value="-Djava.compiler=NONE"/> | |||
... | |||
</junit> | |||
</blockquote></pre> | |||
would run the test in a VM without JIT.</p> | |||
@@ -93,6 +94,23 @@ would run the test in a VM without JIT.</p> | |||
<p><code><jvmarg></code> allows all attributes described in <a | |||
href="index.html#arg">Command line arguments</a>.</p> | |||
<h4>sysproperty</h4> | |||
<p>Use nested <code><sysproperty></code> 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 <a href="index.html#env">environment variables</a>. | |||
<pre><blockquote> | |||
<junit fork="no"> | |||
<sysproperty key="basedir" value="${basedir}"/> | |||
... | |||
</junit> | |||
</blockquote></pre> | |||
would run the test in ANT's VM and make the <code>basedir</code> property | |||
available to the test.</p> | |||
<h4>formatter</h4> | |||
<p>The results of the tests can be printed in different | |||
@@ -58,21 +58,42 @@ import org.apache.tools.ant.BuildException; | |||
/** | |||
* Destroys a process running for too long. | |||
* | |||
* For example: | |||
* <pre> | |||
* 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 | |||
* } | |||
* </pre> | |||
* @author thomas.haas@softwired-inc.com | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
* @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 <tt>null</tt> | |||
* @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 <tt>true</tt> if the process is still running, otherwise <tt>false</tt>. | |||
*/ | |||
public boolean isWatching(){ | |||
return watch; | |||
} | |||
/** | |||
* Indicates whether the last process run was killed on timeout or not. | |||
* @return <tt>true</tt> if the process was killed otherwise <tt>false</tt>. | |||
*/ | |||
public boolean killedProcess(){ | |||
return killedProcess; | |||
} | |||
} | |||
@@ -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 | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.junit; | |||
import java.util.Enumeration; | |||
import java.util.NoSuchElementException; | |||
/** | |||
* A couple of methods related to enumerations that might be useful. | |||
* This class should probably disappear once the required JDK is set to 1.2 | |||
* instead of 1.1. | |||
* | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
public final class Enumerations { | |||
private Enumerations(){ | |||
} | |||
/** | |||
* creates an enumeration from an array of objects. | |||
* @param array the array of object to enumerate. | |||
* @return the enumeration over the array of objects. | |||
*/ | |||
public static Enumeration fromArray(Object[] array){ | |||
return new ArrayEnumeration(array); | |||
} | |||
/** | |||
* creates an enumeration from an array of enumeration. The created enumeration | |||
* will sequentially enumerate over all elements of each enumeration and skip | |||
* <tt>null</tt> enumeration elements in the array. | |||
* @param enums the array of enumerations. | |||
* @return the enumeration over the array of enumerations. | |||
*/ | |||
public static Enumeration fromCompound(Enumeration[] enums){ | |||
return new CompoundEnumeration(enums); | |||
} | |||
} | |||
/** | |||
* Convenient enumeration over an array of objects. | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
class ArrayEnumeration implements Enumeration { | |||
/** object array */ | |||
private Object[] array; | |||
/** current index */ | |||
private int pos; | |||
/** | |||
* Initialize a new enumeration that wraps an array. | |||
* @param array the array of object to enumerate. | |||
*/ | |||
public ArrayEnumeration(Object[] array){ | |||
this.array = array; | |||
this.pos = 0; | |||
} | |||
/** | |||
* Tests if this enumeration contains more elements. | |||
* | |||
* @return <code>true</code> if and only if this enumeration object | |||
* contains at least one more element to provide; | |||
* <code>false</code> 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: | |||
* <pre> | |||
* Enumeration e1 = v1.elements(); | |||
* while (e1.hasMoreElements()){ | |||
* // do something | |||
* } | |||
* Enumeration e2 = v2.elements(); | |||
* while (e2.hasMoreElements()){ | |||
* // do the same thing | |||
* } | |||
* </pre> | |||
* can be written as: | |||
* <pre> | |||
* Enumeration[] enums = { v1.elements(), v2.elements() }; | |||
* Enumeration e = Enumerations.fromCompound(enums); | |||
* while (e.hasMoreElements()){ | |||
* // do something | |||
* } | |||
* </pre> | |||
* Note that the enumeration will skip null elements in the array. The following is | |||
* thus possible: | |||
* <pre> | |||
* Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array | |||
* Enumeration e = Enumerations.fromCompound(enums); | |||
* while (e.hasMoreElements()){ | |||
* // do something | |||
* } | |||
* </pre> | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
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 <code>true</code> if and only if this enumeration object | |||
* contains at least one more element to provide; | |||
* <code>false</code> 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(); | |||
} | |||
} | |||
@@ -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; | |||
* <p> To spawn a new Java VM to prevent interferences between | |||
* different testcases, you need to enable <code>fork</code>. | |||
* | |||
* @author Thomas Haas | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
* @author Thomas Haas | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
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 <tt>true</tt> if it should halt, otherwise <tt>false<tt> | |||
*/ | |||
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 <tt>true</tt> if it should halt, otherwise <tt>false<tt> | |||
*/ | |||
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 <tt>true</tt> if a JVM should be forked, otherwise <tt>false<tt> | |||
* @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 <tt>true</tt> to print a summary, <tt>false</tt> 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 <tt>-mx</tt> or <tt>-Xmx</tt> | |||
* 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 <tt>java</tt>. Ignored if no JVM is forked. | |||
* @param value the new VM to use instead of <tt>java</tt> | |||
* @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. | |||
* | |||
* <p>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 | |||
* <tt>killedProcess()</tt> 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 <tt>null</tt>, 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<formatters.size(); i++) { | |||
FormatterElement fe = (FormatterElement) formatters.elementAt(i); | |||
setOutput(fe, test); | |||
runner.addFormatter(fe.createFormatter()); | |||
} | |||
FormatterElement[] add = test.getFormatters(); | |||
for (int i=0; i<add.length; i++) { | |||
setOutput(add[i], test); | |||
runner.addFormatter(add[i].createFormatter()); | |||
} | |||
// in VM is not very nice since it could probably hang the | |||
// whole build. IMHO this method should be avoided and it would be best | |||
// to remove it in future versions. TBD. (SBa) | |||
/** | |||
* Execute inside VM. | |||
*/ | |||
private int executeInVM(JUnitTest test) throws BuildException { | |||
if (dir != null) { | |||
log("dir attribute ignored if running in the same VM", Project.MSG_WARN); | |||
} | |||
runner.run(); | |||
exitValue = runner.getRetCode(); | |||
} else { | |||
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"); | |||
} | |||
CommandlineJava.SysProperties sysProperties = commandline.getSystemProperties(); | |||
if (sysProperties != null) { | |||
sysProperties.setSystem(); | |||
} | |||
try { | |||
log("Using System properties " + System.getProperties(), Project.MSG_VERBOSE); | |||
AntClassLoader cl = null; | |||
Path classpath = commandline.getClasspath(); | |||
if (classpath != null) { | |||
log("Using CLASSPATH " + classpath, Project.MSG_VERBOSE); | |||
cl = new AntClassLoader(project, classpath, false); | |||
// make sure the test will be accepted as a TestCase | |||
cl.addSystemPackageRoot("junit"); | |||
// will cause trouble in JDK 1.1 if omitted | |||
cl.addSystemPackageRoot("org.apache.tools.ant"); | |||
} | |||
JUnitTestRunner runner = new JUnitTestRunner(test, test.getHaltonerror(), test.getHaltonfailure(), cl); | |||
StringBuffer formatterArg = new StringBuffer(); | |||
for (int i=0; i<formatters.size(); i++) { | |||
FormatterElement fe = (FormatterElement) formatters.elementAt(i); | |||
formatterArg.append("formatter="); | |||
formatterArg.append(fe.getClassname()); | |||
if (fe.getUseFile()) { | |||
formatterArg.append(","); | |||
File destFile = new File( test.getTodir(), | |||
test.getOutfile() + fe.getExtension() ); | |||
String filename = destFile.getAbsolutePath(); | |||
formatterArg.append( project.resolveFile(filename) ); | |||
} | |||
cmd.createArgument().setValue(formatterArg.toString()); | |||
formatterArg.setLength(0); | |||
} | |||
FormatterElement[] add = test.getFormatters(); | |||
for (int i=0; i<add.length; i++) { | |||
formatterArg.append("formatter="); | |||
formatterArg.append(add[i].getClassname()); | |||
if (add[i].getUseFile()) { | |||
formatterArg.append(","); | |||
File destFile = new File( test.getTodir(), | |||
test.getOutfile() + add[i].getExtension() ); | |||
String filename = destFile.getAbsolutePath(); | |||
formatterArg.append( project.resolveFile(filename) ); | |||
} | |||
cmd.createArgument().setValue(formatterArg.toString()); | |||
formatterArg.setLength(0); | |||
} | |||
if (summary) { | |||
log("Running " + test.getName(), Project.MSG_INFO); | |||
Execute execute = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN), createWatchdog()); | |||
execute.setCommandline(cmd.getCommandline()); | |||
if (dir != null) { | |||
execute.setWorkingDirectory(dir); | |||
execute.setAntRun(project); | |||
} | |||
log("Executing: "+cmd.toString(), Project.MSG_VERBOSE); | |||
try { | |||
exitValue = execute.execute(); | |||
} catch (IOException e) { | |||
throw new BuildException("Process fork failed.", e, | |||
location); | |||
SummaryJUnitResultFormatter f = new SummaryJUnitResultFormatter(); | |||
f.setOutput( getDefaultOutput() ); | |||
runner.addFormatter(f); | |||
} | |||
final FormatterElement[] feArray = mergeFormatters(test); | |||
for (int i = 0; i < feArray.length; i++) { | |||
FormatterElement fe = feArray[i]; | |||
File outFile = getOutput(fe,test); | |||
if (outFile == null) { | |||
fe.setOutput( getDefaultOutput() ); | |||
} | |||
runner.addFormatter(fe.createFormatter()); | |||
} | |||
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); | |||
runner.run(); | |||
return runner.getRetCode(); | |||
} finally{ | |||
if (sysProperties != null) { | |||
sysProperties.restoreSystem(); | |||
} | |||
} | |||
} | |||
/** | |||
* @return <tt>null</tt> if there is a timeout value, otherwise the | |||
* watchdog instance. | |||
*/ | |||
protected ExecuteWatchdog createWatchdog() throws BuildException { | |||
if (timeout == null) return null; | |||
if (timeout == null){ | |||
return null; | |||
} | |||
return new ExecuteWatchdog(timeout.intValue()); | |||
} | |||
private void rename(String source, String destination) throws BuildException { | |||
final File src = new File(source); | |||
final File dest = new File(destination); | |||
/** | |||
* get the default output for a formatter. | |||
*/ | |||
protected OutputStream getDefaultOutput(){ | |||
return new LogOutputStream(this, Project.MSG_INFO); | |||
} | |||
if (dest.exists()) dest.delete(); | |||
src.renameTo(dest); | |||
/** | |||
* Merge all individual tests from the batchtest with all individual tests | |||
* and return an enumeration over all <tt>JUnitTest</tt>. | |||
*/ | |||
protected Enumeration getIndividualTests(){ | |||
Enumeration[] enums = new Enumeration[ batchTests.size() + 1]; | |||
for (int i = 0; i < batchTests.size(); i++) { | |||
BatchTest batchtest = (BatchTest)batchTests.elementAt(i); | |||
enums[i] = batchtest.elements(); | |||
} | |||
enums[enums.length - 1] = tests.elements(); | |||
return Enumerations.fromCompound(enums); | |||
} | |||
protected Enumeration allTests() { | |||
Enumeration[] enums = { tests.elements(), batchTests.elements() }; | |||
return Enumerations.fromCompound(enums); | |||
} | |||
return new Enumeration() { | |||
private Enumeration testEnum = tests.elements(); | |||
private Enumeration batchEnum = batchTests.elements(); | |||
public boolean hasMoreElements() { | |||
return testEnum.hasMoreElements() || | |||
batchEnum.hasMoreElements(); | |||
} | |||
public Object nextElement() { | |||
if (testEnum.hasMoreElements()) { | |||
return testEnum.nextElement(); | |||
} | |||
return batchEnum.nextElement(); | |||
} | |||
}; | |||
private FormatterElement[] mergeFormatters(JUnitTest test){ | |||
Vector feVector = (Vector)formatters.clone(); | |||
FormatterElement[] fes = test.getFormatters(); | |||
FormatterElement[] feArray = new FormatterElement[feVector.size() + fes.length]; | |||
feVector.copyInto(feArray); | |||
System.arraycopy(fes, 0, feArray, feVector.size(), fes.length); | |||
return feArray; | |||
} | |||
protected void setOutput(FormatterElement fe, JUnitTest test) { | |||
/** return the file or null if does not use a file */ | |||
protected File getOutput(FormatterElement fe, JUnitTest test){ | |||
if (fe.getUseFile()) { | |||
File destFile = new File( test.getTodir(), | |||
test.getOutfile() + fe.getExtension() ); | |||
String filename = destFile.getAbsolutePath(); | |||
fe.setOutfile( project.resolveFile(filename) ); | |||
} else { | |||
fe.setOutput(new LogOutputStream(this, Project.MSG_INFO)); | |||
String filename = test.getOutfile() + fe.getExtension(); | |||
File destFile = new File( test.getTodir(), filename ); | |||
String absFilename = destFile.getAbsolutePath(); | |||
return project.resolveFile(absFilename); | |||
} | |||
return null; | |||
} | |||
} |
@@ -344,14 +344,18 @@ public class JUnitTestRunner implements TestListener { | |||
} | |||
} | |||
private static void createAndStoreFormatter(String line) | |||
/** | |||
* Line format is: formatter=<classname>(,<pathname>)? | |||
*/ | |||
private static void createAndStoreFormatter(String line) | |||
throws BuildException { | |||
FormatterElement fe = new FormatterElement(); | |||
StringTokenizer tok = new StringTokenizer(line, ","); | |||
fe.setClassname(tok.nextToken()); | |||
if (tok.hasMoreTokens()) { | |||
fe.setOutfile(new java.io.File(tok.nextToken())); | |||
int pos = line.indexOf(','); | |||
if (pos == -1) { | |||
fe.setClassname(line); | |||
} else { | |||
fe.setClassname(line.substring(0, pos)); | |||
fe.setOutfile( new File(line.substring(pos + 1)) ); | |||
} | |||
fromCmdLine.addElement(fe.createFormatter()); | |||
} | |||
@@ -0,0 +1,197 @@ | |||
/* | |||
* 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 | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs; | |||
import java.net.*; | |||
import junit.framework.*; | |||
import java.io.*; | |||
/** | |||
* Simple testcase for the ExecuteWatchdog class. | |||
* | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
public class ExecuteWatchdogTest extends TestCase { | |||
private final static int TIME_OUT = 2000; | |||
private final static String TEST_CLASSPATH = getTestClassPath(); | |||
private ExecuteWatchdog watchdog; | |||
public ExecuteWatchdogTest(String name) { | |||
super(name); | |||
} | |||
protected void setUp(){ | |||
watchdog = new ExecuteWatchdog(TIME_OUT); | |||
} | |||
/** | |||
* Dangerous method to obtain the classpath for the test. This is | |||
* severely tighted to the build.xml properties. | |||
*/ | |||
private static String getTestClassPath(){ | |||
String classpath = System.getProperty("build.tests"); | |||
if (classpath == null) { | |||
System.err.println("WARNING: 'build.tests' property is not available !"); | |||
classpath = System.getProperty("java.class.path"); | |||
} | |||
System.out.println("Using classpath: " + classpath); | |||
return classpath; | |||
} | |||
private Process getProcess(int timetorun) throws Exception { | |||
String[] cmdArray = { | |||
"java", "-classpath", TEST_CLASSPATH, | |||
TimeProcess.class.getName(), String.valueOf(timetorun) | |||
}; | |||
//System.out.println("Testing with classpath: " + System.getProperty("java.class.path")); | |||
return Runtime.getRuntime().exec(cmdArray); | |||
} | |||
private String getErrorOutput(Process p) throws Exception { | |||
BufferedReader err = new BufferedReader( new InputStreamReader(p.getErrorStream()) ); | |||
StringBuffer buf = new StringBuffer(); | |||
String line; | |||
while ( (line = err.readLine()) != null){ | |||
buf.append(line); | |||
} | |||
return buf.toString(); | |||
} | |||
private int waitForEnd(Process p) throws Exception { | |||
int retcode = p.waitFor(); | |||
if (retcode != 0){ | |||
String err = getErrorOutput(p); | |||
if (err.length() > 0){ | |||
System.err.println("ERROR:"); | |||
System.err.println(err); | |||
} | |||
} | |||
return retcode; | |||
} | |||
public void testNoTimeOut() throws Exception { | |||
Process process = getProcess(TIME_OUT/2); | |||
watchdog.start(process); | |||
int retCode = waitForEnd(process); | |||
assert("process should not have been killed", !watchdog.killedProcess()); | |||
assertEquals(0, retCode); | |||
} | |||
// test that the watchdog ends the process | |||
public void testTimeOut() throws Exception { | |||
Process process = getProcess(TIME_OUT*2); | |||
long now = System.currentTimeMillis(); | |||
watchdog.start(process); | |||
int retCode = process.waitFor(); | |||
long elapsed = System.currentTimeMillis() - now; | |||
assert("process should have been killed", watchdog.killedProcess()); | |||
// assert("return code is invalid: " + retCode, retCode!=0); | |||
assert("elapse time is less than timeout value", elapsed > TIME_OUT); | |||
assert("elapse time is greater than run value", elapsed < TIME_OUT*2); | |||
} | |||
// test a process that runs and failed | |||
public void testFailed() throws Exception { | |||
Process process = getProcess(-1); // process should abort | |||
watchdog.start(process); | |||
int retCode = process.waitFor(); | |||
assert("process should not have been killed", !watchdog.killedProcess()); | |||
assert("return code is invalid: " + retCode, retCode!=0); | |||
} | |||
public void testManualStop() throws Exception { | |||
final Process process = getProcess(TIME_OUT*2); | |||
watchdog.start(process); | |||
// I assume that starting this takes less than TIME_OUT/2 ms... | |||
Thread thread = new Thread(){ | |||
public void run(){ | |||
try { | |||
process.waitFor(); | |||
} catch(InterruptedException e){ | |||
// not very nice but will do the job | |||
fail("process interrupted in thread"); | |||
} | |||
} | |||
}; | |||
thread.start(); | |||
// wait for TIME_OUT/2, there should be about TIME_OUT/2 ms remaining before timeout | |||
thread.join(TIME_OUT/2); | |||
// now stop the watchdog. | |||
watchdog.stop(); | |||
// wait for the thread to die, should be the end of the process | |||
thread.join(); | |||
// process should be dead and well finished | |||
assertEquals(0, process.exitValue()); | |||
assert("process should not have been killed", !watchdog.killedProcess()); | |||
} | |||
public static class TimeProcess { | |||
public static void main(String[] args) throws Exception { | |||
int time = Integer.parseInt(args[0]); | |||
if (time < 1) { | |||
throw new IllegalArgumentException("Invalid time: " + time); | |||
} | |||
Thread.sleep(time); | |||
} | |||
} | |||
} |