| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000-2003 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 "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.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Properties; |
| import java.util.Vector; |
| 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.Execute; |
| import org.apache.tools.ant.taskdefs.ExecuteWatchdog; |
| import org.apache.tools.ant.taskdefs.LogOutputStream; |
| import org.apache.tools.ant.taskdefs.LogStreamHandler; |
| import org.apache.tools.ant.types.Assertions; |
| import org.apache.tools.ant.types.Commandline; |
| import org.apache.tools.ant.types.CommandlineJava; |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.Environment; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.Permissions; |
| import org.apache.tools.ant.types.PropertySet; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.LoaderUtils; |
| import junit.framework.AssertionFailedError; |
| import junit.framework.Test; |
| import junit.framework.TestResult; |
| |
| /** |
| * Runs JUnit tests. |
| * |
| * <p> JUnit is a framework to create unit test. It has been initially |
| * created by Erich Gamma and Kent Beck. JUnit can be found at <a |
| * href="http://www.junit.org">http://www.junit.org</a>. |
| * |
| * <p> <code>JUnitTask</code> can run a single specific |
| * <code>JUnitTest</code> using the <code>test</code> element.</p> |
| * For example, the following target <code><pre> |
| * <target name="test-int-chars" depends="jar-test"> |
| * <echo message="testing international characters"/> |
| * <junit printsummary="no" haltonfailure="yes" fork="false"> |
| * <classpath refid="classpath"/> |
| * <formatter type="plain" usefile="false" /> |
| * <test name="org.apache.ecs.InternationalCharTest" /> |
| * </junit> |
| * </target> |
| * </pre></code> |
| * <p>runs a single junit test |
| * (<code>org.apache.ecs.InternationalCharTest</code>) in the current |
| * VM using the path with id <code>classpath</code> as classpath and |
| * presents the results formatted using the standard |
| * <code>plain</code> formatter on the command line.</p> |
| * |
| * <p> This task can also run batches of tests. The |
| * <code>batchtest</code> element creates a <code>BatchTest</code> |
| * based on a fileset. This allows, for example, all classes found in |
| * directory to be run as testcases.</p> |
| * |
| * <p>For example,</p><code><pre> |
| * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present"> |
| * <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}"> |
| * <jvmarg value="-classic"/> |
| * <classpath refid="tests-classpath"/> |
| * <sysproperty key="build.tests" value="${build.tests}"/> |
| * <formatter type="brief" usefile="false" /> |
| * <batchtest> |
| * <fileset dir="${tests.dir}"> |
| * <include name="**/*Test*" /> |
| * </fileset> |
| * </batchtest> |
| * </junit> |
| * </target> |
| * </pre></code> |
| * <p>this target finds any classes with a <code>test</code> directory |
| * anywhere in their path (under the top <code>${tests.dir}</code>, of |
| * course) and creates <code>JUnitTest</code>'s for each one.</p> |
| * |
| * <p> Of course, <code><junit></code> and |
| * <code><batch></code> elements can be combined for more |
| * complex tests. For an example, see the ant <code>build.xml</code> |
| * target <code>run-tests</code> (the second example is an edited |
| * version).</p> |
| * |
| * <p> To spawn a new Java VM to prevent interferences between |
| * different testcases, you need to enable <code>fork</code>. A |
| * number of attributes and elements allow you to set up how this JVM |
| * runs. |
| * @author Thomas Haas |
| * @author Stefan Bodewig |
| * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> |
| * @author <a href="mailto:Gerrit.Riessen@web.de">Gerrit Riessen</a> |
| * @author <a href="mailto:ehatcher@apache.org">Erik Hatcher</a> |
| * @author <a href="mailto:martijn@kruithof.xs4all.nl">Martijn Kruithof></a> |
| * @author <a href="http://nerdmonkey.com">Eli Tucker</a> |
| * |
| * @version $Revision$ |
| * |
| * @since Ant 1.2 |
| * |
| * @see JUnitTest |
| * @see BatchTest |
| */ |
| public class JUnitTask extends Task { |
| |
| private CommandlineJava commandline = new CommandlineJava(); |
| private Vector tests = new Vector(); |
| private Vector batchTests = new Vector(); |
| private Vector formatters = new Vector(); |
| private File dir = null; |
| |
| private Integer timeout = null; |
| private boolean summary = false; |
| private boolean reloading = true; |
| private String summaryValue = ""; |
| private JUnitTestRunner runner = null; |
| |
| private boolean newEnvironment = false; |
| private Environment env = new Environment(); |
| |
| private boolean includeAntRuntime = true; |
| private Path antRuntimeClasses = null; |
| |
| private boolean showOutput = false; |
| private File tmpDir; |
| private AntClassLoader classLoader = null; |
| private Permissions perm = null; |
| |
| private static final int STRING_BUFFER_SIZE = 128; |
| /** |
| * If true, force ant to re-classload all classes for each JUnit TestCase |
| * |
| * @param value force class reloading for each test case |
| */ |
| public void setReloading(boolean value) { |
| reloading = value; |
| } |
| |
| /** |
| * If true, smartly filter the stack frames of |
| * JUnit errors and failures before reporting them. |
| * |
| * <p>This property is applied on all BatchTest (batchtest) and |
| * JUnitTest (test) however it can possibly be overridden by their |
| * own properties.</p> |
| * @param value <tt>false</tt> if it should not filter, otherwise |
| * <tt>true<tt> |
| * |
| * @since Ant 1.5 |
| */ |
| public void setFiltertrace(boolean value) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setFiltertrace(value); |
| } |
| } |
| |
| /** |
| * If true, stop the build process 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> |
| * |
| * @since Ant 1.2 |
| */ |
| public void setHaltonerror(boolean value) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setHaltonerror(value); |
| } |
| } |
| |
| /** |
| * Property to set to "true" if there is a error in a test. |
| * |
| * <p>This property is applied on all BatchTest (batchtest) and |
| * JUnitTest (test), however, it can possibly be overriden by |
| * their own properties.</p> |
| * @param propertyName the name of the property to set in the |
| * event of an error. |
| * |
| * @since Ant 1.4 |
| */ |
| public void setErrorProperty(String propertyName) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setErrorProperty(propertyName); |
| } |
| } |
| |
| /** |
| * If true, stop the build process if a test fails |
| * (errors are considered failures as well). |
| * 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> |
| * |
| * @since Ant 1.2 |
| */ |
| public void setHaltonfailure(boolean value) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setHaltonfailure(value); |
| } |
| } |
| |
| /** |
| * Property to set to "true" if there is a failure in a test. |
| * |
| * <p>This property is applied on all BatchTest (batchtest) and |
| * JUnitTest (test), however, it can possibly be overriden by |
| * their own properties.</p> |
| * @param propertyName the name of the property to set in the |
| * event of an failure. |
| * |
| * @since Ant 1.4 |
| */ |
| public void setFailureProperty(String propertyName) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setFailureProperty(propertyName); |
| } |
| } |
| |
| /** |
| * If true, JVM should be forked for each test. |
| * |
| * <p>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.</p> |
| * @param value <tt>true</tt> if a JVM should be forked, otherwise |
| * <tt>false</tt> |
| * @see #setTimeout |
| * |
| * @since Ant 1.2 |
| */ |
| public void setFork(boolean value) { |
| Enumeration e = allTests(); |
| while (e.hasMoreElements()) { |
| BaseTest test = (BaseTest) e.nextElement(); |
| test.setFork(value); |
| } |
| } |
| |
| /** |
| * If true, print one-line statistics for each test, or "withOutAndErr" |
| * to also show standard output and error. |
| * |
| * Can take the values on, off, and withOutAndErr. |
| * @param value <tt>true</tt> to print a summary, |
| * <tt>withOutAndErr</tt> to include the test's output as |
| * well, <tt>false</tt> otherwise. |
| * @see SummaryJUnitResultFormatter |
| * |
| * @since Ant 1.2 |
| */ |
| public void setPrintsummary(SummaryAttribute value) { |
| summaryValue = value.getValue(); |
| summary = value.asBoolean(); |
| } |
| |
| /** |
| * Print summary enumeration values. |
| */ |
| public static class SummaryAttribute extends EnumeratedAttribute { |
| /** |
| * list the possible values |
| * @return array of allowed values |
| */ |
| public String[] getValues() { |
| return new String[] {"true", "yes", "false", "no", |
| "on", "off", "withOutAndErr"}; |
| } |
| |
| /** |
| * gives the boolean equivalent of the authorized values |
| * @return boolean equivalent of the value |
| */ |
| public boolean asBoolean() { |
| String value = getValue(); |
| return "true".equals(value) |
| || "on".equals(value) |
| || "yes".equals(value) |
| || "withOutAndErr".equals(value); |
| } |
| } |
| |
| /** |
| * Set the timeout value (in milliseconds). |
| * |
| * <p>If the test is running for more than this value, the test |
| * will be canceled. (works only when in 'fork' mode).</p> |
| * @param value the maximum time (in milliseconds) allowed before |
| * declaring the test as 'timed-out' |
| * @see #setFork(boolean) |
| * |
| * @since Ant 1.2 |
| */ |
| 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. |
| * |
| * @since Ant 1.2 |
| */ |
| public void setMaxmemory(String max) { |
| commandline.setMaxmemory(max); |
| } |
| |
| /** |
| * The command used to invoke the Java Virtual Machine, |
| * default is 'java'. The command is resolved by |
| * java.lang.Runtime.exec(). Ignored if fork is disabled. |
| * |
| * @param value the new VM to use instead of <tt>java</tt> |
| * @see #setFork(boolean) |
| * |
| * @since Ant 1.2 |
| */ |
| public void setJvm(String value) { |
| commandline.setVm(value); |
| } |
| |
| /** |
| * Adds a JVM argument; ignored if not forking. |
| * |
| * @return create a new JVM argument so that any argument can be |
| * passed to the JVM. |
| * @see #setFork(boolean) |
| * |
| * @since Ant 1.2 |
| */ |
| 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) |
| * |
| * @since Ant 1.2 |
| */ |
| public void setDir(File dir) { |
| this.dir = dir; |
| } |
| |
| /** |
| * Adds a system property that tests can access. |
| * This might be useful to tranfer Ant properties to the |
| * testcases when JVM forking is not enabled. |
| * |
| * @since Ant 1.3 |
| * @deprecated since ant 1.6 |
| * @param sysp environment variable to add |
| */ |
| public void addSysproperty(Environment.Variable sysp) { |
| |
| commandline.addSysproperty(sysp); |
| } |
| |
| /** |
| * Adds a system property that tests can access. |
| * This might be useful to tranfer Ant properties to the |
| * testcases when JVM forking is not enabled. |
| * @param sysp new environment variable to add |
| * @since Ant 1.6 |
| */ |
| public void addConfiguredSysproperty(Environment.Variable sysp) { |
| // get a build exception if there is a missing key or value |
| // see bugzilla report 21684 |
| String testString = sysp.getContent(); |
| getProject().log("sysproperty added : " + testString, Project.MSG_DEBUG); |
| commandline.addSysproperty(sysp); |
| } |
| |
| /** |
| * Adds a set of properties that will be used as system properties |
| * that tests can access. |
| * |
| * This might be useful to tranfer Ant properties to the |
| * testcases when JVM forking is not enabled. |
| * |
| * @param sysp set of properties to be added |
| * @since Ant 1.6 |
| */ |
| public void addSyspropertyset(PropertySet sysp) { |
| commandline.addSyspropertyset(sysp); |
| } |
| |
| /** |
| * Adds path to classpath used for tests. |
| * |
| * @return reference to the classpath in the embedded java command line |
| * @since Ant 1.2 |
| */ |
| public Path createClasspath() { |
| return commandline.createClasspath(getProject()).createPath(); |
| } |
| |
| /** |
| * Adds a path to the bootclasspath. |
| * @return reference to the bootclasspath in the embedded java command line |
| * @since Ant 1.6 |
| */ |
| public Path createBootclasspath() { |
| return commandline.createBootclasspath(getProject()).createPath(); |
| } |
| |
| /** |
| * Adds an environment variable; used when forking. |
| * |
| * <p>Will be ignored if we are not forking a new VM.</p> |
| * @param var environment variable to be added |
| * @since Ant 1.5 |
| */ |
| public void addEnv(Environment.Variable var) { |
| env.addVariable(var); |
| } |
| |
| /** |
| * If true, use a new environment when forked. |
| * |
| * <p>Will be ignored if we are not forking a new VM.</p> |
| * |
| * @param newenv boolean indicating if setting a new environment is wished |
| * @since Ant 1.5 |
| */ |
| public void setNewenvironment(boolean newenv) { |
| newEnvironment = newenv; |
| } |
| |
| /** |
| * Add a new single testcase. |
| * @param test a new single testcase |
| * @see JUnitTest |
| * |
| * @since Ant 1.2 |
| */ |
| public void addTest(JUnitTest test) { |
| tests.addElement(test); |
| } |
| |
| /** |
| * Adds a set of tests based on pattern matching. |
| * |
| * @return a new instance of a batch test. |
| * @see BatchTest |
| * |
| * @since Ant 1.2 |
| */ |
| public BatchTest createBatchTest() { |
| BatchTest test = new BatchTest(getProject()); |
| batchTests.addElement(test); |
| return test; |
| } |
| |
| /** |
| * Add a new formatter to all tests of this task. |
| * |
| * @param fe formatter element |
| * @since Ant 1.2 |
| */ |
| public void addFormatter(FormatterElement fe) { |
| formatters.addElement(fe); |
| } |
| |
| /** |
| * If true, include ant.jar, optional.jar and junit.jar in the forked VM. |
| * |
| * @param b include ant run time yes or no |
| * @since Ant 1.5 |
| */ |
| public void setIncludeantruntime(boolean b) { |
| includeAntRuntime = b; |
| } |
| |
| /** |
| * If true, send any output generated by tests to Ant's logging system |
| * as well as to the formatters. |
| * By default only the formatters receive the output. |
| * |
| * <p>Output will always be passed to the formatters and not by |
| * shown by default. This option should for example be set for |
| * tests that are interactive and prompt the user to do |
| * something.</p> |
| * |
| * @param showOutput if true, send output to Ant's logging system too |
| * @since Ant 1.5 |
| */ |
| public void setShowOutput(boolean showOutput) { |
| this.showOutput = showOutput; |
| } |
| |
| /** |
| * Assertions to enable in this program (if fork=true) |
| * @since Ant 1.6 |
| * @param asserts assertion set |
| */ |
| public void addAssertions(Assertions asserts) { |
| if (commandline.getAssertions() != null) { |
| throw new BuildException("Only one assertion declaration is allowed"); |
| } |
| commandline.setAssertions(asserts); |
| } |
| |
| /** |
| * Sets the permissions for the application run inside the same JVM. |
| * @since Ant 1.6 |
| * @return . |
| */ |
| public Permissions createPermissions() { |
| if (perm == null) { |
| perm = new Permissions(); |
| } |
| return perm; |
| } |
| |
| /** |
| * Creates a new JUnitRunner and enables fork of a new Java VM. |
| * |
| * @throws Exception under ??? circumstances |
| * @since Ant 1.2 |
| */ |
| public JUnitTask() throws Exception { |
| commandline |
| .setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); |
| } |
| |
| /** |
| * Where Ant should place temporary files. |
| * |
| * @param tmpDir location where temporary files should go to |
| * @since Ant 1.6 |
| */ |
| public void setTempdir(File tmpDir) { |
| this.tmpDir = tmpDir; |
| } |
| |
| /** |
| * Adds the jars or directories containing Ant, this task and |
| * JUnit to the classpath - this should make the forked JVM work |
| * without having to specify them directly. |
| * |
| * @since Ant 1.4 |
| */ |
| public void init() { |
| antRuntimeClasses = new Path(getProject()); |
| addClasspathEntry("/junit/framework/TestCase.class"); |
| addClasspathEntry("/org/apache/tools/ant/launch/AntMain.class"); |
| addClasspathEntry("/org/apache/tools/ant/Task.class"); |
| addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class"); |
| } |
| |
| /** |
| * Runs the testcase. |
| * |
| * @throws BuildException in case of test failures or errors |
| * @since Ant 1.2 |
| */ |
| public void execute() throws BuildException { |
| Enumeration list = getIndividualTests(); |
| while (list.hasMoreElements()) { |
| JUnitTest test = (JUnitTest) list.nextElement(); |
| if (test.shouldRun(getProject())) { |
| execute(test); |
| } |
| } |
| } |
| |
| /** |
| * Run the tests. |
| * @param arg one JunitTest |
| * @throws BuildException in case of test failures or errors |
| */ |
| protected void execute(JUnitTest arg) throws BuildException { |
| JUnitTest test = (JUnitTest) arg.clone(); |
| // set the default values if not specified |
| //@todo should be moved to the test class instead. |
| if (test.getTodir() == null) { |
| test.setTodir(getProject().resolveFile(".")); |
| } |
| |
| if (test.getOutfile() == null) { |
| test.setOutfile("TEST-" + test.getName()); |
| } |
| |
| // 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) { |
| wasKilled = watchdog.killedProcess(); |
| } |
| } |
| |
| // if there is an error/failure and that it should halt, stop |
| // everything otherwise just log a statement |
| boolean errorOccurredHere = |
| exitValue == JUnitTestRunner.ERRORS || wasKilled; |
| boolean failureOccurredHere = |
| exitValue != JUnitTestRunner.SUCCESS || wasKilled; |
| if (errorOccurredHere || failureOccurredHere) { |
| if ((errorOccurredHere && test.getHaltonerror()) |
| || (failureOccurredHere && test.getHaltonfailure())) { |
| throw new BuildException("Test " + test.getName() + " failed" |
| + (wasKilled ? " (timeout)" : ""), getLocation()); |
| } else { |
| log("TEST " + test.getName() + " FAILED" |
| + (wasKilled ? " (timeout)" : ""), Project.MSG_ERR); |
| if (errorOccurredHere && test.getErrorProperty() != null) { |
| getProject().setNewProperty(test.getErrorProperty(), "true"); |
| } |
| if (failureOccurredHere && test.getFailureProperty() != null) { |
| getProject().setNewProperty(test.getFailureProperty(), "true"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| * @throws BuildException in case of error creating a temporary property file, |
| * or if the junit process can not be forked |
| */ |
| private int executeAsForked(JUnitTest test, ExecuteWatchdog watchdog) |
| throws BuildException { |
| |
| if(perm != null) { |
| log("Permissions ignored when running in forked mode!", Project.MSG_WARN); |
| } |
| |
| CommandlineJava cmd = (CommandlineJava) commandline.clone(); |
| |
| cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); |
| cmd.createArgument().setValue(test.getName()); |
| cmd.createArgument().setValue("filtertrace=" + test.getFiltertrace()); |
| cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror()); |
| cmd.createArgument().setValue("haltOnFailure=" |
| + test.getHaltonfailure()); |
| if (includeAntRuntime) { |
| Vector v = Execute.getProcEnvironment(); |
| Enumeration e = v.elements(); |
| while (e.hasMoreElements()) { |
| String s = (String) e.nextElement(); |
| if (s.startsWith("CLASSPATH=")) { |
| cmd.createClasspath(getProject()).createPath() |
| .append(new Path(getProject(), |
| s.substring(10 // "CLASSPATH=".length() |
| ))); |
| } |
| } |
| log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH", |
| Project.MSG_VERBOSE); |
| cmd.createClasspath(getProject()).createPath() |
| .append(antRuntimeClasses); |
| } |
| |
| if (summary) { |
| log("Running " + test.getName(), Project.MSG_INFO); |
| cmd.createArgument() |
| .setValue("formatter" |
| + "=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter"); |
| } |
| |
| cmd.createArgument().setValue("showoutput=" |
| + String.valueOf(showOutput)); |
| |
| StringBuffer formatterArg = new StringBuffer(STRING_BUFFER_SIZE); |
| final FormatterElement[] feArray = mergeFormatters(test); |
| for (int i = 0; i < feArray.length; i++) { |
| FormatterElement fe = feArray[i]; |
| if (fe.shouldUse(this)) { |
| 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 = new StringBuffer(); |
| } |
| } |
| |
| // Create a temporary file to pass the Ant properties to the |
| // forked test |
| File propsFile = |
| FileUtils.newFileUtils().createTempFile("junit", ".properties", |
| tmpDir != null ? tmpDir : getProject().getBaseDir()); |
| propsFile.deleteOnExit(); |
| cmd.createArgument().setValue("propsfile=" |
| + propsFile.getAbsolutePath()); |
| Hashtable p = getProject().getProperties(); |
| Properties props = new Properties(); |
| for (Enumeration e = p.keys(); e.hasMoreElements();) { |
| Object key = e.nextElement(); |
| props.put(key, p.get(key)); |
| } |
| try { |
| FileOutputStream outstream = new FileOutputStream(propsFile); |
| props.store(outstream, "Ant JUnitTask generated properties file"); |
| outstream.close(); |
| } catch (java.io.IOException e) { |
| propsFile.delete(); |
| throw new BuildException("Error creating temporary properties " |
| + "file.", e, getLocation()); |
| } |
| |
| Execute execute = new Execute(new LogStreamHandler(this, |
| Project.MSG_INFO, |
| Project.MSG_WARN), |
| watchdog); |
| execute.setCommandline(cmd.getCommandline()); |
| execute.setAntRun(getProject()); |
| if (dir != null) { |
| execute.setWorkingDirectory(dir); |
| } |
| |
| String[] environment = env.getVariables(); |
| if (environment != null) { |
| for (int i = 0; i < environment.length; i++) { |
| log("Setting environment variable: " + environment[i], |
| Project.MSG_VERBOSE); |
| } |
| } |
| execute.setNewenvironment(newEnvironment); |
| execute.setEnvironment(environment); |
| |
| log(cmd.describeCommand(), Project.MSG_VERBOSE); |
| int retVal; |
| try { |
| retVal = execute.execute(); |
| } catch (IOException e) { |
| throw new BuildException("Process fork failed.", e, getLocation()); |
| } finally { |
| if (watchdog != null && watchdog.killedProcess()) { |
| logTimeout(feArray, test); |
| } |
| |
| if (!propsFile.delete()) { |
| throw new BuildException("Could not delete temporary " |
| + "properties file."); |
| } |
| } |
| |
| return retVal; |
| } |
| |
| |
| /** |
| * Pass output sent to System.out to the TestRunner so it can |
| * collect ot for the formatters. |
| * |
| * @param output output coming from System.out |
| * @since Ant 1.5 |
| */ |
| protected void handleOutput(String output) { |
| if (runner != null) { |
| runner.handleOutput(output); |
| if (showOutput) { |
| super.handleOutput(output); |
| } |
| } else { |
| super.handleOutput(output); |
| } |
| } |
| |
| /** |
| * @see Task#handleInput(byte[], int, int) |
| * |
| * @since Ant 1.6 |
| */ |
| protected int handleInput(byte[] buffer, int offset, int length) |
| throws IOException { |
| if (runner != null) { |
| return runner.handleInput(buffer, offset, length); |
| } else { |
| return super.handleInput(buffer, offset, length); |
| } |
| } |
| |
| |
| /** |
| * Pass output sent to System.out to the TestRunner so it can |
| * collect ot for the formatters. |
| * |
| * @param output output coming from System.out |
| * @since Ant 1.5.2 |
| */ |
| protected void handleFlush(String output) { |
| if (runner != null) { |
| runner.handleFlush(output); |
| if (showOutput) { |
| super.handleFlush(output); |
| } |
| } else { |
| super.handleFlush(output); |
| } |
| } |
| |
| /** |
| * Pass output sent to System.err to the TestRunner so it can |
| * collect it for the formatters. |
| * |
| * @param output output coming from System.err |
| * @since Ant 1.5 |
| */ |
| public void handleErrorOutput(String output) { |
| if (runner != null) { |
| runner.handleErrorOutput(output); |
| if (showOutput) { |
| super.handleErrorOutput(output); |
| } |
| } else { |
| super.handleErrorOutput(output); |
| } |
| } |
| |
| |
| /** |
| * Pass output sent to System.err to the TestRunner so it can |
| * collect it for the formatters. |
| * |
| * @param output coming from System.err |
| * @since Ant 1.5.2 |
| */ |
| public void handleErrorFlush(String output) { |
| if (runner != null) { |
| runner.handleErrorFlush(output); |
| if (showOutput) { |
| super.handleErrorFlush(output); |
| } |
| } else { |
| super.handleErrorFlush(output); |
| } |
| } |
| |
| // 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. |
| * @param arg one JUnitTest |
| * @throws BuildException under unspecified circumstances |
| */ |
| private int executeInVM(JUnitTest arg) throws BuildException { |
| JUnitTest test = (JUnitTest) arg.clone(); |
| test.setProperties(getProject().getProperties()); |
| if (dir != null) { |
| log("dir attribute ignored if running in the same VM", |
| Project.MSG_WARN); |
| } |
| |
| if (newEnvironment || null != env.getVariables()) { |
| log("Changes to environment variables are ignored if running in " |
| + "the same VM.", Project.MSG_WARN); |
| } |
| |
| if (commandline.getBootclasspath() != null) { |
| log("bootclasspath is ignored if running in the same VM.", |
| Project.MSG_WARN); |
| } |
| |
| CommandlineJava.SysProperties sysProperties = |
| commandline.getSystemProperties(); |
| if (sysProperties != null) { |
| sysProperties.setSystem(); |
| } |
| |
| try { |
| log("Using System properties " + System.getProperties(), |
| Project.MSG_VERBOSE); |
| createClassLoader(); |
| if (classLoader != null) { |
| classLoader.setThreadContextLoader(); |
| } |
| runner = new JUnitTestRunner(test, test.getHaltonerror(), |
| test.getFiltertrace(), |
| test.getHaltonfailure(), classLoader); |
| if (summary) { |
| log("Running " + test.getName(), Project.MSG_INFO); |
| |
| SummaryJUnitResultFormatter f = |
| new SummaryJUnitResultFormatter(); |
| f.setWithOutAndErr("withoutanderr" |
| .equalsIgnoreCase(summaryValue)); |
| f.setOutput(getDefaultOutput()); |
| runner.addFormatter(f); |
| } |
| |
| runner.setPermissions(perm); |
| |
| final FormatterElement[] feArray = mergeFormatters(test); |
| for (int i = 0; i < feArray.length; i++) { |
| FormatterElement fe = feArray[i]; |
| if (fe.shouldUse(this)) { |
| File outFile = getOutput(fe, test); |
| if (outFile != null) { |
| fe.setOutfile(outFile); |
| } else { |
| fe.setOutput(getDefaultOutput()); |
| } |
| runner.addFormatter(fe.createFormatter(classLoader)); |
| } |
| } |
| |
| runner.run(); |
| return runner.getRetCode(); |
| } finally { |
| if (sysProperties != null) { |
| sysProperties.restoreSystem(); |
| } |
| if (classLoader != null) { |
| classLoader.resetThreadContextLoader(); |
| } |
| } |
| } |
| |
| /** |
| * @return <tt>null</tt> if there is a timeout value, otherwise the |
| * watchdog instance. |
| * |
| * @throws BuildException under unspecified circumstances |
| * @since Ant 1.2 |
| */ |
| protected ExecuteWatchdog createWatchdog() throws BuildException { |
| if (timeout == null) { |
| return null; |
| } |
| return new ExecuteWatchdog(timeout.intValue()); |
| } |
| |
| /** |
| * Get the default output for a formatter. |
| * |
| * @return default output stream for a formatter |
| * @since Ant 1.3 |
| */ |
| protected OutputStream getDefaultOutput() { |
| return new LogOutputStream(this, Project.MSG_INFO); |
| } |
| |
| /** |
| * Merge all individual tests from the batchtest with all individual tests |
| * and return an enumeration over all <tt>JUnitTest</tt>. |
| * |
| * @return enumeration over individual tests |
| * @since Ant 1.3 |
| */ |
| protected Enumeration getIndividualTests() { |
| final int count = batchTests.size(); |
| final Enumeration[] enums = new Enumeration[ count + 1]; |
| for (int i = 0; i < count; i++) { |
| BatchTest batchtest = (BatchTest) batchTests.elementAt(i); |
| enums[i] = batchtest.elements(); |
| } |
| enums[enums.length - 1] = tests.elements(); |
| return Enumerations.fromCompound(enums); |
| } |
| |
| /** |
| * return an enumeration listing each test, then each batchtest |
| * @return enumeration |
| * @since Ant 1.3 |
| */ |
| protected Enumeration allTests() { |
| Enumeration[] enums = {tests.elements(), batchTests.elements()}; |
| return Enumerations.fromCompound(enums); |
| } |
| |
| /** |
| * @param test junit test |
| * @return array of FormatterElement |
| * @since Ant 1.3 |
| */ |
| private FormatterElement[] mergeFormatters(JUnitTest test) { |
| Vector feVector = (Vector) formatters.clone(); |
| test.addFormattersTo(feVector); |
| FormatterElement[] feArray = new FormatterElement[feVector.size()]; |
| feVector.copyInto(feArray); |
| return feArray; |
| } |
| |
| /** |
| * If the formatter sends output to a file, return that file. |
| * null otherwise. |
| * @param fe formatter element |
| * @param test one JUnit test |
| * @return file reference |
| * @since Ant 1.3 |
| */ |
| protected File getOutput(FormatterElement fe, JUnitTest test) { |
| if (fe.getUseFile()) { |
| String filename = test.getOutfile() + fe.getExtension(); |
| File destFile = new File(test.getTodir(), filename); |
| String absFilename = destFile.getAbsolutePath(); |
| return getProject().resolveFile(absFilename); |
| } |
| return null; |
| } |
| |
| /** |
| * Search for the given resource and add the directory or archive |
| * that contains it to the classpath. |
| * |
| * <p>Doesn't work for archives in JDK 1.1 as the URL returned by |
| * getResource doesn't contain the name of the archive.</p> |
| * |
| * @param resource resource that one wants to lookup |
| * @since Ant 1.4 |
| */ |
| protected void addClasspathEntry(String resource) { |
| /* |
| * pre Ant 1.6 this method used to call getClass().getResource |
| * while Ant 1.6 will call ClassLoader.getResource(). |
| * |
| * The difference is that Class.getResource expects a leading |
| * slash for "absolute" resources and will strip it before |
| * delegating to ClassLoader.getResource - so we now have to |
| * emulate Class's behavior. |
| */ |
| if (resource.startsWith("/")) { |
| resource = resource.substring(1); |
| } else { |
| resource = "org/apache/tools/ant/taskdefs/optional/junit/" |
| + resource; |
| } |
| |
| File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), |
| resource); |
| if (f != null) { |
| log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG); |
| antRuntimeClasses.createPath().setLocation(f); |
| } else { |
| log("Couldn\'t find " + resource, Project.MSG_DEBUG); |
| } |
| } |
| |
| /** |
| * Take care that some output is produced in report files if the |
| * watchdog kills the test. |
| * |
| * @since Ant 1.5.2 |
| */ |
| |
| private void logTimeout(FormatterElement[] feArray, JUnitTest test) { |
| createClassLoader(); |
| test.setCounts(1, 0, 1); |
| test.setProperties(getProject().getProperties()); |
| for (int i = 0; i < feArray.length; i++) { |
| FormatterElement fe = feArray[i]; |
| File outFile = getOutput(fe, test); |
| JUnitResultFormatter formatter = fe.createFormatter(classLoader); |
| if (outFile != null && formatter != null) { |
| try { |
| OutputStream out = new FileOutputStream(outFile); |
| addTimeout(test, formatter, out); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| if (summary) { |
| SummaryJUnitResultFormatter f = new SummaryJUnitResultFormatter(); |
| f.setWithOutAndErr("withoutanderr".equalsIgnoreCase(summaryValue)); |
| addTimeout(test, f, getDefaultOutput()); |
| } |
| } |
| |
| /** |
| * Adds the actual timeout to the formatter. |
| * Only used from the logTimeout method. |
| * @since Ant 1.6 |
| */ |
| private void addTimeout(JUnitTest test, JUnitResultFormatter formatter, |
| OutputStream out) { |
| formatter.setOutput(out); |
| formatter.startTestSuite(test); |
| Test t = new Test() { |
| public int countTestCases() { return 1; } |
| public void run(TestResult r) { |
| throw new AssertionFailedError("Timeout occurred"); |
| } |
| }; |
| formatter.startTest(t); |
| formatter.addError(t, new AssertionFailedError("Timeout occurred")); |
| formatter.endTestSuite(test); |
| } |
| |
| /** |
| * Creates and configures an AntClassLoader instance from the |
| * nested classpath element. |
| * |
| * @since Ant 1.6 |
| */ |
| private void createClassLoader() { |
| Path userClasspath = commandline.getClasspath(); |
| if (userClasspath != null) { |
| if (reloading || classLoader == null) { |
| Path classpath = (Path) userClasspath.clone(); |
| if (includeAntRuntime) { |
| log("Implicitly adding " + antRuntimeClasses |
| + " to CLASSPATH", Project.MSG_VERBOSE); |
| classpath.append(antRuntimeClasses); |
| } |
| classLoader = getProject().createClassLoader(classpath); |
| log("Using CLASSPATH " + classLoader.getClasspath(), |
| Project.MSG_VERBOSE); |
| classLoader.setParentFirst(false); |
| classLoader.addJavaLibraries(); |
| log("Using CLASSPATH " + classLoader.getClasspath(), Project.MSG_VERBOSE); |
| // make sure the test will be accepted as a TestCase |
| classLoader.addSystemPackageRoot("junit"); |
| // will cause trouble in JDK 1.1 if omitted |
| classLoader.addSystemPackageRoot("org.apache.tools.ant"); |
| } |
| } |
| } |
| } |