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