* 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); | |||
| } | |||
| } | |||
| } | |||