Author: jhm Date: Mon Aug 6 00:55:07 2007 New Revision: 563053 URL: http://svn.apache.org/viewvc?view=rev&rev=563053 Log: New JUnit formatter: collects failing test cases (testXXX methods) for a rerun. * works from command line * its own JUnit test scenario fails (for - to me - unknown reason)
* BFT has new method 'assertOutputNotContaining' similar to 'assertOutputContaining' Added: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java Modified: ant/core/trunk/src/etc/testcases/taskdefs/optional/junit.xml ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java Modified: ant/core/trunk/src/etc/testcases/taskdefs/optional/junit.xml URL: http://svn.apache.org/viewvc/ant/core/trunk/src/etc/testcases/taskdefs/optional/junit.xml?view=diff&rev=563053&r1=563052&r2=563053 ============================================================================== --- ant/core/trunk/src/etc/testcases/taskdefs/optional/junit.xml (original) +++ ant/core/trunk/src/etc/testcases/taskdefs/optional/junit.xml Mon Aug 6 00:55:07 2007 @@ -143,4 +143,94 @@ </batchtest> </junit> </target> + + <target name="failureRecorder.prepare"> + <property name="tmp.dir" value="out"/> + <mkdir dir="${tmp.dir}/org"/> + <echo file="${tmp.dir}/A.java"> + import junit.framework.*; + public class A extends TestCase { + public A(String s) { super(s); } + 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.*; + public class B extends TestCase { + public B(String s) { super(s); } + public void test04() { System.out.println("B.test04"); fail(); } + public void test05() { System.out.println("B.test05"); } + public void test06() { System.out.println("B.test06"); } + } + </echo> + <echo file="${tmp.dir}/C.java"> + import junit.framework.*; + public class C extends TestCase { + public C(String s) { super(s); } + public void test07() { System.out.println("C.test07"); } + public void test08() { System.out.println("C.test08"); } + public void test09() { System.out.println("C.test09"); } + } + </echo> + <echo file="${tmp.dir}/org/D.java"> + package org; + import junit.framework.*; + public class D extends TestCase { + public D(String s) { super(s); } + public void test10() { System.out.println("D.test10"); fail(); } + } + </echo> + <javac srcdir="${tmp.dir}" destdir="${tmp.dir}"/> + </target> + + <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 --> + <javac srcdir="${tmp.dir}" destdir="${tmp.dir}"/> + <available file="${tmp.dir}/FailedTests.class" property="hasFailingTests"/> + <junit haltonerror="false" haltonfailure="false"> + <sysproperty key="ant.junit.failureCollector" value="${tmp.dir}/FailedTests"/> + <classpath> + <pathelement location="${tmp.dir}"/> + </classpath> + <batchtest todir="${tmp.dir}" unless="hasFailingTests"> + <fileset dir="${tmp.dir}" includes="**/*.java" excludes="**/FailedTests.*"/> + <!-- for initial creation of the FailingTests.java --> + <formatter type="failure"/> + <!-- I want to see something ... --> + <formatter type="plain" usefile="false"/> + </batchtest> + <test name="FailedTests" if="hasFailingTests"> + <!-- update the FailingTests.java --> + <formatter type="failure"/> + <!-- again, I want to see something --> + <formatter type="plain" usefile="false"/> + </test> + </junit> + </target> + + <target name="failureRecorder.runtest"> + <ant target="failureRecorder.internal" antfile="junit.xml" inheritAll="false"/> + </target> + + <target name="failureRecorder.fixing"> + <property name="tmp.dir" value="out"/> + <echo file="${tmp.dir}/A.java"> + import junit.framework.*; + public class A extends TestCase { + public A(String s) { super(s); } + 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> + </project> Added: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java?view=auto&rev=563053 ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java (added) +++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java Mon Aug 6 00:55:07 2007 @@ -0,0 +1,200 @@ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.FileUtils; + +/** + * <p>Collects all failing test <i>cases</i> and creates a new JUnit test class containing + * a suite() method which calls these failed tests.</p> + * <p>Having classes <i>A</i> ... <i>D</i> with each several testcases you could earn a new + * test class like + * <pre> + * // generated on: 2007.08.06 09:42:34,555 + * import junit.framework.*; + * public class FailedTests extends TestCase { + * public FailedTests(String s) { + * super(s); + * } + * public static Test suite() { + * TestSuite suite = new TestSuite(); + * suite.addTest( new B("test04") ); + * suite.addTest( new org.D("test10") ); + * return suite; + * } + * } + * </pre> + * + * @since Ant 1.7.1 + */ +/* + * 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. + */ +public class FailureRecorder implements JUnitResultFormatter { + + /** + * This is the name of a magic System property ([EMAIL PROTECTED]). The value of this + * <b>System</b> property should point to the location where to store the + * generated class (without suffix). + * Default location and name is defined in DEFAULT_CLASS_LOCATION. + * @see #DEFAULT_CLASS_LOCATION + */ + public static final String MAGIC_PROPERTY_CLASS_LOCATION = "ant.junit.failureCollector"; + + /** Default location and name for the generated JUnit class file. [EMAIL PROTECTED] */ + public static final String DEFAULT_CLASS_LOCATION = System.getProperty("java.io.tmpdir") + "FailedTests"; + + /** Class names of failed tests without duplicates. */ + private static HashSet/*<Test>*/ failedTests = new HashSet(); + + /** 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? + private String getLocationName() { + if (locationName == null) { + String propValue = System.getProperty(MAGIC_PROPERTY_CLASS_LOCATION); + locationName = (propValue != null) ? propValue : DEFAULT_CLASS_LOCATION; + } + return locationName; + } + + /** + * After each test suite, the whole new JUnit class will be regenerated. + * @see org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter#endTestSuite(org.apache.tools.ant.taskdefs.optional.junit.JUnitTest) + */ + public void endTestSuite(JUnitTest suite) throws BuildException { + if (failedTests.isEmpty()) return; + try { + File sourceFile = new File(getLocationName() + ".java"); + sourceFile.delete(); + writer = new PrintWriter(new FileOutputStream(sourceFile)); + + createClassHeader(); + createTestSuiteHeader(); + for (Iterator iter = failedTests.iterator(); iter.hasNext();) { + Test test = (Test) iter.next(); + if (test!=null) { + createAddTestToSuite(test); + } + } + createTestSuiteFooter(); + createClassFooter(); + + FileUtils.close(writer); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + public void addError(Test test, Throwable throwable) { + failedTests.add(test); + } + + public void addFailure(Test test, AssertionFailedError error) { + failedTests.add(test); + } + + public void setOutput(OutputStream out) { + // not in use + } + + public void setSystemError(String err) { + // not in use + } + + public void setSystemOutput(String out) { + // not in use + } + + public void startTestSuite(JUnitTest suite) throws BuildException { + // not in use + } + + public void endTest(Test test) { + // not in use + } + + public void startTest(Test test) { + // not in use + } + + // "Templates" for generating the JUnit class + + private void createClassHeader() { + String className = getLocationName().replace('\\', '/'); + if (className.indexOf('/') > -1) { + className = className.substring(className.lastIndexOf('/')+1); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss,SSS"); + writer.print("// generated on: "); + writer.println(sdf.format(new Date())); + writer.println("import junit.framework.*;"); + writer.print("public class "); + writer.print( className ); + // If this class does not extend TC, Ant doesnt run these + writer.println(" extends TestCase {"); + // no-arg constructor + writer.print(" public "); + writer.print(className); + writer.println("(String s) {"); + writer.println(" super(s);"); + writer.println(" }"); + } + + private void createTestSuiteHeader() { + writer.println(" public static Test suite() {"); + writer.println(" TestSuite suite = new TestSuite();"); + } + + private void createAddTestToSuite(Test test) { + writer.print(" suite.addTest( new "); + writer.print( getClassName(test) ); + writer.print("(\""); + writer.print( getMethodName(test) ); + writer.println("\") );"); + } + + private void createTestSuiteFooter() { + writer.println(" return suite;"); + writer.println(" }"); + } + + private void createClassFooter() { + writer.println("}"); + } + + // Helper methods + + private String getMethodName(Test test) { + String methodName = test.toString(); + return methodName.substring(0, methodName.indexOf('(')); + } + + private String getClassName(Test test) { + return test.getClass().getName(); + } + +} Modified: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java?view=diff&rev=563053&r1=563052&r2=563053 ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java (original) +++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java Mon Aug 6 00:55:07 2007 @@ -47,6 +47,7 @@ * @see XMLJUnitResultFormatter * @see BriefJUnitResultFormatter * @see PlainJUnitResultFormatter + * @see FailureRecorder * @see JUnitResultFormatter */ public class FormatterElement { @@ -68,6 +69,9 @@ /** plain formatter class */ public static final String PLAIN_FORMATTER_CLASS_NAME = "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter"; + /** failure recorder class */ + public static final String FAILURE_RECORDER_CLASS_NAME = + "org.apache.tools.ant.taskdefs.optional.junit.FailureRecorder"; /** * <p> Quick way to use a standard formatter. @@ -77,6 +81,7 @@ * <li> The <code>xml</code> type uses a <code>XMLJUnitResultFormatter</code>. * <li> The <code>brief</code> type uses a <code>BriefJUnitResultFormatter</code>. * <li> The <code>plain</code> type (the default) uses a <code>PlainJUnitResultFormatter</code>. + * <li> The <code>failure</code> type uses a <code>FailureRecorder</code>. * </ul> * * <p> Sets <code>classname</code> attribute - so you can't use that @@ -84,13 +89,18 @@ * @param type the enumerated value to use. */ public void setType(TypeAttribute type) { + //TODO: Besseren Zugriffsalgorithums: TypeAttribut.getClassname() if ("xml".equals(type.getValue())) { setClassname(XML_FORMATTER_CLASS_NAME); } else { if ("brief".equals(type.getValue())) { setClassname(BRIEF_FORMATTER_CLASS_NAME); - } else { // must be plain, ensured by TypeAttribute - setClassname(PLAIN_FORMATTER_CLASS_NAME); + } else { + if ("failure".equals(type.getValue())) { + setClassname(FAILURE_RECORDER_CLASS_NAME); + } else { // must be plain, ensured by TypeAttribute + setClassname(PLAIN_FORMATTER_CLASS_NAME); + } } } } @@ -268,14 +278,14 @@ } /** - * <p> Enumerated attribute with the values "plain", "xml" and "brief". + * <p> Enumerated attribute with the values "plain", "xml", "brief" and "failure". * * <p> Use to enumerate options for <code>type</code> attribute. */ public static class TypeAttribute extends EnumeratedAttribute { /** [EMAIL PROTECTED] */ public String[] getValues() { - return new String[] {"plain", "xml", "brief"}; + return new String[] {"plain", "xml", "brief", "failure"}; } } } Modified: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java?view=diff&rev=563053&r1=563052&r2=563053 ============================================================================== --- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java (original) +++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java Mon Aug 6 00:55:07 2007 @@ -163,7 +163,7 @@ private JUnitTaskMirror delegate; /** A boolean on whether to get the forked path for ant classes */ - private boolean forkedPathChecked = false; + private boolean forkedPathChecked = false; // Attributes for basetest private boolean haltOnError = false; Modified: ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java?view=diff&rev=563053&r1=563052&r2=563053 ============================================================================== --- ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java (original) +++ ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java Mon Aug 6 00:55:07 2007 @@ -18,11 +18,11 @@ package org.apache.tools.ant; -import junit.framework.TestCase; import java.io.File; import java.io.PrintStream; import java.net.URL; -import java.util.Hashtable; + +import junit.framework.TestCase; /** * A BuildFileTest is a TestCase which executes targets from an Ant buildfile @@ -117,18 +117,35 @@ /** * Assert that the given substring is in the output messages. + * @param message Print this message if the test fails. Defaults to + * a meaningful text if <tt>null</tt> is passed. + * @since Ant1.7 + */ + public void assertOutputContaining(String message, String substring) { + String realOutput = getOutput(); + String realMessage = (message != null) + ? message + : "expecting output to contain \"" + substring + "\" output was \"" + realOutput + "\""; + assertTrue(realMessage, realOutput.indexOf(substring) >= 0); + } + + /** + * Assert that the given substring is not in the output messages. + * @param message Print this message if the test fails. Defaults to + * a meaningful text if <tt>null</tt> is passed. * @since Ant1.7 */ - public void assertOutputContaining(String substring) { + public void assertOutputNotContaining(String message, String substring) { String realOutput = getOutput(); - assertTrue("expecting output to contain \"" + substring - + "\" output was \"" + realOutput + "\"", - realOutput.indexOf(substring) >= 0); + String realMessage = (message != null) + ? message + : "expecting output to contain \"" + substring + "\" output was \"" + realOutput + "\""; + assertFalse(realMessage, realOutput.indexOf(substring) >= 0); } /** - * Assert that the given message has been logged with a priority - * <= INFO when running the given target. + * Assert that the given message has been logged with a priority <= INFO when running the + * given target. */ public void expectLogContaining(String target, String log) { executeTarget(target); Modified: ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java?view=diff&rev=563053&r1=563052&r2=563053 ============================================================================== --- ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java (original) +++ ant/core/trunk/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java Mon Aug 6 00:55:07 2007 @@ -17,31 +17,31 @@ */ package org.apache.tools.ant.taskdefs.optional.junit; -import org.apache.tools.ant.BuildFileTest; import java.io.BufferedReader; +import java.io.File; import java.io.FileReader; import java.io.IOException; +import org.apache.tools.ant.BuildFileTest; + public class JUnitTaskTest extends BuildFileTest { /** - * Constructor for the JUnitTaskTest object + * Constructor for the JUnitTaskTest object. */ public JUnitTaskTest(String name) { super(name); } - /** - * The JUnit setup method + * The JUnit setup method. */ public void setUp() { configureProject("src/etc/testcases/taskdefs/optional/junit.xml"); } - /** - * The teardown method for JUnit + * The teardown method for JUnit. */ public void tearDown() { executeTarget("cleanup"); @@ -86,7 +86,83 @@ 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() { + 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.", + testDir.exists()); + assertFalse("The collector file 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("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()); + + // 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()); + // the passing test cases + assertOutputContaining("1st run: should run A.test01", "A.test01"); + assertOutputContaining("1st run: should run B.test05", "B.test05"); + assertOutputContaining("1st run: should run B.test06", "B.test06"); + assertOutputContaining("1st run: should run C.test07", "C.test07"); + assertOutputContaining("1st run: should run C.test08", "C.test08"); + assertOutputContaining("1st run: should run C.test09", "C.test09"); + // the failing test cases + assertOutputContaining("1st run: should run A.test02", "A.test02"); + assertOutputContaining("1st run: should run A.test03", "A.test03"); + 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()); + // the passing test cases + assertOutputNotContaining("2nd run: should not run A.test01", "A.test01"); + assertOutputNotContaining("2nd run: should not run A.test05", "B.test05"); + assertOutputNotContaining("2nd run: should not run B.test06", "B.test06"); + assertOutputNotContaining("2nd run: should not run C.test07", "C.test07"); + assertOutputNotContaining("2nd run: should not run C.test08", "C.test08"); + assertOutputNotContaining("2nd run: should not run C.test09", "C.test09"); + // the failing test cases + assertOutputContaining("2nd run: should run A.test02", "A.test02"); + assertOutputContaining("2nd run: should run A.test03", "A.test03"); + 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()); + 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"); + assertOutputContaining("4th run: should run B.test04", "B.test04"); + assertOutputContaining("4th run: should run D.test10", "D.test10"); + } + public void testBatchTestForkOnceCustomFormatter() { assertResultFilesExist("testBatchTestForkOnceCustomFormatter", "foo"); } @@ -155,5 +231,4 @@ assertEquals(search, line); } -} - +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]