> -----Original Message----- > From: Philip Aston > Sent: 28 September 2003 15:49 > To: '[EMAIL PROTECTED]' > 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:[EMAIL PROTECTED]">Erik Hatcher</a> * @author <a href="mailto:[EMAIL PROTECTED]">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 [EMAIL PROTECTED] + * + * 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: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]