diff --git a/WHATSNEW b/WHATSNEW index 51440bda7..30dc39db3 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -306,6 +306,12 @@ Other changes: * now supports XML namespaces. Bugzilla Report 36804. + * A new listener for has been added that tries to invoke the + tearDown method of a TestCase if that TestCase was run in a forked + VM and the VM crashed or a timeout occured. See the task's + manual page for details. + Bugzilla Report 37241. + Changes from Ant 1.7.0 TO Ant 1.7.1 ============================================= diff --git a/docs/manual/OptionalTasks/junit.html b/docs/manual/OptionalTasks/junit.html index 8ecf6d811..4af2ef929 100644 --- a/docs/manual/OptionalTasks/junit.html +++ b/docs/manual/OptionalTasks/junit.html @@ -569,6 +569,46 @@ supported.

Batchtests can define their own formatters via nested <formatter> elements.

+

Forked tests and tearDown

+ +

If a forked test runs into a timeout, Ant will terminate the Java + VM process it has created, which probably means the + test's tearDown method will never be called. The same + is true if the forked VM crashes for some other reason.

+ +

Starting with Ant 1.8.0, a special formatter is distributed with + Ant that tries to load the testcase that was in the forked VM and + invoke that class' tearDown method. This formatter has + the following limitations:

+ +
    +
  • It runs in the same Java VM as Ant itself, this is a different + Java VM than the one that was executing the test and it may see a + different classloader (and thus may be unable to load the tast + class).
  • +
  • It cannot determine which test was run when the timeout/crash + occured if the forked VM was running multiple test. I.e. the + formatter cannot work with any forkMode other + than perTest and it won't do anything if the test + class contains a suite() method.
  • +
+ +

If the formatter recognizes an incompatible forkMode + or a suite method or fails to load the test class it + will silently do nothing.

+ +

The formatter doesn't have any effect on tests that were not + forked or didn't cause timeouts or VM crashes.

+ +

To enable the formatter, add a formatter like

+ +
+<formatter classname="org.apache.tools.ant.taskdefs.optional.junit.TearDownOnVmCrash"
+           usefile="false"/>
+
+ +

to your junit task.

+

Examples

diff --git a/src/etc/testcases/taskdefs/optional/junit/teardownlistener.xml b/src/etc/testcases/taskdefs/optional/junit/teardownlistener.xml
new file mode 100644
index 000000000..abb6a4107
--- /dev/null
+++ b/src/etc/testcases/taskdefs/optional/junit/teardownlistener.xml
@@ -0,0 +1,50 @@
+
+
+
+
+  
+    
+    
+  
+
+  
+    
+      
+      
+        
+          
+        
+      
+      
+    
+  
+
+  
+    
+      
+      
+      
+        
+          
+        
+      
+      
+    
+  
+
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
index a103ba82c..a68cef05b 100644
--- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
@@ -731,6 +731,7 @@ public class JUnitTask extends Task {
             "OutErrSummaryJUnitResultFormatter",
             "PlainJUnitResultFormatter",
             "SummaryJUnitResultFormatter",
+            "TearDownOnVmCrash",
             "XMLJUnitResultFormatter",
         };
 
@@ -1502,19 +1503,19 @@ public class JUnitTask extends Task {
         }
     }
 
