From da10e54de9442a412e0bd1ccf9cf235f49e6c2b7 Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Tue, 30 Jan 2001 17:01:39 +0000 Subject: [PATCH] * Added to * fixed a bug that prevented from logging to logfiles with a comma in its name in fork mode * fixed some problems within ExecuteWatchdog Submitted by: Stephane Bailliez git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268543 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 6 + docs/junit.html | 18 + .../tools/ant/taskdefs/ExecuteWatchdog.java | 87 +++- .../taskdefs/optional/junit/Enumerations.java | 216 ++++++++ .../taskdefs/optional/junit/JUnitTask.java | 473 +++++++++++------- .../optional/junit/JUnitTestRunner.java | 16 +- .../ant/taskdefs/ExecuteWatchdogTest.java | 197 ++++++++ 7 files changed, 804 insertions(+), 209 deletions(-) create mode 100644 src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java create mode 100644 src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java diff --git a/build.xml b/build.xml index 928607286..c966d6f0c 100644 --- a/build.xml +++ b/build.xml @@ -497,6 +497,8 @@ + + @@ -506,6 +508,9 @@ + + + @@ -535,6 +540,7 @@ + diff --git a/docs/junit.html b/docs/junit.html index d1512a5f3..17c8a705d 100644 --- a/docs/junit.html +++ b/docs/junit.html @@ -86,6 +86,7 @@ VM via nested <jvmarg> attributes, for example:

<junit fork="yes"> <jvmarg value="-Djava.compiler=NONE"/> + ... </junit>
would run the test in a VM without JIT.

@@ -93,6 +94,23 @@ would run the test in a VM without JIT.

<jvmarg> allows all attributes described in Command line arguments.

+

sysproperty

+ +

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. + +

+<junit fork="no"> + <sysproperty key="basedir" value="${basedir}"/> + ... +</junit> +
+would run the test in ANT's VM and make the basedir property +available to the test.

+ +

formatter

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 + * . + */ +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 Stephane Bailliez + */ +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 + * null 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 Stephane Bailliez + */ +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 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; inull 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 JUnitTest. + */ + 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; } + } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java index 603afc5ba..102322a2b 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -344,14 +344,18 @@ public class JUnitTestRunner implements TestListener { } } - private static void createAndStoreFormatter(String line) + /** + * Line format is: formatter=(,)? + */ + 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()); } diff --git a/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java new file mode 100644 index 000000000..537e4a9f8 --- /dev/null +++ b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java @@ -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 + * . + */ + +package org.apache.tools.ant.taskdefs; + +import java.net.*; +import junit.framework.*; +import java.io.*; + +/** + * Simple testcase for the ExecuteWatchdog class. + * + * @author Stephane Bailliez + */ +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); + } + } +}