git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@953761 13f79535-47bb-0310-9956-ffa450edef68master
@@ -61,6 +61,9 @@ Other changes: | |||
overwrite attribute that is consistent with <copy>'s attribute | |||
names. | |||
* You can now specify a list of methods to run in a JUnit test case. | |||
Bugzilla Report 34748. | |||
Changes from Ant 1.8.0 TO Ant 1.8.1 | |||
=================================== | |||
@@ -350,7 +350,7 @@ | |||
classname="org.apache.xalan.trace.TraceListenerEx3" | |||
classpathref="classpath" ignoresystemclasses="true"/> | |||
<available property="junit.present" | |||
classname="junit.framework.TestCase" | |||
classname="org.junit.Test" | |||
classpathref="classpath" ignoresystemclasses="true"/> | |||
<available property="antunit.present" | |||
classname="org.apache.ant.antunit.AntUnit" | |||
@@ -430,6 +430,26 @@ the name of the resulting class (without suffix). It defaults to <i>java-tmp-dir | |||
<td valign="top">Name of the test class.</td> | |||
<td align="center">Yes</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">methods</td> | |||
<td valign="top">Comma-separated list of names of test case methods to execute. | |||
<em>Since 1.8.2</em> | |||
<p>The <code>methods</code> attribute can be useful in the following scenarios:</p> | |||
<ul> | |||
<li>A test method has failed and you want to re-run the test method | |||
to test a fix or re-run the test under the Java debugger without | |||
having to wait for the other (possibly long running) test methods | |||
to complete.</li> | |||
<li>One or more test methods are running slower than expected and you | |||
want to re-run them under a Java profiler (without the overhead | |||
of running the profiler whilst other test methods are being | |||
executed).</li> | |||
</ul> | |||
<p>If the <code>methods</code> attribute is used but no test method | |||
is specified, then no test method from the suite will be executed.</p> | |||
</td> | |||
<td align="center">No; default is to run all test methods in the suite.</td> | |||
</tr> | |||
<tr> | |||
<td valign="top">fork</td> | |||
<td valign="top">Run the tests in a separate VM. | |||
@@ -45,7 +45,7 @@ jasper-compiler.version=4.1.36 | |||
jasper-runtime.version=${jasper-compiler.version} | |||
jdepend.version=2.9.1 | |||
jruby.version=0.9.8 | |||
junit.version=3.8.2 | |||
junit.version=4.8.1 | |||
jsch.version=0.1.42 | |||
jython.version=2.1 | |||
#log4j 1.2.15 requires JMS and a few other Sun jars that are not in the m2 repo | |||
@@ -1,3 +1,3 @@ | |||
The file junit-3.8.2.jar is version 3.8.2 of JUnit, see the file LICENSE.junit.html | |||
The file junit-4.8.1.jar is version 4.8.1 of JUnit, see the file LICENSE.junit.html | |||
for the terms of distribution. For more information about JUnit or | |||
the latest release, see <http://www.junit.org/>. |
@@ -23,6 +23,7 @@ package org.apache.tools.ant.taskdefs.optional.junit; | |||
*/ | |||
public class Constants { | |||
static final String METHOD_NAMES = "methods="; | |||
static final String HALT_ON_ERROR = "haltOnError="; | |||
static final String HALT_ON_FAILURE = "haltOnFailure="; | |||
static final String FILTERTRACE = "filtertrace="; | |||
@@ -0,0 +1,227 @@ | |||
/* | |||
* 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.util.Iterator; | |||
import java.util.List; | |||
import junit.framework.JUnit4TestAdapterCache; | |||
import junit.framework.Test; | |||
import junit.framework.TestResult; | |||
import org.junit.runner.Description; | |||
import org.junit.runner.Request; | |||
import org.junit.runner.Runner; | |||
import org.junit.runner.manipulation.Filter; | |||
import org.junit.runner.notification.Failure; | |||
import org.junit.runner.notification.RunListener; | |||
import org.junit.runner.notification.RunNotifier; | |||
/** | |||
* Adapter between JUnit 3.8.x API and JUnit 4.x API for execution of tests | |||
* and listening of events (test start, test finish, test failure). | |||
* The constructor is passed a JUnit 4 test class and a list of name of methods | |||
* in it that should be executed. Method {@link #run run(TestResult)} executes | |||
* the given JUnit-4-style test methods and notifies the given {@code TestResult} | |||
* object using its old (JUnit 3.8.x style) API. | |||
* | |||
* @author Marian Petras | |||
*/ | |||
public class JUnit4TestMethodAdapter implements Test { | |||
private final Class testClass; | |||
private final String[] methodNames; | |||
private final Runner runner; | |||
private final Cache cache; | |||
/** | |||
* Creates a new adapter for the given class and a method within the class. | |||
* | |||
* @param testClass test class containing the method to be executed | |||
* @param methodNames names of the test methods that are to be executed | |||
* @exception java.lang.IllegalArgumentException | |||
* if any of the arguments is {@code null} | |||
* or if any of the given method names is {@code null} or empty | |||
*/ | |||
public JUnit4TestMethodAdapter(final Class testClass, | |||
final String[] methodNames) { | |||
if (testClass == null) { | |||
throw new IllegalArgumentException("testClass is <null>"); | |||
} | |||
if (methodNames == null) { | |||
throw new IllegalArgumentException("methodNames is <null>"); | |||
} | |||
for (int i = 0; i < methodNames.length; i++) { | |||
if (methodNames[i] == null) { | |||
throw new IllegalArgumentException("method name #" + i + " is <null>"); | |||
} | |||
if (methodNames[i].length() == 0) { | |||
throw new IllegalArgumentException("method name #" + i + " is empty"); | |||
} | |||
} | |||
this.testClass = testClass; | |||
this.methodNames = methodNames; | |||
this.cache = Cache.instance; | |||
// Warning: If 'testClass' is an old-style (pre-JUnit-4) class, | |||
// then all its test methods will be executed by the returned runner! | |||
Request request; | |||
if (methodNames.length == 1) { | |||
request = Request.method(testClass, methodNames[0]); | |||
} else { | |||
request = Request.aClass(testClass).filterWith( | |||
new MultipleMethodsFilter(testClass, methodNames)); | |||
} | |||
runner = request.getRunner(); | |||
} | |||
public int countTestCases() { | |||
return runner.testCount(); | |||
} | |||
public Description getDescription() { | |||
return runner.getDescription(); | |||
} | |||
public List/*<Test>*/ getTests() { | |||
return cache.asTestList(getDescription()); | |||
} | |||
public Class getTestClass() { | |||
return testClass; | |||
} | |||
public void run(final TestResult result) { | |||
runner.run(cache.getNotifier(result)); | |||
} | |||
public String toString() { | |||
String testClassName = testClass.getName(); | |||
StringBuilder buf = new StringBuilder(testClassName.length() | |||
+ 12 * methodNames.length) | |||
.append(':'); | |||
if (methodNames.length != 0) { | |||
buf.append(methodNames[0]); | |||
for (int i = 1; i < methodNames.length; i++) { | |||
buf.append(',') | |||
.append(methodNames[i]); | |||
} | |||
} | |||
return buf.toString(); | |||
} | |||
private static final class MultipleMethodsFilter extends Filter { | |||
private final Description methodsListDescription; | |||
private final Class testClass; | |||
private final String[] methodNames; | |||
private MultipleMethodsFilter(Class testClass, String[] methodNames) { | |||
if (testClass == null) { | |||
throw new IllegalArgumentException("testClass is <null>"); | |||
} | |||
if (methodNames == null) { | |||
throw new IllegalArgumentException("methodNames is <null>"); | |||
} | |||
methodsListDescription = Description.createSuiteDescription(testClass); | |||
for (int i = 0; i < methodNames.length; i++) { | |||
methodsListDescription.addChild( | |||
Description.createTestDescription(testClass, methodNames[i])); | |||
} | |||
this.testClass = testClass; | |||
this.methodNames = methodNames; | |||
} | |||
public boolean shouldRun(Description description) { | |||
if (methodNames.length == 0) { | |||
return false; | |||
} | |||
if (description.isTest()) { | |||
Iterator/*<Description>*/ it = methodsListDescription.getChildren().iterator(); | |||
while (it.hasNext()) { | |||
Description methodDescription = (Description) it.next(); | |||
if (methodDescription.equals(description)) { | |||
return true; | |||
} | |||
} | |||
} else { | |||
Iterator/*<Description>*/ it = description.getChildren().iterator(); | |||
while (it.hasNext()) { | |||
Description each = (Description) it.next(); | |||
if (shouldRun(each)) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
public String describe() { | |||
StringBuilder buf = new StringBuilder(40); | |||
if (methodNames.length == 0) { | |||
buf.append("No methods"); | |||
} else { | |||
buf.append(methodNames.length == 1 ? "Method" : "Methods"); | |||
buf.append(' '); | |||
buf.append(methodNames[0]); | |||
for (int i = 1; i < methodNames.length; i++) { | |||
buf.append(',').append(methodNames[i]); | |||
} | |||
} | |||
buf.append('(').append(testClass.getName()).append(')'); | |||
return buf.toString(); | |||
} | |||
} | |||
/** | |||
* Effectively a copy of {@code JUnit4TestAdapterCache}, except that its | |||
* method {@code getNotifier()} does not require an argument | |||
* of type {@code JUnit4TestAdapter}. | |||
*/ | |||
private static final class Cache extends JUnit4TestAdapterCache { | |||
private static final Cache instance = new Cache(); | |||
public static JUnit4TestAdapterCache getDefault() { | |||
return instance; | |||
} | |||
public RunNotifier getNotifier(final TestResult result) { | |||
RunNotifier notifier = new RunNotifier(); | |||
notifier.addListener(new RunListener() { | |||
public void testFailure(Failure failure) throws Exception { | |||
result.addError(asTest(failure.getDescription()), | |||
failure.getException()); | |||
} | |||
public void testFinished(Description description) | |||
throws Exception { | |||
result.endTest(asTest(description)); | |||
} | |||
public void testStarted(Description description) | |||
throws Exception { | |||
result.startTest(asTest(description)); | |||
} | |||
}); | |||
return notifier; | |||
} | |||
} | |||
} |
@@ -728,6 +728,7 @@ public class JUnitTask extends Task { | |||
new SplitClassLoader(myLoader, path, getProject(), | |||
new String[] { | |||
"BriefJUnitResultFormatter", | |||
"JUnit4TestMethodAdapter", | |||
"JUnitResultFormatter", | |||
"JUnitTaskMirrorImpl", | |||
"JUnitTestRunner", | |||
@@ -751,6 +752,8 @@ public class JUnitTask extends Task { | |||
* @since Ant 1.2 | |||
*/ | |||
public void execute() throws BuildException { | |||
checkMethodLists(); | |||
setupJUnitDelegate(); | |||
List testLists = new ArrayList(); | |||
@@ -851,6 +854,9 @@ public class JUnitTask extends Task { | |||
while (iter.hasNext()) { | |||
test = (JUnitTest) iter.next(); | |||
printDual(writer, logWriter, test.getName()); | |||
if (test.getMethods() != null) { | |||
printDual(writer, logWriter, ":" + test.getMethodsString().replace(',', '+')); | |||
} | |||
if (test.getTodir() == null) { | |||
printDual(writer, logWriter, | |||
"," + getProject().resolveFile(".")); | |||
@@ -922,6 +928,9 @@ public class JUnitTask extends Task { | |||
cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); | |||
if (casesFile == null) { | |||
cmd.createArgument().setValue(test.getName()); | |||
if (test.getMethods() != null) { | |||
cmd.createArgument().setValue(Constants.METHOD_NAMES + test.getMethodsString()); | |||
} | |||
} else { | |||
log("Running multiple tests in the same VM", Project.MSG_VERBOSE); | |||
cmd.createArgument().setValue(Constants.TESTSFILE + casesFile); | |||
@@ -1322,7 +1331,7 @@ public class JUnitTask extends Task { | |||
if (classLoader != null) { | |||
classLoader.setThreadContextLoader(); | |||
} | |||
runner = delegate.newJUnitTestRunner(test, test.getHaltonerror(), | |||
runner = delegate.newJUnitTestRunner(test, test.getMethods(), test.getHaltonerror(), | |||
test.getFiltertrace(), | |||
test.getHaltonfailure(), false, | |||
true, classLoader); | |||
@@ -1407,6 +1416,29 @@ public class JUnitTask extends Task { | |||
return Enumerations.fromCompound(enums); | |||
} | |||
/** | |||
* Verifies all <code>test</code> elements having the <code>methods</code> | |||
* attribute specified and having the <code>if</code>-condition resolved | |||
* to true, that the value of the <code>methods</code> attribute is valid. | |||
* @exception BuildException if some of the tests matching the described | |||
* conditions has invalid value of the | |||
* <code>methods</code> attribute | |||
* @since 1.8.2 | |||
*/ | |||
private void checkMethodLists() throws BuildException { | |||
if (tests.isEmpty()) { | |||
return; | |||
} | |||
Enumeration testsEnum = tests.elements(); | |||
while (testsEnum.hasMoreElements()) { | |||
JUnitTest test = (JUnitTest) testsEnum.nextElement(); | |||
if (test.hasMethodsSpecified() && test.shouldRun(getProject())) { | |||
test.resolveMethods(); | |||
} | |||
} | |||
} | |||
/** | |||
* return an enumeration listing each test, then each batchtest | |||
* @return enumeration | |||
@@ -55,6 +55,7 @@ public interface JUnitTaskMirror { | |||
/** | |||
* Create a new test runner for a test. | |||
* @param test the test to run. | |||
* @param methods names of the test methods to be run. | |||
* @param haltOnError if true halt the tests if an error occurs. | |||
* @param filterTrace if true filter the stack traces. | |||
* @param haltOnFailure if true halt the test if a failure occurs. | |||
@@ -63,7 +64,7 @@ public interface JUnitTaskMirror { | |||
* @param classLoader the classloader to use to create the runner. | |||
* @return the test runner. | |||
*/ | |||
JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, boolean haltOnError, | |||
JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError, | |||
boolean filterTrace, boolean haltOnFailure, boolean showOutput, | |||
boolean logTestListenerEvents, AntClassLoader classLoader); | |||
@@ -60,9 +60,10 @@ public final class JUnitTaskMirrorImpl implements JUnitTaskMirror { | |||
/** {@inheritDoc}. */ | |||
public JUnitTaskMirror.JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, | |||
String[] methods, | |||
boolean haltOnError, boolean filterTrace, boolean haltOnFailure, | |||
boolean showOutput, boolean logTestListenerEvents, AntClassLoader classLoader) { | |||
return new JUnitTestRunner(test, haltOnError, filterTrace, haltOnFailure, | |||
return new JUnitTestRunner(test, methods, haltOnError, filterTrace, haltOnFailure, | |||
showOutput, logTestListenerEvents, classLoader); | |||
} | |||
@@ -22,6 +22,7 @@ import java.util.Enumeration; | |||
import java.util.Hashtable; | |||
import java.util.Properties; | |||
import java.util.Vector; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.PropertyHelper; | |||
@@ -41,6 +42,19 @@ public class JUnitTest extends BaseTest implements Cloneable { | |||
/** the name of the test case */ | |||
private String name = null; | |||
/** | |||
* whether the list of test methods has been specified | |||
* @see #setMethods(java.lang.String) | |||
* @see #setMethods(java.lang.String[]) | |||
*/ | |||
private boolean methodsSpecified = false; | |||
/** comma-separated list of names of test methods to execute */ | |||
private String methodsList = null; | |||
/** the names of test methods to execute */ | |||
private String[] methods = null; | |||
/** the name of the result file */ | |||
private String outfile = null; | |||
@@ -73,11 +87,53 @@ public class JUnitTest extends BaseTest implements Cloneable { | |||
* @param filtertrace if true filter stack traces. | |||
*/ | |||
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, | |||
boolean filtertrace) { | |||
boolean filtertrace) { | |||
this(name, haltOnError, haltOnFailure, filtertrace, null); | |||
} | |||
/** | |||
* Constructor with options. | |||
* @param name the name of the test. | |||
* @param haltOnError if true halt the tests if there is an error. | |||
* @param haltOnFailure if true halt the tests if there is a failure. | |||
* @param filtertrace if true filter stack traces. | |||
* @param methods if true run only test methods that failed during the | |||
* previous run of the test suite | |||
* @since 1.8.2 | |||
*/ | |||
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, | |||
boolean filtertrace, String[] methods) { | |||
this.name = name; | |||
this.haltOnError = haltOnError; | |||
this.haltOnFail = haltOnFailure; | |||
this.filtertrace = filtertrace; | |||
this.methods = methods; | |||
this.methodsSpecified = (methods != null); | |||
} | |||
/** | |||
* Sets names of individual test methods to be executed. | |||
* @param value comma-separated list of names of individual test methods | |||
* to be executed, | |||
* or <code>null</code> if all test methods should be executed | |||
* @since 1.8.2 | |||
*/ | |||
public void setMethods(String value) { | |||
methodsList = value; | |||
methodsSpecified = (value != null); | |||
methods = null; | |||
} | |||
/** | |||
* Sets names of individual test methods to be executed. | |||
* @param value non-empty array of names of test methods to be executed | |||
* @see #setMethods(String) | |||
* @since 1.8.2 | |||
*/ | |||
void setMethods(String[] value) { | |||
methods = value; | |||
methodsSpecified = (value != null); | |||
methodsList = null; | |||
} | |||
/** | |||
@@ -96,6 +152,189 @@ public class JUnitTest extends BaseTest implements Cloneable { | |||
outfile = value; | |||
} | |||
/** | |||
* Informs whether a list of test methods has been specified in this test. | |||
* @return <code>true</code> if test methods to be executed have been | |||
* specified, <code>false</code> otherwise | |||
* @see #setMethods(java.lang.String) | |||
* @see #setMethods(java.lang.String[]) | |||
* @since 1.8.2 | |||
*/ | |||
boolean hasMethodsSpecified() { | |||
return methodsSpecified; | |||
} | |||
/** | |||
* Get names of individual test methods to be executed. | |||
* | |||
* @return array of names of the individual test methods to be executed, | |||
* or <code>null</code> if all test methods in the suite | |||
* defined by the test class will be executed | |||
* @since 1.8.2 | |||
*/ | |||
String[] getMethods() { | |||
if (methodsSpecified && (methods == null)) { | |||
resolveMethods(); | |||
} | |||
return methods; | |||
} | |||
/** | |||
* Gets a comma-separated list of names of methods that are to be executed | |||
* by this test. | |||
* @return the comma-separated list of test method names, or an empty | |||
* string of no method is to be executed, or <code>null</code> | |||
* if no method is specified | |||
* @since 1.8.2 | |||
*/ | |||
String getMethodsString() { | |||
if ((methodsList == null) && methodsSpecified) { | |||
if (methods.length == 0) { | |||
methodsList = ""; | |||
} else if (methods.length == 1) { | |||
methodsList = methods[0]; | |||
} else { | |||
StringBuffer buf = new StringBuffer(methods.length * 16); | |||
buf.append(methods[0]); | |||
for (int i = 1; i < methods.length; i++) { | |||
buf.append(',').append(methods[i]); | |||
} | |||
methodsList = buf.toString(); | |||
} | |||
} | |||
return methodsList; | |||
} | |||
/** | |||
* Computes the value of the {@link #methods} field from the value | |||
* of the {@link #methodsList} field, if it has not been computed yet. | |||
* @exception BuildException if the value of the {@link #methodsList} field | |||
* was invalid | |||
* @since 1.8.2 | |||
*/ | |||
void resolveMethods() { | |||
if ((methods == null) && methodsSpecified) { | |||
try { | |||
methods = parseTestMethodNamesList(methodsList); | |||
} catch (IllegalArgumentException ex) { | |||
throw new BuildException( | |||
"Invalid specification of test methods: \"" | |||
+ methodsList | |||
+ "\"; expected: comma-separated list of valid Java identifiers", | |||
ex); | |||
} | |||
} | |||
} | |||
/** | |||
* Parses a comma-separated list of method names and check their validity. | |||
* @param methodNames comma-separated list of method names to be parsed | |||
* @return array of individual test method names | |||
* @exception java.lang.IllegalArgumentException | |||
* if the given string is <code>null</code> or if it is not | |||
* a comma-separated list of valid Java identifiers; | |||
* an empty string is acceptable and is handled as an empty | |||
* list | |||
* @since 1.8.2 | |||
*/ | |||
public static String[] parseTestMethodNamesList(String methodNames) | |||
throws IllegalArgumentException { | |||
if (methodNames == null) { | |||
throw new IllegalArgumentException("methodNames is <null>"); | |||
} | |||
methodNames = methodNames.trim(); | |||
int length = methodNames.length(); | |||
if (length == 0) { | |||
return new String[0]; | |||
} | |||
/* strip the trailing comma, if any */ | |||
if (methodNames.charAt(length - 1) == ',') { | |||
methodNames = methodNames.substring(0, length - 1).trim(); | |||
length = methodNames.length(); | |||
if (length == 0) { | |||
throw new IllegalArgumentException("Empty method name"); | |||
} | |||
} | |||
final char[] chars = methodNames.toCharArray(); | |||
/* easy detection of one particular case of illegal string: */ | |||
if (chars[0] == ',') { | |||
throw new IllegalArgumentException("Empty method name"); | |||
} | |||
/* count number of method names: */ | |||
int wordCount = 1; | |||
for (int i = 1; i < chars.length; i++) { | |||
if (chars[i] == ',') { | |||
wordCount++; | |||
} | |||
} | |||
/* prepare the resulting array: */ | |||
String[] result = new String[wordCount]; | |||
/* parse the string: */ | |||
final int stateBeforeWord = 1; | |||
final int stateInsideWord = 2; | |||
final int stateAfterWord = 3; | |||
// | |||
int state = stateBeforeWord; | |||
int wordStartIndex = -1; | |||
int wordIndex = 0; | |||
for (int i = 0; i < chars.length; i++) { | |||
char c = chars[i]; | |||
switch (state) { | |||
case stateBeforeWord: | |||
if (c == ',') { | |||
throw new IllegalArgumentException("Empty method name"); | |||
} else if (c == ' ') { | |||
// remain in the same state | |||
} else if (Character.isJavaIdentifierStart(c)) { | |||
wordStartIndex = i; | |||
state = stateInsideWord; | |||
} else { | |||
throw new IllegalArgumentException("Illegal start of method name: " + c); | |||
} | |||
break; | |||
case stateInsideWord: | |||
if (c == ',') { | |||
result[wordIndex++] = new String(methodNames.substring(wordStartIndex, i)); | |||
state = stateBeforeWord; | |||
} else if (c == ' ') { | |||
result[wordIndex++] = new String(methodNames.substring(wordStartIndex, i)); | |||
state = stateAfterWord; | |||
} else if (Character.isJavaIdentifierPart(c)) { | |||
// remain in the same state | |||
} else { | |||
throw new IllegalArgumentException("Illegal character in method name: " + c); | |||
} | |||
break; | |||
case stateAfterWord: | |||
if (c == ',') { | |||
state = stateBeforeWord; | |||
} else if (c == ' ') { | |||
// remain in the same state | |||
} else { | |||
throw new IllegalArgumentException("Space in method name"); | |||
} | |||
break; | |||
default: | |||
// this should never happen | |||
} | |||
} | |||
switch (state) { | |||
case stateBeforeWord: | |||
case stateAfterWord: | |||
break; | |||
case stateInsideWord: | |||
result[wordIndex++] = new String(methodNames.substring(wordStartIndex, chars.length)); | |||
break; | |||
default: | |||
// this should never happen | |||
} | |||
return result; | |||
} | |||
/** | |||
* Get the name of the test class. | |||
* @return the name of the test. | |||
@@ -163,6 +163,9 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
*/ | |||
private static String crashFile = null; | |||
/** Names of test methods to execute */ | |||
private String[] methods = null; | |||
/** | |||
* Constructor for fork=true or when the user hasn't specified a | |||
* classpath. | |||
@@ -205,7 +208,26 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
public JUnitTestRunner(JUnitTest test, boolean haltOnError, | |||
boolean filtertrace, boolean haltOnFailure, | |||
boolean showOutput, boolean logTestListenerEvents) { | |||
this(test, haltOnError, filtertrace, haltOnFailure, showOutput, | |||
this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, | |||
logTestListenerEvents, null); | |||
} | |||
/** | |||
* Constructor for fork=true or when the user hasn't specified a | |||
* classpath. | |||
* @param test the test to run. | |||
* @param methods names of methods of the test to be executed. | |||
* @param haltOnError whether to stop the run if an error is found. | |||
* @param filtertrace whether to filter junit.*.* stack frames out of exceptions | |||
* @param haltOnFailure whether to stop the run if failure is found. | |||
* @param showOutput whether to send output to System.out/.err as well as formatters. | |||
* @param logTestListenerEvents whether to print TestListener events. | |||
* @since 1.8.2 | |||
*/ | |||
public JUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError, | |||
boolean filtertrace, boolean haltOnFailure, | |||
boolean showOutput, boolean logTestListenerEvents) { | |||
this(test, methods, haltOnError, filtertrace, haltOnFailure, showOutput, | |||
logTestListenerEvents, null); | |||
} | |||
@@ -254,12 +276,26 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
boolean filtertrace, boolean haltOnFailure, | |||
boolean showOutput, boolean logTestListenerEvents, | |||
ClassLoader loader) { | |||
this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, | |||
logTestListenerEvents, loader); | |||
} | |||
/** | |||
* Constructor to use when the user has specified a classpath. | |||
* @since 1.8.2 | |||
*/ | |||
public JUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError, | |||
boolean filtertrace, boolean haltOnFailure, | |||
boolean showOutput, boolean logTestListenerEvents, | |||
ClassLoader loader) { | |||
JUnitTestRunner.filtertrace = filtertrace; | |||
this.junitTest = test; | |||
this.haltOnError = haltOnError; | |||
this.haltOnFailure = haltOnFailure; | |||
this.showOutput = showOutput; | |||
this.logTestListenerEvents = logTestListenerEvents; | |||
this.methods = methods; | |||
this.loader = loader; | |||
} | |||
@@ -340,9 +376,12 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
loader); | |||
} | |||
final boolean testMethodsSpecified = (methods != null); | |||
// check for a static suite method first, even when using | |||
// JUnit 4 | |||
Method suiteMethod = null; | |||
if (!testMethodsSpecified) { | |||
try { | |||
// check if there is a suite method | |||
suiteMethod = testClass.getMethod("suite", new Class[0]); | |||
@@ -350,6 +389,7 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
// 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 | |||
@@ -359,7 +399,23 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
} else { | |||
Class junit4TestAdapterClass = null; | |||
boolean useSingleMethodAdapter = false; | |||
if (junit.framework.TestCase.class.isAssignableFrom(testClass)) { | |||
// Do not use JUnit 4 API for running JUnit 3.x | |||
// tests - it is not able to run individual test | |||
// methods. | |||
// | |||
// Technical details: | |||
// org.junit.runner.Request.method(Class, String).getRunner() | |||
// would return a runner which always executes all | |||
// test methods. The reason is that the Runner would be | |||
// an instance of class | |||
// org.junit.internal.runners.OldTestClassRunner | |||
// that does not implement interface Filterable - so it | |||
// is unable to filter out test methods not matching | |||
// the requested name. | |||
} else { | |||
// Check for JDK 5 first. Will *not* help on JDK 1.4 | |||
// if only junit-4.0.jar in CP because in that case | |||
// linkage of whole task will already have failed! But | |||
@@ -373,29 +429,69 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
if (loader == null) { | |||
junit4TestAdapterClass = | |||
Class.forName(JUNIT_4_TEST_ADAPTER); | |||
if (testMethodsSpecified) { | |||
/* | |||
* We cannot try to load the JUnit4TestAdapter | |||
* before trying to load JUnit4TestMethodAdapter | |||
* because it might fail with | |||
* NoClassDefFoundException, instead of plain | |||
* ClassNotFoundException. | |||
*/ | |||
junit4TestAdapterClass = Class.forName( | |||
"org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter"); | |||
useSingleMethodAdapter = true; | |||
} | |||
} else { | |||
junit4TestAdapterClass = | |||
Class.forName(JUNIT_4_TEST_ADAPTER, | |||
true, loader); | |||
if (testMethodsSpecified) { | |||
junit4TestAdapterClass = | |||
Class.forName( | |||
"org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter", | |||
true, loader); | |||
useSingleMethodAdapter = true; | |||
} | |||
} | |||
} catch (ClassNotFoundException e) { | |||
// OK, fall back to JUnit 3. | |||
} | |||
} | |||
junit4 = junit4TestAdapterClass != null; | |||
if (junit4) { | |||
// Let's use it! | |||
Class[] formalParams; | |||
Object[] actualParams; | |||
if (useSingleMethodAdapter) { | |||
formalParams = new Class[] {Class.class, String[].class}; | |||
actualParams = new Object[] {testClass, methods}; | |||
} else { | |||
formalParams = new Class[] {Class.class}; | |||
actualParams = new Object[] {testClass}; | |||
} | |||
suite = | |||
(Test) junit4TestAdapterClass | |||
.getConstructor(new Class[] {Class.class}). | |||
newInstance(new Object[] {testClass}); | |||
.getConstructor(formalParams). | |||
newInstance(actualParams); | |||
} else { | |||
// Use JUnit 3. | |||
// try to extract a test suite automatically this | |||
// will generate warnings if the class is no | |||
// suitable Test | |||
suite = new TestSuite(testClass); | |||
if (!testMethodsSpecified) { | |||
suite = new TestSuite(testClass); | |||
} else if (methods.length == 1) { | |||
suite = TestSuite.createTest(testClass, methods[0]); | |||
} else { | |||
TestSuite testSuite = new TestSuite(testClass.getName()); | |||
for (int i = 0; i < methods.length; i++) { | |||
testSuite.addTest( | |||
TestSuite.createTest(testClass, methods[i])); | |||
} | |||
suite = testSuite; | |||
} | |||
} | |||
} | |||
@@ -670,11 +766,16 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
* <tr><td>logtestlistenerevents</td><td>log TestListener events to | |||
* System.out.</td><td>false</td></tr> | |||
* | |||
* <tr><td>methods</td><td>Comma-separated list of names of individual | |||
* test methods to execute. | |||
* </td><td>null</td></tr> | |||
* | |||
* </table> | |||
* @param args the command line arguments. | |||
* @throws IOException on error. | |||
*/ | |||
public static void main(String[] args) throws IOException { | |||
String[] methods = null; | |||
boolean haltError = false; | |||
boolean haltFail = false; | |||
boolean stackfilter = true; | |||
@@ -696,7 +797,15 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
} | |||
for (int i = 1; i < args.length; i++) { | |||
if (args[i].startsWith(Constants.HALT_ON_ERROR)) { | |||
if (args[i].startsWith(Constants.METHOD_NAMES)) { | |||
try { | |||
String methodsList = args[i].substring(Constants.METHOD_NAMES.length()); | |||
methods = JUnitTest.parseTestMethodNamesList(methodsList); | |||
} catch (IllegalArgumentException ex) { | |||
System.err.println("Invalid specification of test method names: " + args[i]); | |||
System.exit(ERRORS); | |||
} | |||
} else if (args[i].startsWith(Constants.HALT_ON_ERROR)) { | |||
haltError = Project.toBoolean(args[i].substring(Constants.HALT_ON_ERROR.length())); | |||
} else if (args[i].startsWith(Constants.HALT_ON_FAILURE)) { | |||
haltFail = Project.toBoolean(args[i].substring(Constants.HALT_ON_FAILURE.length())); | |||
@@ -744,18 +853,30 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
java.io.BufferedReader reader = | |||
new java.io.BufferedReader(new java.io.FileReader(args[0])); | |||
String testCaseName; | |||
String[] testMethodNames; | |||
int code = 0; | |||
boolean errorOccurred = false; | |||
boolean failureOccurred = false; | |||
String line = null; | |||
while ((line = reader.readLine()) != null) { | |||
StringTokenizer st = new StringTokenizer(line, ","); | |||
testCaseName = st.nextToken(); | |||
String testListSpec = st.nextToken(); | |||
int colonIndex = testListSpec.indexOf(':'); | |||
if (colonIndex == -1) { | |||
testCaseName = testListSpec; | |||
testMethodNames = null; | |||
} else { | |||
testCaseName = testListSpec.substring(0, colonIndex); | |||
testMethodNames = JUnitTest.parseTestMethodNamesList( | |||
testListSpec | |||
.substring(colonIndex + 1) | |||
.replace('+', ',')); | |||
} | |||
JUnitTest t = new JUnitTest(testCaseName); | |||
t.setTodir(new File(st.nextToken())); | |||
t.setOutfile(st.nextToken()); | |||
t.setProperties(props); | |||
code = launch(t, haltError, stackfilter, haltFail, | |||
code = launch(t, testMethodNames, haltError, stackfilter, haltFail, | |||
showOut, outputToFormat, | |||
logTestListenerEvents); | |||
errorOccurred = (code == ERRORS); | |||
@@ -783,7 +904,7 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
JUnitTest t = new JUnitTest(args[0]); | |||
t.setProperties(props); | |||
returnCode = launch( | |||
t, haltError, stackfilter, haltFail, | |||
t, methods, haltError, stackfilter, haltFail, | |||
showOut, outputToFormat, logTestListenerEvents); | |||
} | |||
@@ -917,12 +1038,12 @@ public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestR | |||
/** | |||
* @since Ant 1.6.2 | |||
*/ | |||
private static int launch(JUnitTest t, boolean haltError, | |||
private static int launch(JUnitTest t, String[] methods, boolean haltError, | |||
boolean stackfilter, boolean haltFail, | |||
boolean showOut, boolean outputToFormat, | |||
boolean logTestListenerEvents) { | |||
JUnitTestRunner runner = | |||
new JUnitTestRunner(t, haltError, stackfilter, haltFail, showOut, | |||
new JUnitTestRunner(t, methods, haltError, stackfilter, haltFail, showOut, | |||
logTestListenerEvents, null); | |||
runner.forked = true; | |||
runner.outputToFormatters = outputToFormat; | |||
@@ -0,0 +1,142 @@ | |||
/* | |||
* 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 junit.framework.ComparisonFailure; | |||
import junit.framework.TestCase; | |||
/** | |||
* | |||
* @author Marian Petras | |||
*/ | |||
public class BatchTestTest extends TestCase { | |||
public BatchTestTest(String testName) { | |||
super(testName); | |||
} | |||
public void testParseTestMethodNamesList() { | |||
try { | |||
JUnitTest.parseTestMethodNamesList(null); | |||
fail("IllegalArgumentException expected when the param is <null>"); | |||
} catch (IllegalArgumentException ex) { | |||
//this is an expected exception | |||
} | |||
assertEquals(new String[0], JUnitTest.parseTestMethodNamesList("")); | |||
assertEquals(new String[0], JUnitTest.parseTestMethodNamesList(" ")); | |||
assertEquals(new String[0], JUnitTest.parseTestMethodNamesList(" ")); | |||
checkParseCausesIAE(","); | |||
checkParseCausesIAE(" ,"); | |||
checkParseCausesIAE(", "); | |||
checkParseCausesIAE(" , "); | |||
checkParseCausesIAE(",a"); | |||
checkParseCausesIAE(" ,a"); | |||
checkParseCausesIAE(" ,a"); | |||
checkParseCausesIAE(" , a"); | |||
checkParseCausesIAE(" ,a "); | |||
checkParseCausesIAE(" ,a ,"); | |||
checkParseCausesIAE("ab,,cd"); | |||
checkParseCausesIAE("ab, ,cd"); | |||
checkParseCausesIAE("ab, ,cd"); | |||
checkParseCausesIAE("ab, ,cd,"); | |||
checkParseCausesIAE(",ab, ,cd,"); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc ")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc ")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc ")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc,")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc, ")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc ,")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc , ")); | |||
assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc ,")); | |||
/* legal Java identifiers: */ | |||
assertEquals(new String[] {"a"}, JUnitTest.parseTestMethodNamesList("a")); | |||
assertEquals(new String[] {"a1"}, JUnitTest.parseTestMethodNamesList("a1")); | |||
assertEquals(new String[] {"a$"}, JUnitTest.parseTestMethodNamesList("a$")); | |||
assertEquals(new String[] {"a$1"}, JUnitTest.parseTestMethodNamesList("a$1")); | |||
assertEquals(new String[] {"_bc"}, JUnitTest.parseTestMethodNamesList("_bc")); | |||
assertEquals(new String[] {"___"}, JUnitTest.parseTestMethodNamesList("___")); | |||
/* illegal Java identifiers: */ | |||
checkParseCausesIAE("1"); | |||
checkParseCausesIAE("1a"); | |||
checkParseCausesIAE("1ab"); | |||
checkParseCausesIAE("1abc"); | |||
checkParseCausesIAE("1abc d"); | |||
checkParseCausesIAE("1abc de"); | |||
checkParseCausesIAE("1abc def"); | |||
checkParseCausesIAE("1abc def,"); | |||
checkParseCausesIAE(",1abc def"); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def,")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc, def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc, def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc ,def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc ,def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc , def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc , def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc,def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc,def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc, def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc, def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc ,def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc ,def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def ")); | |||
assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def ,")); | |||
} | |||
private static void checkParseCausesIAE(String param) { | |||
try { | |||
JUnitTest.parseTestMethodNamesList(param); | |||
fail("IllegalArgumentException expected when the param is \"" + param + '"'); | |||
} catch (IllegalArgumentException ex) { | |||
//this is an expected exception | |||
} | |||
} | |||
private static void assertEquals(String[] expected, String[] actual) { | |||
assertEquals(null, expected, actual); | |||
} | |||
private static void assertEquals(String message, | |||
String[] expected, | |||
String[] actual) { | |||
if ((expected == null) && (actual == null)) { | |||
return; | |||
} | |||
if (expected.length != actual.length) { | |||
throw new ComparisonFailure(message, | |||
expected.toString(), | |||
actual.toString()); | |||
} | |||
for (int i = 0; i < expected.length; i++) { | |||
assertEquals(expected[i], actual[i]); | |||
} | |||
} | |||
} |
@@ -33,6 +33,22 @@ public class JUnitTestRunnerTest extends TestCase { | |||
super(name); | |||
} | |||
// check that a valid method name generates no errors | |||
public void testValidMethod(){ | |||
TestRunner runner = createRunnerForTestMethod(ValidMethodTestCase.class,"testA"); | |||
runner.run(); | |||
assertEquals(runner.getFormatter().getError(), JUnitTestRunner.SUCCESS, runner.getRetCode()); | |||
} | |||
// check that having an invalid method name generates an error | |||
public void testInvalidMethod(){ | |||
TestRunner runner = createRunnerForTestMethod(InvalidMethodTestCase.class,"testInvalid"); | |||
runner.run(); | |||
String error = runner.getFormatter().getError(); | |||
// might be FAILURES or ERRORS depending on JUnit version? | |||
assertTrue(error, runner.getRetCode() != JUnitTestRunner.SUCCESS); | |||
} | |||
// check that having no suite generates no errors | |||
public void testNoSuite(){ | |||
TestRunner runner = createRunner(NoSuiteTestCase.class); | |||
@@ -87,14 +103,22 @@ public class JUnitTestRunnerTest extends TestCase { | |||
} | |||
protected TestRunner createRunner(Class clazz){ | |||
return new TestRunner(new JUnitTest(clazz.getName()), true, true, true); | |||
return new TestRunner(new JUnitTest(clazz.getName()), null, | |||
true, true, true); | |||
} | |||
protected TestRunner createRunnerForTestMethod(Class clazz, String method){ | |||
return new TestRunner(new JUnitTest(clazz.getName()), new String[] {method}, | |||
true, true, true); | |||
} | |||
// the test runner that wrap the dummy formatter that interests us | |||
private final static class TestRunner extends JUnitTestRunner { | |||
private ResultFormatter formatter = new ResultFormatter(); | |||
TestRunner(JUnitTest test, boolean haltonerror, boolean filtertrace, boolean haltonfailure){ | |||
super(test, haltonerror, filtertrace, haltonfailure, TestRunner.class.getClassLoader()); | |||
TestRunner(JUnitTest test, String[] methods, boolean haltonerror, | |||
boolean filtertrace, boolean haltonfailure){ | |||
super(test, methods, haltonerror, filtertrace, haltonfailure, | |||
false, false, TestRunner.class.getClassLoader()); | |||
// use the classloader that loaded this class otherwise | |||
// it will not be able to run inner classes if this test | |||
// is ran in non-forked mode. | |||
@@ -133,6 +157,24 @@ public class JUnitTestRunnerTest extends TestCase { | |||
public static class NoTestCase { | |||
} | |||
public static class InvalidMethodTestCase extends TestCase { | |||
public InvalidMethodTestCase(String name){ super(name); } | |||
public void testA(){ | |||
throw new NullPointerException("thrown on purpose"); | |||
} | |||
} | |||
public static class ValidMethodTestCase extends TestCase { | |||
public ValidMethodTestCase(String name){ super(name); } | |||
public void testA(){ | |||
// expected to be executed | |||
} | |||
public void testB(){ | |||
// should not be executed | |||
throw new NullPointerException("thrown on purpose"); | |||
} | |||
} | |||
public static class InvalidTestCase extends TestCase { | |||
public InvalidTestCase(String name){ | |||
super(name); | |||