+    static final String TIMEOUT_MESSAGE = 
+        "Timeout occurred. Please note the time in the report does"
+        + " not reflect the time until the timeout.";
+
     /**
      * 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, String testCase) {
-        logVmExit(
-            feArray, test,
-            "Timeout occurred. Please note the time in the report does"
-            + " not reflect the time until the timeout.",
-            testCase);
+    private void logTimeout(FormatterElement[] feArray, JUnitTest test,
+                            String testCase) {
+        logVmExit(feArray, test, TIMEOUT_MESSAGE, testCase);
     }
 
     /**
@@ -1942,6 +1943,8 @@ public class JUnitTask extends Task {
         }
     }
 
+    static final String NAME_OF_DUMMY_TEST = "Batch-With-Multiple-Tests";
+
     /**
      * Creates a JUnitTest instance that shares all flags with the
      * passed in instance but has a more meaningful name.
@@ -1962,7 +1965,7 @@ public class JUnitTask extends Task {
         // make sure test looks as if it was in the same "package" as
         // the last test of the batch
         String pack = index > 0 ? test.getName().substring(0, index + 1) : "";
-        t.setName(pack + "Batch-With-Multiple-Tests");
+        t.setName(pack + NAME_OF_DUMMY_TEST);
         return t;
     }
 
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java
new file mode 100644
index 000000000..be5083d3e
--- /dev/null
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.tools.ant.taskdefs.optional.junit;
+
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+
+/**
+ * Formatter that doesn't create any output but tries to invoke the
+ * tearDown method on a testcase if that test was forked and caused a
+ * timeout or VM crash.
+ *
+ * 

This formatter has some limitations, for details see the + * <junit> task's manual.

+ * + * @since Ant 1.8.0 + */ +public class TearDownOnVmCrash implements JUnitResultFormatter { + + private String suiteName; + + /** + * Records the suite's name to later determine the class to invoke + * tearDown on. + */ + public void startTestSuite(final JUnitTest suite) { + suiteName = suite.getName(); + if (suiteName != null && + suiteName.endsWith(JUnitTask.NAME_OF_DUMMY_TEST)) { + // no way to know which class caused the timeout + suiteName = null; + } + } + + /** + * Only invoke tearDown if the suite is known and not the dummy + * test we get when a Batch fails and the error is an actual + * error generated by Ant. + */ + public void addError(final Test fakeTest, final Throwable t) { + if (suiteName != null + && fakeTest instanceof JUnitTaskMirrorImpl.VmExitErrorTest) { + tearDown(); + } + } + + // no need to implement the rest + public void addFailure(Test test, Throwable t) {} + + public void addFailure(Test test, AssertionFailedError t) {} + + public void startTest(Test test) {} + + public void endTest(Test test) {} + + public void endTestSuite(JUnitTest suite) {} + + public void setOutput(OutputStream out) {} + + public void setSystemOutput(String out) {} + + public void setSystemError(String err) {} + + private void tearDown() { + try { + // first try to load the class and let's hope it is on our + // classpath + Class testClass = null; + if (Thread.currentThread().getContextClassLoader() != null) { + try { + testClass = Thread.currentThread().getContextClassLoader() + .loadClass(suiteName); + } catch (ClassNotFoundException cnfe) { + } + } + if (testClass == null && getClass().getClassLoader() != null) { + try { + testClass = + getClass().getClassLoader().loadClass(suiteName); + } catch (ClassNotFoundException cnfe) { + } + } + if (testClass == null) { + // fall back to system classloader + testClass = Class.forName(suiteName); + } + + // if the test has a suite method, then we can't know + // which test of the executed suite timed out, ignore it + try { + // check if there is a suite method + testClass.getMethod("suite", new Class[0]); + return; + } catch (NoSuchMethodException e) { + // no suite method + } + + // a loadable class and no suite method + // no reason to check for JUnit 4 since JUnit4TestAdapter + // doesn't have any tearDown method. + + try { + Method td = testClass.getMethod("tearDown", new Class[0]); + if (td.getReturnType() == Void.TYPE) { + td.invoke(testClass.newInstance(), new Object[0]); + } + } catch (NoSuchMethodException nsme) { + // no tearDown, fine + } + + } catch (ClassNotFoundException cnfe) { + // class probably is not in our classpath, there is + // nothing we can do + } catch (InvocationTargetException ite) { + System.err.println("Caught an exception while trying to invoke" + + " tearDown: " + ite.getMessage()); + } catch (Throwable t) { + System.err.println("Caught an exception while trying to invoke" + + " tearDown: " + t.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrashTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrashTest.java new file mode 100644 index 000000000..f6125e754 --- /dev/null +++ b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrashTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import org.apache.tools.ant.BuildFileTest; + +public class TearDownOnVmCrashTest extends BuildFileTest { + + public void setUp() { + configureProject("src/etc/testcases/taskdefs/optional/junit/teardownlistener.xml"); + } + + public void testNoTeardown() { + expectPropertySet("testNoTeardown", "error"); + assertOutputNotContaining(null, "tearDown called on Timeout"); + } + + public void testTeardown() { + expectPropertySet("testTeardown", "error"); + assertOutputContaining("tearDown called on Timeout"); + } +} \ No newline at end of file diff --git a/src/tests/junit/org/example/junit/Timeout.java b/src/tests/junit/org/example/junit/Timeout.java new file mode 100644 index 000000000..e19d37197 --- /dev/null +++ b/src/tests/junit/org/example/junit/Timeout.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.example.junit; + +import junit.framework.TestCase; + +public class Timeout extends TestCase { + public void testTimeout() throws InterruptedException { + Thread.sleep(5000); + } + public void tearDown() { + System.out.println("tearDown called on Timeout"); + } +}