You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by Philip Aston <pa...@bea.com> on 2003/09/29 20:33:54 UTC
[PATCH] JUnit task - forking only once for a batch - RESEND
> -----Original Message-----
> From: Philip Aston
> Sent: 28 September 2003 15:49
> To: 'dev@ant.apache.org'
> Subject: [PATCH] JUnit task - forking only once for a batch
>
> Patch and new file attached for consideration. Example usage:
>
> <junit fork="true">
> <batchtest singleProcess="false">
> <formatter type="plain" usefile="false"/>
>
> <fileset dir="${test-src.dir}">
> <include name="**/*Tests.java"/>
> </fileset>
> </batchtest>
> </junit>
>
>
> If its OK, I'm more than happy to update the docs.
Previous mail had attachments that were defeated by ezmlm. Resending as inline text.
- Phil
Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java
===================================================================
RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java,v
retrieving revision 1.16
diff -u -u -r1.16 BatchTest.java
--- src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java 19 Jul 2003 11:20:19 -0000 1.16
+++ src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java 28 Sep 2003 14:44:14 -0000
@@ -85,6 +85,8 @@
/** the list of filesets containing the testcase filename rules */
private Vector filesets = new Vector();
+ private boolean singleProcess;
+
/**
* create a new batchtest instance
* @param project the project it depends on.
@@ -101,6 +103,14 @@
*/
public void addFileSet(FileSet fs) {
filesets.addElement(fs);
+ }
+
+ public void setSingleProcess(boolean value) {
+ singleProcess = value;
+ }
+
+ public boolean getSingleProcess() {
+ return singleProcess;
}
/**
Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
===================================================================
RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java,v
retrieving revision 1.84
diff -u -u -r1.84 JUnitTask.java
--- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java 23 Sep 2003 06:31:46 -0000 1.84
+++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java 28 Sep 2003 14:44:15 -0000
@@ -58,6 +58,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
@@ -150,6 +151,7 @@
* @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>
+ * @author Philip Aston
*
* @version $Revision: 1.84 $
*
@@ -620,6 +622,7 @@
addClasspathEntry("/junit/framework/TestCase.class");
addClasspathEntry("/org/apache/tools/ant/Task.class");
addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class");
+ addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitForkOnceTestRunner.class");
}
/**
@@ -631,10 +634,24 @@
public void execute() throws BuildException {
Enumeration list = getIndividualTests();
while (list.hasMoreElements()) {
- JUnitTest test = (JUnitTest) list.nextElement();
- if (test.shouldRun(getProject())) {
- execute(test);
- }
+ final Object o = list.nextElement();
+
+ if (o instanceof JUnitTest) {
+ final JUnitTest test = (JUnitTest) o;
+
+ if (test.shouldRun(getProject())) {
+ execute(test);
+ }
+ }
+ else {
+ final BatchTest batchTest = (BatchTest)o;
+
+ if (!batchTest.getSingleProcess()) {
+ throw new BuildException("Assertion failure");
+ }
+
+ execute(batchTest);
+ }
}
}
@@ -693,6 +710,46 @@
}
}
+ protected void execute(BatchTest test) throws BuildException {
+
+ // execute the test and get the return code
+ int exitValue = JUnitTestRunner.ERRORS;
+ boolean wasKilled = false;
+
+ final ExecuteWatchdog watchdog = createWatchdog();
+
+ exitValue = executeMultipleTestsInOneFork(test, watchdog);
+
+ 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;
+ boolean failureOccurredHere =
+ exitValue != JUnitTestRunner.SUCCESS;
+ if (errorOccurredHere || failureOccurredHere) {
+ if ((errorOccurredHere && test.getHaltonerror())
+ || (failureOccurredHere && test.getHaltonfailure())) {
+ throw new BuildException("Batch test failed"
+ + (wasKilled ? " (timeout)" : ""), getLocation());
+ } else {
+ log("BATCH TEST 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
@@ -827,6 +884,165 @@
return retVal;
}
+ private int executeMultipleTestsInOneFork(BatchTest test,
+ ExecuteWatchdog watchdog)
+ throws BuildException {
+
+ final CommandlineJava cmd = (CommandlineJava) commandline.clone();
+ cmd.setClassname(JUnitForkOnceTestRunner.class.getName());
+
+ if (includeAntRuntime) {
+ log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH",
+ Project.MSG_VERBOSE);
+ cmd.createClasspath(getProject()).createPath()
+ .append(antRuntimeClasses);
+ }
+
+ cmd.createArgument().setValue("showoutput="
+ + String.valueOf(showOutput));
+
+ final File propsFile =
+ FileUtils.newFileUtils().createTempFile("junit", ".properties",
+ getProject().getBaseDir());
+
+ cmd.createArgument().setValue(
+ "propsfile=" + propsFile.getAbsolutePath());
+
+ final File testsFile =
+ FileUtils.newFileUtils().createTempFile(
+ "junit-tests", ".properties", getProject().getBaseDir());
+
+ cmd.createArgument().setValue(
+ "testsfile=" + testsFile.getAbsolutePath());
+
+ try {
+ final Hashtable p = getProject().getProperties();
+ final Properties props = new Properties();
+
+ for (Enumeration enum = p.keys(); enum.hasMoreElements();) {
+ Object key = enum.nextElement();
+ props.put(key, p.get(key));
+ }
+
+ try {
+ final FileOutputStream outstream =
+ new FileOutputStream(propsFile);
+
+ props.save(outstream,
+ "Ant JUnitTask generated properties file");
+
+ outstream.close();
+ }
+ catch (java.io.IOException e) {
+ throw new BuildException("Error creating temporary properties "
+ + "file.", e, getLocation());
+ }
+
+ try {
+ final PrintStream out =
+ new PrintStream(new FileOutputStream(testsFile));
+
+ final Enumeration list = test.elements();
+
+ while (list.hasMoreElements()) {
+ out.println(testInvocationAsString(
+ (JUnitTest)list.nextElement()));
+ }
+
+ out.close();
+ }
+ catch (IOException e) {
+ throw new BuildException(
+ "Error creating temporary file", e, getLocation());
+ }
+
+ final Execute execute =
+ new Execute(new LogStreamHandler(this,
+ Project.MSG_INFO,
+ Project.MSG_WARN),
+ watchdog);
+
+ execute.setCommandline(cmd.getCommandline());
+ execute.setAntRun(project);
+
+ 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);
+
+ try {
+ return execute.execute();
+ }
+ catch (IOException e) {
+ throw new BuildException("Process fork failed.", e,
+ getLocation());
+ }
+ }
+ finally {
+ if (!propsFile.delete()) {
+ testsFile.delete();
+ log("Could not delete temporary file", Project.MSG_ERR);
+ }
+
+ if (!testsFile.delete()) {
+ log("Could not delete temporary file", Project.MSG_ERR);
+ }
+ }
+ }
+
+ private String testInvocationAsString(JUnitTest test) {
+
+ // 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());
+ }
+
+ final StringBuffer result = new StringBuffer();
+
+ result.append(test.getName());
+ result.append(" filtertrace=" + test.getFiltertrace());
+ result.append(" haltOnError=" + test.getHaltonerror());
+ result.append(" haltOnFailure=" + test.getHaltonfailure());
+
+ if (summary) {
+ log("Running " + test.getName(), Project.MSG_INFO);
+ result.append(" formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter");
+ }
+
+ final FormatterElement[] feArray = mergeFormatters(test);
+ for (int i = 0; i < feArray.length; i++) {
+ final FormatterElement fe = feArray[i];
+
+ result.append(" formatter=" + fe.getClassname());
+
+ final File outFile = getOutput(fe, test);
+
+ if (outFile != null) {
+ result.append(",");
+ result.append(outFile);
+ }
+ }
+
+ return result.toString();
+ }
+
/**
* Pass output sent to System.out to the TestRunner so it can
@@ -1030,15 +1246,23 @@
* @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);
- }
+ final Vector singleProcessBatchTests = new Vector();
+
+ final int count = batchTests.size();
+ final Enumeration[] enums = new Enumeration[ count + 2];
+ for (int i = 0; i < count; i++) {
+ BatchTest batchtest = (BatchTest) batchTests.elementAt(i);
+
+ if (batchtest.getSingleProcess()) {
+ singleProcessBatchTests.addElement(batchtest);
+ }
+ else {
+ enums[i] = batchtest.elements();
+ }
+ enums[enums.length - 1] = tests.elements();
+ enums[enums.length - 2] = singleProcessBatchTests.elements();
+ return Enumerations.fromCompound(enums);
+ }
/**
* return an enumeration listing each test, then each batchtest
Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
===================================================================
RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java,v
retrieving revision 1.42
diff -u -u -r1.42 JUnitTestRunner.java
--- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java 21 Sep 2003 20:20:03 -0000 1.42
+++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java 28 Sep 2003 14:44:15 -0000
@@ -214,6 +214,13 @@
this(test, haltOnError, filtertrace, haltOnFailure, showOutput, null);
}
+ public JUnitTestRunner(JUnitTest test, boolean haltOnError,
+ boolean filtertrace, boolean haltOnFailure,
+ boolean showOutput, boolean forked) {
+ this(test, haltOnError, filtertrace, haltOnFailure, showOutput, null);
+ this.forked = forked;
+ }
+
/**
* Constructor to use when the user has specified a classpath.
*/
Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitForkOnceTestRunner.java
===================================================================
--- /tmp/JUnitForkOnceTestRunner.java 2003-09-29 18:18:31.000000000 +0100
+++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitForkOnceTestRunner.java 2003-09-29 18:20:07.000000000 +0100
@@ -0,0 +1,195 @@
+/*
+ * 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 "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.io.BufferedReader;
+import java.io.FileReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Simple Testrunner for JUnit that runs the tests of several
+ * testsuites in a single JVM.
+ *
+ * @author Phil Aston
+ *
+ * @see TestRunner
+ * @since Ant 1.6
+ */
+
+public class JUnitForkOnceTestRunner {
+
+ public static void main(String[] args) throws IOException {
+
+ final Properties properties = new Properties();
+ boolean showOut = false;
+ BufferedReader testsReader = null;
+
+ for (int i=0; i < args.length; i++) {
+ if (args[i].startsWith("testsfile=")) {
+ testsReader =
+ new BufferedReader(new FileReader(args[i].substring(10)));
+ }
+ else if (args[i].startsWith("propsfile=")) {
+ final FileInputStream in = new FileInputStream(args[i].substring(10));
+
+ properties.load(in);
+ in.close();
+ }
+ else if (args[i].startsWith("showoutput=")) {
+ showOut = Project.toBoolean(args[i].substring(11));
+ }
+ }
+
+ if (testsReader == null) {
+ System.err.println("required testsfile argument missing");
+ System.exit(JUnitTestRunner.ERRORS);
+ }
+
+ final Hashtable p = System.getProperties();
+ for (Enumeration enum = p.keys(); enum.hasMoreElements();) {
+ final Object key = enum.nextElement();
+ properties.put(key, p.get(key));
+ }
+
+ int combinedReturnCode = 0;
+
+ while (true) {
+ final String line = testsReader.readLine();
+
+ if (line == null) {
+ break;
+ }
+
+ final StringTokenizer tokenizer = new StringTokenizer(line);
+
+ final JUnitTest test = new JUnitTest(tokenizer.nextToken());
+
+ test.setProperties(properties);
+
+ final Vector formatters = new Vector();
+
+ boolean exitAtEnd = true;
+ boolean haltError = false;
+ boolean haltFail = false;
+ boolean stackfilter = true;
+
+ while (tokenizer.hasMoreTokens()) {
+ final String token = tokenizer.nextToken();
+
+ if (token.startsWith("haltOnError=")) {
+ haltError = Project.toBoolean(token.substring(12));
+ }
+ else if (token.startsWith("haltOnFailure=")) {
+ haltFail = Project.toBoolean(token.substring(14));
+ }
+ else if (token.startsWith("filtertrace=")) {
+ stackfilter = Project.toBoolean(token.substring(12));
+ }
+ else if (token.startsWith("formatter=")) {
+ try {
+ formatters.addElement(createFormatter(token.substring(10)));
+ } catch (BuildException be) {
+ System.err.println(be.getMessage());
+ System.exit(JUnitTestRunner.ERRORS);
+ }
+ }
+ }
+
+ JUnitTestRunner runner =
+ new JUnitTestRunner(test, haltError, stackfilter, haltFail, showOut,
+ true);
+
+ for (int i = 0; i < formatters.size(); i++) {
+ runner.addFormatter((JUnitResultFormatter) formatters.elementAt(i));
+ }
+
+ runner.run();
+
+ final int retCode = runner.getRetCode();
+
+ if (retCode > combinedReturnCode) {
+ combinedReturnCode = retCode;
+ }
+ }
+
+ System.exit(combinedReturnCode);
+ }
+
+ /**
+ * Line format is: formatter=<classname>(,<pathname>)?
+ */
+ private static JUnitResultFormatter createFormatter(String line)
+ throws BuildException {
+ FormatterElement fe = new FormatterElement();
+ 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)));
+ }
+
+ return fe.createFormatter();
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org