diff --git a/WHATSNEW b/WHATSNEW
index 3d6f67791..00c9b383a 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -77,6 +77,8 @@ Changes that could break older environments:
Fixed bugs:
-----------
+*
JUnit4TestAdapter
.
Note: This task depends on external libraries not included in the Ant distribution. See Library Dependencies for more information. diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java index cc7d6039e..59419e63b 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -35,6 +35,7 @@ import java.util.StringTokenizer; import java.util.Vector; import junit.framework.AssertionFailedError; import junit.framework.Test; +import junit.framework.TestFailure; import junit.framework.TestListener; import junit.framework.TestResult; import junit.framework.TestSuite; @@ -99,7 +100,13 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR "junit.textui.TestRunner", "java.lang.reflect.Method.invoke(", "sun.reflect.", - "org.apache.tools.ant." + "org.apache.tools.ant.", + // JUnit 4 support: + "org.junit.", + "junit.framework.JUnit4TestAdapter", + // See wrapListener for reason: + "Caused by: java.lang.AssertionError", + " more", }; @@ -141,6 +148,9 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR /** Do we print TestListener events? */ private boolean logTestListenerEvents = false; + /** Turned on if we are using JUnit 4 for this test suite. see #38811 */ + private boolean junit4; + /** * Constructor for fork=true or when the user hasn't specified a * classpath. @@ -212,9 +222,9 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR public void run() { res = new TestResult(); - res.addListener(this); + res.addListener(wrapListener(this)); for (int i = 0; i < formatters.size(); i++) { - res.addListener((TestListener) formatters.elementAt(i)); + res.addListener(wrapListener((TestListener) formatters.elementAt(i))); } ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); @@ -255,6 +265,19 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR try { try { + Class junit4TestAdapterClass = null; + // Note that checking for JDK 5 directly won't work; under JDK 4, this will already have failed. + try { + if (loader == null) { + junit4TestAdapterClass = Class.forName("junit.framework.JUnit4TestAdapter"); + } else { + junit4TestAdapterClass = Class.forName("junit.framework.JUnit4TestAdapter", true, loader); + } + } catch (ClassNotFoundException e) { + // OK, fall back to JUnit 3. + } + junit4 = junit4TestAdapterClass != null; + Class testClass = null; if (loader == null) { testClass = Class.forName(junitTest.getName()); @@ -263,24 +286,33 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR loader); } - Method suiteMethod = null; - try { - // check if there is a suite method - suiteMethod = testClass.getMethod("suite", new Class[0]); - } catch (NoSuchMethodException e) { - // no appropriate suite method found. We don't report any - // error here since it might be perfectly normal. - } - if (suiteMethod != null) { - // if there is a suite method available, then try - // to extract the suite from it. If there is an error - // here it will be caught below and reported. - suite = (Test) suiteMethod.invoke(null, new Class[0]); + if (junit4) { + // Let's use it! + suite = (Test) junit4TestAdapterClass.getConstructor(new Class[] {Class.class}). + newInstance(new Object[] {testClass}); } else { - // try to extract a test suite automatically this - // will generate warnings if the class is no - // suitable Test - suite = new TestSuite(testClass); + // Use JUnit 3. + + Method suiteMethod = null; + try { + // check if there is a suite method + suiteMethod = testClass.getMethod("suite", new Class[0]); + } catch (NoSuchMethodException e) { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. + } + if (suiteMethod != null) { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = (Test) suiteMethod.invoke(null, new Class[0]); + } else { + // try to extract a test suite automatically this + // will generate warnings if the class is no + // suitable Test + suite = new TestSuite(testClass); + } + } } catch (Throwable e) { @@ -303,8 +335,13 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR logTestListenerEvent("tests to run: " + suite.countTestCases()); suite.run(res); } finally { - junitTest.setCounts(res.runCount(), res.failureCount(), - res.errorCount()); + if (junit4) { + int[] cnts = findJUnit4FailureErrorCount(res); + junitTest.setCounts(res.runCount(), cnts[0], cnts[1]); + } else { + junitTest.setCounts(res.runCount(), res.failureCount(), + res.errorCount()); + } junitTest.setRunTime(System.currentTimeMillis() - start); } } @@ -692,7 +729,7 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR private static boolean filterLine(String line) { for (int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++) { - if (line.indexOf(DEFAULT_TRACE_FILTERS[i]) > 0) { + if (line.indexOf(DEFAULT_TRACE_FILTERS[i]) != -1) { return true; } } @@ -733,4 +770,83 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR } } } + + /** + * Modifies a TestListener when running JUnit 4: + * treats AssertionFailedError as a failure not an error. + * @since Ant 1.7 + */ + private TestListener wrapListener(final TestListener testListener) { + return new TestListener() { + public void addError(Test test, Throwable t) { + if (junit4 && t instanceof AssertionFailedError) { + // JUnit 4 does not distinguish between errors and failures even in the JUnit 3 adapter. + // So we need to help it a bit to retain compatibility for JUnit 3 tests. + testListener.addFailure(test, (AssertionFailedError) t); + } else if (junit4 && t.getClass().getName().equals("java.lang.AssertionError")) { + // Not strictly necessary but probably desirable. + // JUnit 4-specific test GUIs will show just "failures". + // But Ant's output shows "failures" vs. "errors". + // We would prefer to show "failure" for things that logically are. + try { + String msg = t.getMessage(); + AssertionFailedError failure = msg != null ? + new AssertionFailedError(msg) : new AssertionFailedError(); + // To compile on pre-JDK 4 (even though this should always succeed): + Method initCause = Throwable.class.getMethod("initCause", new Class[] {Throwable.class}); + initCause.invoke(failure, new Object[] {t}); + testListener.addFailure(test, failure); + } catch (Exception e) { + // Rats. + e.printStackTrace(); // should not happen + testListener.addError(test, t); + } + } else { + testListener.addError(test, t); + } + } + public void addFailure(Test test, AssertionFailedError t) { + testListener.addFailure(test, t); + } + public void addFailure(Test test, Throwable t) { // pre-3.4 + if (t instanceof AssertionFailedError) { + testListener.addFailure(test, (AssertionFailedError) t); + } else { + testListener.addError(test, t); + } + } + public void endTest(Test test) { + testListener.endTest(test); + } + public void startTest(Test test) { + testListener.startTest(test); + } + }; + } + + /** + * Use instead of TestResult.get{Failure,Error}Count on JUnit 4, + * since the adapter claims that all failures are errors. + * @since Ant 1.7 + */ + private int[] findJUnit4FailureErrorCount(TestResult res) { + int failures = 0; + int errors = 0; + Enumeration e = res.failures(); + while (e.hasMoreElements()) { + e.nextElement(); + failures++; + } + e = res.errors(); + while (e.hasMoreElements()) { + Throwable t = ((TestFailure) e.nextElement()).thrownException(); + if (t instanceof AssertionFailedError || t.getClass().getName().equals("java.lang.AssertionError")) { + failures++; + } else { + errors++; + } + } + return new int[] {failures, errors}; + } + } // JUnitTestRunner diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java index 4ee36c6b8..0f63bd271 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2001-2002,2004-2005 The Apache Software Foundation + * Copyright 2001-2002,2004-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,8 +51,21 @@ public class JUnitVersionHelper { *
since Ant 1.5.1 this method will invoke "public
* String getName()
" on any implementation of Test if
* it exists.
Since Ant 1.7 also checks for JUnit4TestCaseFacade explicitly. + * This is used by junit.framework.JUnit4TestAdapter.
*/ public static String getTestCaseName(Test t) { + if (t != null && t.getClass().getName().equals("junit.framework.JUnit4TestCaseFacade")) { + // Self-describing as of JUnit 4 (#38811). But trim "(ClassName)". + String name = t.toString(); + if (name.endsWith(")")) { + int paren = name.lastIndexOf('('); + return name.substring(0, paren); + } else { + return name; + } + } if (t instanceof TestCase && testCaseName != null) { try { return (String) testCaseName.invoke(t, new Object[0]);