git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@689751 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -306,6 +306,12 @@ Other changes: | |||||
| * <echoxml> now supports XML namespaces. | * <echoxml> now supports XML namespaces. | ||||
| Bugzilla Report 36804. | Bugzilla Report 36804. | ||||
| * A new listener for <junit> 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 <junit> task's | |||||
| manual page for details. | |||||
| Bugzilla Report 37241. | |||||
| Changes from Ant 1.7.0 TO Ant 1.7.1 | Changes from Ant 1.7.0 TO Ant 1.7.1 | ||||
| ============================================= | ============================================= | ||||
| @@ -569,6 +569,46 @@ supported.</p> | |||||
| <p>Batchtests can define their own formatters via nested | <p>Batchtests can define their own formatters via nested | ||||
| <code><formatter></code> elements.</p> | <code><formatter></code> elements.</p> | ||||
| <h3>Forked tests and <code>tearDown</code></h3> | |||||
| <p>If a forked test runs into a timeout, Ant will terminate the Java | |||||
| VM process it has created, which probably means the | |||||
| test's <code>tearDown</code> method will never be called. The same | |||||
| is true if the forked VM crashes for some other reason.</p> | |||||
| <p>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' <code>tearDown</code> method. This formatter has | |||||
| the following limitations:</p> | |||||
| <ul> | |||||
| <li>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).</li> | |||||
| <li>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 <code>forkMode</code> other | |||||
| than <code>perTest</code> and it won't do anything if the test | |||||
| class contains a <code>suite()</code> method.</li> | |||||
| </ul> | |||||
| <p>If the formatter recognizes an incompatible <code>forkMode</code> | |||||
| or a <code>suite</code> method or fails to load the test class it | |||||
| will silently do nothing.</p> | |||||
| <p>The formatter doesn't have any effect on tests that were not | |||||
| forked or didn't cause timeouts or VM crashes.</p> | |||||
| <p>To enable the formatter, add a <code>formatter</code> like</p> | |||||
| <pre> | |||||
| <formatter classname="org.apache.tools.ant.taskdefs.optional.junit.TearDownOnVmCrash" | |||||
| usefile="false"/> | |||||
| </pre> | |||||
| <p>to your <code>junit</code> task.</p> | |||||
| <h3>Examples</h3> | <h3>Examples</h3> | ||||
| <pre> | <pre> | ||||
| @@ -0,0 +1,50 @@ | |||||
| <?xml version="1.0"?> | |||||
| <!-- | |||||
| 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. | |||||
| --> | |||||
| <project> | |||||
| <path id="test"> | |||||
| <pathelement path="${java.class.path}" /> | |||||
| <pathelement location="../../../../../../build/testcases" /> | |||||
| </path> | |||||
| <target name="testNoTeardown"> | |||||
| <junit haltonerror="false" errorproperty="error" fork="true" timeout="1000"> | |||||
| <formatter type="plain" usefile="false"/> | |||||
| <batchtest> | |||||
| <fileset dir="../../../../../../build/testcases"> | |||||
| <include name="org/example/junit/Timeout*"/> | |||||
| </fileset> | |||||
| </batchtest> | |||||
| <classpath refid="test"/> | |||||
| </junit> | |||||
| </target> | |||||
| <target name="testTeardown"> | |||||
| <junit haltonerror="false" errorproperty="error" fork="true" timeout="1000"> | |||||
| <formatter type="plain" usefile="false"/> | |||||
| <formatter classname="org.apache.tools.ant.taskdefs.optional.junit.TearDownOnVmCrash" | |||||
| usefile="false"/> | |||||
| <batchtest> | |||||
| <fileset dir="../../../../../../build/testcases"> | |||||
| <include name="org/example/junit/Timeout*"/> | |||||
| </fileset> | |||||
| </batchtest> | |||||
| <classpath refid="test"/> | |||||
| </junit> | |||||
| </target> | |||||
| </project> | |||||
| @@ -731,6 +731,7 @@ public class JUnitTask extends Task { | |||||
| "OutErrSummaryJUnitResultFormatter", | "OutErrSummaryJUnitResultFormatter", | ||||
| "PlainJUnitResultFormatter", | "PlainJUnitResultFormatter", | ||||
| "SummaryJUnitResultFormatter", | "SummaryJUnitResultFormatter", | ||||
| "TearDownOnVmCrash", | |||||
| "XMLJUnitResultFormatter", | "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 | * Take care that some output is produced in report files if the | ||||
| * watchdog kills the test. | * watchdog kills the test. | ||||
| * | * | ||||
| * @since Ant 1.5.2 | * @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 | * Creates a JUnitTest instance that shares all flags with the | ||||
| * passed in instance but has a more meaningful name. | * 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 | // make sure test looks as if it was in the same "package" as | ||||
| // the last test of the batch | // the last test of the batch | ||||
| String pack = index > 0 ? test.getName().substring(0, index + 1) : ""; | 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; | return t; | ||||
| } | } | ||||
| @@ -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. | |||||
| * | |||||
| * <p>This formatter has some limitations, for details see the | |||||
| * <junit> task's manual.</p> | |||||
| * | |||||
| * @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()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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"); | |||||
| } | |||||
| } | |||||
| @@ -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"); | |||||
| } | |||||
| } | |||||