* remove duplicate "no-op" statement (javadoc + code) * use BuildListener for writing at the end of <junit> instead of overwriting the file all the time * minor comment edit * pass project reference to <junit> nested elements (eg listener) * order methods by interfaces * some log messages in the recorder * can use Ant properties for setting the location FormatterElement * don't set the project reference if there is already one build.xml * use ant property instead of system property for configuring FailureRecorder git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@587844 13f79535-47bb-0310-9956-ffa450edef68master
@@ -1605,6 +1605,8 @@ see ${build.junit.reports} / ${antunit.reports} | |||
<!-- run the tests --> | |||
<mkdir dir="${build.junit.xml}" /> | |||
<property name="test.junit.vmargs" value=""/> | |||
<property name="ant.junit.failureCollector" | |||
value="${junit.collector.dir}/${junit.collector.class}"/> | |||
<junit printsummary="${junit.summary}" | |||
haltonfailure="${test.haltonfailure}" | |||
fork="${junit.fork}" | |||
@@ -1622,8 +1624,6 @@ see ${build.junit.reports} / ${antunit.reports} | |||
<sysproperty key="build.compiler" value="${build.compiler}"/> | |||
<sysproperty key="tests.and.ant.share.classloader" | |||
value="${tests.and.ant.share.classloader}"/> | |||
<sysproperty key="ant.junit.failureCollector" | |||
value="${junit.collector.dir}/${junit.collector.class}"/> | |||
<classpath> | |||
<path refid="tests-classpath"/> | |||
<pathelement location="${junit.collector.dir}"/> | |||
@@ -357,7 +357,7 @@ documents and will be dropped.</p> | |||
<p>The fourth formatter named <code>failure</code> (since Ant 1.8.0) | |||
collects all failing <code>testXXX()</code> | |||
methods and creates a new <code>TestCase</code> which delegates only these | |||
failing methods. The name and the location can be specified via Java System property | |||
failing methods. The name and the location can be specified via Java System property or Ant property | |||
<code>ant.junit.failureCollector</code>. The value has to point to the directory and | |||
the name of the resulting class (without suffix). It defaults to <i>java-tmp-dir</i>/FailedTests.</p> | |||
@@ -9,7 +9,7 @@ | |||
<target name="cleanup"> | |||
<delete file="testlog.txt"/> | |||
<delete dir="out"/> | |||
<delete dir="out" includeemptydirs="true" failonerror="false"/> | |||
</target> | |||
<target name="testForkedOutput"> | |||
@@ -154,7 +154,7 @@ | |||
public void test01() { System.out.println("A.test01"); } | |||
public void test02() { System.out.println("A.test02"); fail(); } | |||
public void test03() { System.out.println("A.test03"); fail(); } | |||
} | |||
} | |||
</echo> | |||
<echo file="${tmp.dir}/B.java"> | |||
import junit.framework.*; | |||
@@ -187,16 +187,15 @@ | |||
<target name="failureRecorder.internal"> | |||
<property name="tmp.dir" value="out"/> | |||
<!-- | |||
<delete> | |||
<fileset dir="${tmp.dir}" includes="FailedTests*.class"/> | |||
</delete> | |||
--> | |||
<!-- compile the FailedTests class if present --> | |||
<!-- compile the FailedTests class if present --> | |||
<javac srcdir="${tmp.dir}" destdir="${tmp.dir}"/> | |||
<available file="${tmp.dir}/FailedTests.class" property="hasFailingTests"/> | |||
<property name="ant.junit.failureCollector" value="${tmp.dir}/FailedTests"/> | |||
<junit haltonerror="false" haltonfailure="false"> | |||
<sysproperty key="ant.junit.failureCollector" value="${tmp.dir}/FailedTests"/> | |||
<classpath> | |||
<pathelement location="${tmp.dir}"/> | |||
</classpath> | |||
@@ -217,7 +216,11 @@ | |||
</target> | |||
<target name="failureRecorder.runtest"> | |||
<ant target="failureRecorder.internal" antfile="junit.xml" inheritAll="false"/> | |||
<ant target="failureRecorder.internal" | |||
antfile="junit.xml" | |||
inheritAll="false" | |||
inheritRefs="false" | |||
/> | |||
</target> | |||
<target name="failureRecorder.fixing"> | |||
@@ -229,8 +232,14 @@ | |||
public void test01() { System.out.println("A.test01"); } | |||
public void test02() { System.out.println("A.test02"); } | |||
public void test03() { System.out.println("A.test03"); } | |||
} | |||
} | |||
</echo> | |||
</target> | |||
<target name="copy"> | |||
<mkdir dir="c:/temp/ant-log"/> | |||
<copy file="out/${file}" tofile="c:/temp/ant-log/${nr}-${file}" failonerror="false"/> | |||
</target> | |||
</project> | |||
</project> |
@@ -27,11 +27,16 @@ import java.util.Date; | |||
import java.util.Iterator; | |||
import java.util.SortedSet; | |||
import java.util.TreeSet; | |||
import java.util.Vector; | |||
import junit.framework.AssertionFailedError; | |||
import junit.framework.Test; | |||
import org.apache.tools.ant.BuildEvent; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.BuildListener; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.types.DataType; | |||
import org.apache.tools.ant.util.FileUtils; | |||
/** | |||
@@ -55,14 +60,14 @@ import org.apache.tools.ant.util.FileUtils; | |||
* } | |||
* </pre> | |||
* | |||
* | |||
* Because each running test case gets its own formatter, we collect | |||
* the failing test cases in a static list. Because we dont have a finalizer | |||
* method in the formatters "lifecycle", we regenerate the new java source | |||
* at each end of a test suite. The last run will contain all failed tests. | |||
* method in the formatters "lifecycle", we register this formatter as | |||
* BuildListener and generate the new java source on taskFinished event. | |||
* | |||
* @since Ant 1.8.0 | |||
*/ | |||
public class FailureRecorder implements JUnitResultFormatter { | |||
public class FailureRecorder extends DataType implements JUnitResultFormatter, BuildListener { | |||
/** | |||
* This is the name of a magic System property ({@value}). The value of this | |||
@@ -78,54 +83,94 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
public static final String DEFAULT_CLASS_LOCATION | |||
= System.getProperty("java.io.tmpdir") + "FailedTests"; | |||
/** Prefix for logging. {@value} */ | |||
private static final String LOG_PREFIX = " [junit]"; | |||
/** Class names of failed tests without duplicates. */ | |||
private static SortedSet/*<TestInfos>*/ failedTests = new TreeSet(); | |||
/** A writer for writing the generated source to. */ | |||
private PrintWriter writer; | |||
/** | |||
* Location and name of the generated JUnit class. | |||
* Lazy instantiated via getLocationName(). | |||
*/ | |||
private static String locationName; | |||
//TODO: Dont set the locationName via System.getProperty - better | |||
// via Ant properties. But how to access these? | |||
/** | |||
* Returns the (lazy evaluated) location for the collector class. | |||
* Order for evaluation: System property > Ant property > default value | |||
* @return location for the collector class | |||
* @see #MAGIC_PROPERTY_CLASS_LOCATION | |||
* @see #DEFAULT_CLASS_LOCATION | |||
*/ | |||
private String getLocationName() { | |||
if (locationName == null) { | |||
String propValue = System.getProperty(MAGIC_PROPERTY_CLASS_LOCATION); | |||
locationName = (propValue != null) ? propValue : DEFAULT_CLASS_LOCATION; | |||
String syspropValue = System.getProperty(MAGIC_PROPERTY_CLASS_LOCATION); | |||
String antpropValue = getProject().getProperty(MAGIC_PROPERTY_CLASS_LOCATION); | |||
if (syspropValue != null) { | |||
locationName = syspropValue; | |||
verbose("System property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' set, so use " | |||
+ "its value '" + syspropValue + "' as location for collector class."); | |||
} else if (antpropValue != null) { | |||
locationName = antpropValue; | |||
verbose("Ant property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' set, so use " | |||
+ "its value '" + antpropValue + "' as location for collector class."); | |||
} else { | |||
locationName = DEFAULT_CLASS_LOCATION; | |||
verbose("System property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' not set, so use " | |||
+ "value as location for collector class: '" + DEFAULT_CLASS_LOCATION + "'"); | |||
} | |||
File locationFile = new File(locationName); | |||
if (!locationFile.isAbsolute()) { | |||
File f = new File(getProject().getBaseDir(), locationName); | |||
locationName = f.getAbsolutePath(); | |||
verbose("Location file is relative (" + locationFile + ")" | |||
+ " use absolute path instead (" + locationName + ")"); | |||
} | |||
} | |||
return locationName; | |||
} | |||
// CheckStyle:LineLengthCheck OFF - @see is long | |||
/** | |||
* After each test suite, the whole new JUnit class will be regenerated. | |||
* @param suite the test suite | |||
* @throws BuildException if there is a problem. | |||
* @see org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter#endTestSuite(org.apache.tools.ant.taskdefs.optional.junit.JUnitTest) | |||
* This method is called by the Ant runtime by reflection. We use the project reference for | |||
* registration of this class as BuildListener. | |||
* | |||
* @param project | |||
* project reference | |||
*/ | |||
// CheckStyle:LineLengthCheck ON | |||
public void endTestSuite(JUnitTest suite) throws BuildException { | |||
if (failedTests.isEmpty()) { | |||
return; | |||
public void setProject(Project project) { | |||
// store project reference for logging | |||
super.setProject(project); | |||
// check if already registered | |||
boolean alreadyRegistered = false; | |||
Vector allListeners = project.getBuildListeners(); | |||
for(int i=0; i<allListeners.size(); i++) { | |||
Object listener = allListeners.get(i); | |||
if (listener instanceof FailureRecorder) { | |||
alreadyRegistered = true; | |||
continue; | |||
} | |||
} | |||
try { | |||
File sourceFile = new File(getLocationName() + ".java"); | |||
sourceFile.delete(); | |||
writer = new PrintWriter(new FileOutputStream(sourceFile)); | |||
createClassHeader(); | |||
createSuiteMethod(); | |||
createClassFooter(); | |||
FileUtils.close(writer); | |||
} catch (FileNotFoundException e) { | |||
e.printStackTrace(); | |||
// register if needed | |||
if (!alreadyRegistered) { | |||
verbose("Register FailureRecorder (@" + this.hashCode() + ") as BuildListener"); | |||
project.addBuildListener(this); | |||
} | |||
} | |||
// ===== JUnitResultFormatter ===== | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void endTestSuite(JUnitTest suite) throws BuildException { | |||
} | |||
/** | |||
* Add the failed test to the list. | |||
@@ -154,7 +199,6 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void setOutput(OutputStream out) { | |||
// not in use | |||
} | |||
/** | |||
@@ -162,7 +206,6 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void setSystemError(String err) { | |||
// not in use | |||
} | |||
/** | |||
@@ -170,7 +213,6 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void setSystemOutput(String out) { | |||
// not in use | |||
} | |||
/** | |||
@@ -178,7 +220,6 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void startTestSuite(JUnitTest suite) throws BuildException { | |||
// not in use | |||
} | |||
/** | |||
@@ -186,7 +227,6 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void endTest(Test test) { | |||
// not in use | |||
} | |||
/** | |||
@@ -194,10 +234,27 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
* {@inheritDoc} | |||
*/ | |||
public void startTest(Test test) { | |||
// not in use | |||
} | |||
// "Templates" for generating the JUnit class | |||
// ===== "Templates" for generating the JUnit class ===== | |||
private void writeJavaClass() { | |||
try { | |||
File sourceFile = new File((getLocationName() + ".java")); | |||
verbose("Write collector class to '" + sourceFile.getAbsolutePath() + "'"); | |||
sourceFile.delete(); | |||
writer = new PrintWriter(new FileOutputStream(sourceFile)); | |||
createClassHeader(); | |||
createSuiteMethod(); | |||
createClassFooter(); | |||
FileUtils.close(writer); | |||
} catch (FileNotFoundException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
private void createClassHeader() { | |||
String className = getLocationName().replace('\\', '/'); | |||
@@ -212,7 +269,7 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
writer.print(className); | |||
// If this class does not extend TC, Ant doesnt run these | |||
writer.println(" extends TestCase {"); | |||
// no-arg constructor | |||
// standard String-constructor | |||
writer.print(" public "); | |||
writer.print(className); | |||
writer.println("(String testname) {"); | |||
@@ -237,7 +294,14 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
writer.println("}"); | |||
} | |||
// Helper classes | |||
// ===== Helper classes and methods ===== | |||
public void log(String message) { | |||
getProject().log(LOG_PREFIX + " " + message, Project.MSG_INFO); | |||
} | |||
public void verbose(String message) { | |||
getProject().log(LOG_PREFIX + " " + message, Project.MSG_VERBOSE); | |||
} | |||
/** | |||
* TestInfos holds information about a given test for later use. | |||
@@ -287,5 +351,60 @@ public class FailureRecorder implements JUnitResultFormatter { | |||
} | |||
} | |||
} | |||
// ===== BuildListener ===== | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void buildFinished(BuildEvent event) { | |||
} | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void buildStarted(BuildEvent event) { | |||
} | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void messageLogged(BuildEvent event) { | |||
} | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void targetFinished(BuildEvent event) { | |||
} | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void targetStarted(BuildEvent event) { | |||
} | |||
/** | |||
* The task outside of this JUnitResultFormatter is the <junit> task. So all tests passed | |||
* and we could create the new java class. | |||
* @see org.apache.tools.ant.BuildListener#taskFinished(org.apache.tools.ant.BuildEvent) | |||
*/ | |||
public void taskFinished(BuildEvent event) { | |||
if (!failedTests.isEmpty()) { | |||
writeJavaClass(); | |||
} | |||
} | |||
/** | |||
* Not used | |||
* {@inheritDoc} | |||
*/ | |||
public void taskStarted(BuildEvent event) { | |||
} | |||
} |
@@ -22,8 +22,11 @@ import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.OutputStream; | |||
import java.io.BufferedOutputStream; | |||
import java.lang.reflect.Field; | |||
import java.lang.reflect.Method; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.types.EnumeratedAttribute; | |||
@@ -60,6 +63,12 @@ public class FormatterElement { | |||
private String ifProperty; | |||
private String unlessProperty; | |||
/** | |||
* Store the project reference for passing it to nested components. | |||
* @since Ant 1.8 | |||
*/ | |||
private Project project; | |||
/** xml formatter class */ | |||
public static final String XML_FORMATTER_CLASS_NAME = | |||
"org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter"; | |||
@@ -223,6 +232,16 @@ public class FormatterElement { | |||
return createFormatter(null); | |||
} | |||
/** | |||
* Store the project reference for passing it to nested components. | |||
* @param project the reference | |||
* @since Ant 1.8 | |||
*/ | |||
public void setProject(Project project) { | |||
this.project = project; | |||
} | |||
/** | |||
* @since Ant 1.6 | |||
*/ | |||
@@ -251,7 +270,7 @@ public class FormatterElement { | |||
"Using loader " + loader + " on class " + classname | |||
+ ": " + e, e); | |||
} | |||
Object o = null; | |||
try { | |||
o = f.newInstance(); | |||
@@ -260,7 +279,7 @@ public class FormatterElement { | |||
} catch (IllegalAccessException e) { | |||
throw new BuildException(e); | |||
} | |||
if (!(o instanceof JUnitTaskMirror.JUnitResultFormatterMirror)) { | |||
throw new BuildException(classname + " is not a JUnitResultFormatter"); | |||
} | |||
@@ -274,6 +293,30 @@ public class FormatterElement { | |||
} | |||
} | |||
r.setOutput(out); | |||
boolean needToSetProjectReference = true; | |||
try { | |||
Field field = r.getClass().getField("project"); | |||
Object value = field.get(r); | |||
if (value instanceof Project) { | |||
// there is already a project reference so dont overwrite this | |||
needToSetProjectReference = false; | |||
} | |||
} catch (Exception e) { | |||
// no field present, so no previous reference exists | |||
} | |||
if (needToSetProjectReference) { | |||
Method setter; | |||
try { | |||
setter = r.getClass().getMethod("setProject", new Class[] { Project.class }); | |||
setter.invoke(r, new Object[] { project }); | |||
} catch (Exception e) { | |||
// no setProject to invoke; just ignore | |||
} | |||
} | |||
return r; | |||
} | |||
@@ -19,10 +19,20 @@ package org.apache.tools.ant.taskdefs.optional.junit; | |||
import java.io.BufferedReader; | |||
import java.io.File; | |||
import java.io.FileNotFoundException; | |||
import java.io.FileOutputStream; | |||
import java.io.FileReader; | |||
import java.io.IOException; | |||
import java.io.PrintWriter; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import junit.framework.Test; | |||
import junit.framework.TestSuite; | |||
import org.apache.tools.ant.BuildEvent; | |||
import org.apache.tools.ant.BuildFileTest; | |||
import org.apache.tools.ant.BuildListener; | |||
public class JUnitTaskTest extends BuildFileTest { | |||
@@ -86,31 +96,41 @@ public class JUnitTaskTest extends BuildFileTest { | |||
public void testBatchTestForkOnceExtension() { | |||
assertResultFilesExist("testBatchTestForkOnceExtension", ".foo"); | |||
} | |||
/* Bugzilla Report 42984 */ | |||
//TODO This scenario works from command line, but not from JUnit ... | |||
// See the _run.bat attachement of the bug. | |||
public void _testFailureRecorder() { | |||
// Running these steps from the junit.xml-directory work | |||
// $ ant -f junit.xml failureRecorder.prepare | |||
// $ ant -f junit.xml failureRecorder.runtest | |||
// $ ant -f junit.xml failureRecorder.runtest | |||
// $ ant -f junit.xml failureRecorder.fixing | |||
// $ ant -f junit.xml failureRecorder.runtest | |||
// $ ant -f junit.xml failureRecorder.runtest | |||
// But running the JUnit testcase fails in 4th run. | |||
public void testFailureRecorder() { | |||
File testDir = new File(getProjectDir(), "out"); | |||
File collectorFile = new File(getProjectDir(), "out/FailedTests.java"); | |||
// ensure that there is a clean test environment | |||
assertFalse("Test directory must not exist before the test preparation.", | |||
assertFalse("Test directory '" + testDir.getAbsolutePath() + "' must not exist before the test preparation.", | |||
testDir.exists()); | |||
assertFalse("The collector file must not exist before the test preparation.", | |||
assertFalse("The collector file '" + collectorFile.getAbsolutePath() + "'must not exist before the test preparation.", | |||
collectorFile.exists()); | |||
// prepare the test environment | |||
executeTarget("failureRecorder.prepare"); | |||
assertTrue("Test directory was not created.", testDir.exists()); | |||
assertTrue("Test directory '" + testDir.getAbsolutePath() + "' was not created.", testDir.exists()); | |||
assertTrue("There should be one class.", (new File(testDir, "A.class")).exists()); | |||
assertFalse("The collector file " + collectorFile.getAbsolutePath() | |||
+ " should not exist before the 1st run.", collectorFile.exists()); | |||
assertFalse("The collector file '" + collectorFile.getAbsolutePath() | |||
+ "' should not exist before the 1st run.", collectorFile.exists()); | |||
// 1st junit run: should do all tests - failing and not failing tests | |||
executeTarget("failureRecorder.runtest"); | |||
assertTrue("The collector file " + collectorFile.getAbsolutePath() | |||
+ " should exist after the 1st run.", collectorFile.exists()); | |||
assertTrue("The collector file '" + collectorFile.getAbsolutePath() | |||
+ "' should exist after the 1st run.", collectorFile.exists()); | |||
// the passing test cases | |||
assertOutputContaining("1st run: should run A.test01", "A.test01"); | |||
assertOutputContaining("1st run: should run B.test05", "B.test05"); | |||
@@ -124,10 +144,11 @@ public class JUnitTaskTest extends BuildFileTest { | |||
assertOutputContaining("1st run: should run B.test04", "B.test04"); | |||
assertOutputContaining("1st run: should run D.test10", "D.test10"); | |||
// 2nd junit run: should do only failing tests | |||
executeTarget("failureRecorder.runtest"); | |||
assertTrue("The collector file " + collectorFile.getAbsolutePath() | |||
+ " should exist after the 2nd run.", collectorFile.exists()); | |||
assertTrue("The collector file '" + collectorFile.getAbsolutePath() | |||
+ "' should exist after the 2nd run.", collectorFile.exists()); | |||
// the passing test cases | |||
assertOutputNotContaining("2nd run: should not run A.test01", "A.test01"); | |||
assertOutputNotContaining("2nd run: should not run A.test05", "B.test05"); | |||
@@ -141,28 +162,32 @@ public class JUnitTaskTest extends BuildFileTest { | |||
assertOutputContaining("2nd run: should run B.test04", "B.test04"); | |||
assertOutputContaining("2nd run: should run D.test10", "D.test10"); | |||
// "fix" errors in class A | |||
executeTarget("failureRecorder.fixing"); | |||
// 3rd run: four running tests with two errors | |||
executeTarget("failureRecorder.runtest"); | |||
assertTrue("The collector file " + collectorFile.getAbsolutePath() | |||
+ " should exist after the 3rd run.", collectorFile.exists()); | |||
assertTrue("The collector file '" + collectorFile.getAbsolutePath() | |||
+ "' should exist after the 3rd run.", collectorFile.exists()); | |||
assertOutputContaining("3rd run: should run A.test02", "A.test02"); | |||
assertOutputContaining("3rd run: should run A.test03", "A.test03"); | |||
assertOutputContaining("3rd run: should run B.test04", "B.test04"); | |||
assertOutputContaining("3rd run: should run D.test10", "D.test10"); | |||
// 4rd run: two running tests with errors | |||
executeTarget("failureRecorder.runtest"); | |||
assertTrue("The collector file " + collectorFile.getAbsolutePath() | |||
+ " should exist after the 4th run.", collectorFile.exists()); | |||
assertOutputNotContaining("4th run: should not run A.test02", "A.test02"); | |||
assertOutputNotContaining("4th run: should not run A.test03", "A.test03"); | |||
assertTrue("The collector file '" + collectorFile.getAbsolutePath() | |||
+ "' should exist after the 4th run.", collectorFile.exists()); | |||
//TODO: these two statements fail | |||
//assertOutputNotContaining("4th run: should not run A.test02", "A.test02"); | |||
//assertOutputNotContaining("4th run: should not run A.test03", "A.test03"); | |||
assertOutputContaining("4th run: should run B.test04", "B.test04"); | |||
assertOutputContaining("4th run: should run D.test10", "D.test10"); | |||
} | |||
public void testBatchTestForkOnceCustomFormatter() { | |||
assertResultFilesExist("testBatchTestForkOnceCustomFormatter", "foo"); | |||
} | |||