stack trace filtering for formatters. It will filter out those unneeded frame from Ant and JUnit so the stack will be much more readable. nb: Looks like the JUnit task need serious refactoring since we have been adding feature incrementally. It starts to be a real mess, I can barely read my own code :-( git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@270099 13f79535-47bb-0310-9956-ffa450edef68master
@@ -60,12 +60,13 @@ import java.util.Vector; | |||||
/** | /** | ||||
* Baseclass for BatchTest and JUnitTest. | * Baseclass for BatchTest and JUnitTest. | ||||
* | * | ||||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | ||||
*/ | */ | ||||
public abstract class BaseTest { | public abstract class BaseTest { | ||||
protected boolean haltOnError = false; | protected boolean haltOnError = false; | ||||
protected boolean haltOnFail = false; | protected boolean haltOnFail = false; | ||||
protected boolean filtertrace = true; | |||||
protected boolean fork = false; | protected boolean fork = false; | ||||
protected String ifProperty = null; | protected String ifProperty = null; | ||||
protected String unlessProperty = null; | protected String unlessProperty = null; | ||||
@@ -76,6 +77,14 @@ public abstract class BaseTest { | |||||
protected String failureProperty; | protected String failureProperty; | ||||
protected String errorProperty; | protected String errorProperty; | ||||
public void setFiltertrace(boolean value) { | |||||
filtertrace = value; | |||||
} | |||||
public boolean getFiltertrace() { | |||||
return filtertrace; | |||||
} | |||||
public void setFork(boolean value) { | public void setFork(boolean value) { | ||||
fork = value; | fork = value; | ||||
} | } | ||||
@@ -116,12 +125,12 @@ public abstract class BaseTest { | |||||
* Sets the destination directory. | * Sets the destination directory. | ||||
*/ | */ | ||||
public void setTodir(File destDir) { | public void setTodir(File destDir) { | ||||
this.destDir = destDir; | |||||
this.destDir = destDir; | |||||
} | } | ||||
/** | /** | ||||
* @return the destination directory as an absolute path if it exists | * @return the destination directory as an absolute path if it exists | ||||
* otherwise return <tt>null</tt> | |||||
* otherwise return <tt>null</tt> | |||||
*/ | */ | ||||
public String getTodir(){ | public String getTodir(){ | ||||
if (destDir != null){ | if (destDir != null){ | ||||
@@ -133,15 +142,15 @@ public abstract class BaseTest { | |||||
public java.lang.String getFailureProperty() { | public java.lang.String getFailureProperty() { | ||||
return failureProperty; | return failureProperty; | ||||
} | } | ||||
public void setFailureProperty(String failureProperty) { | public void setFailureProperty(String failureProperty) { | ||||
this.failureProperty = failureProperty; | this.failureProperty = failureProperty; | ||||
} | } | ||||
public java.lang.String getErrorProperty() { | public java.lang.String getErrorProperty() { | ||||
return errorProperty; | return errorProperty; | ||||
} | } | ||||
public void setErrorProperty(String errorProperty) { | public void setErrorProperty(String errorProperty) { | ||||
this.errorProperty = errorProperty; | this.errorProperty = errorProperty; | ||||
} | } | ||||
@@ -199,6 +199,7 @@ public final class BatchTest extends BaseTest { | |||||
test.setName(classname); | test.setName(classname); | ||||
test.setHaltonerror(this.haltOnError); | test.setHaltonerror(this.haltOnError); | ||||
test.setHaltonfailure(this.haltOnFail); | test.setHaltonfailure(this.haltOnFail); | ||||
test.setFiltertrace(this.filtertrace); | |||||
test.setFork(this.fork); | test.setFork(this.fork); | ||||
test.setIf(this.ifProperty); | test.setIf(this.ifProperty); | ||||
test.setUnless(this.unlessProperty); | test.setUnless(this.unlessProperty); | ||||
@@ -155,7 +155,23 @@ public class JUnitTask extends Task { | |||||
private Integer timeout = null; | private Integer timeout = null; | ||||
private boolean summary = false; | private boolean summary = false; | ||||
private String summaryValue = ""; | private String summaryValue = ""; | ||||
private boolean filtertrace = true; | |||||
private JUnitTestRunner runner = null; | private JUnitTestRunner runner = null; | ||||
/** | |||||
* Tells this task whether to smartly filter the stack frames of JUnit testcase | |||||
* errors and failures before reporting them. This property is applied on all | |||||
* BatchTest (batchtest) and JUnitTest (test) however it can possibly be | |||||
* overridden by their own properties. | |||||
* @param value <tt>false</tt> if it should not filter, otherwise <tt>true<tt> | |||||
*/ | |||||
public void setFiltertrace(boolean value) { | |||||
Enumeration enum = allTests(); | |||||
while (enum.hasMoreElements()) { | |||||
BaseTest test = (BaseTest) enum.nextElement(); | |||||
test.setFiltertrace(value); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Tells this task to halt when there is an error in a test. | * Tells this task to halt when there is an error in a test. | ||||
@@ -361,7 +377,7 @@ public class JUnitTask extends Task { | |||||
/** | /** | ||||
* Adds the jars or directories containing Ant, this task and | * Adds the jars or directories containing Ant, this task and | ||||
* JUnit to the classpath - this should make the forked JVM work | * JUnit to the classpath - this should make the forked JVM work | ||||
* without having to specify the directly. | |||||
* without having to specify them directly. | |||||
*/ | */ | ||||
public void init() { | public void init() { | ||||
addClasspathEntry("/junit/framework/TestCase.class"); | addClasspathEntry("/junit/framework/TestCase.class"); | ||||
@@ -447,6 +463,7 @@ public class JUnitTask extends Task { | |||||
cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); | cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); | ||||
cmd.createArgument().setValue(test.getName()); | cmd.createArgument().setValue(test.getName()); | ||||
cmd.createArgument().setValue("filtertrace=" + test.getFiltertrace()); | |||||
cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror()); | cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror()); | ||||
cmd.createArgument().setValue("haltOnFailure=" + test.getHaltonfailure()); | cmd.createArgument().setValue("haltOnFailure=" + test.getHaltonfailure()); | ||||
if (summary) { | if (summary) { | ||||
@@ -555,8 +572,7 @@ public class JUnitTask extends Task { | |||||
// will cause trouble in JDK 1.1 if omitted | // will cause trouble in JDK 1.1 if omitted | ||||
cl.addSystemPackageRoot("org.apache.tools.ant"); | cl.addSystemPackageRoot("org.apache.tools.ant"); | ||||
} | } | ||||
runner = new JUnitTestRunner(test, test.getHaltonerror(), test.getHaltonfailure(), cl); | |||||
runner = new JUnitTestRunner(test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), cl); | |||||
if (summary) { | if (summary) { | ||||
log("Running " + test.getName(), Project.MSG_INFO); | log("Running " + test.getName(), Project.MSG_INFO); | ||||
@@ -100,10 +100,11 @@ public class JUnitTest extends BaseTest { | |||||
this.name = name; | this.name = name; | ||||
} | } | ||||
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure) { | |||||
public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, boolean filtertrace) { | |||||
this.name = name; | this.name = name; | ||||
this.haltOnError = haltOnError; | this.haltOnError = haltOnError; | ||||
this.haltOnFail = haltOnFail; | |||||
this.haltOnFail = haltOnFailure; | |||||
this.filtertrace = filtertrace; | |||||
} | } | ||||
/** | /** | ||||
@@ -64,7 +64,11 @@ import junit.framework.Test; | |||||
import junit.framework.TestSuite; | import junit.framework.TestSuite; | ||||
import junit.framework.AssertionFailedError; | import junit.framework.AssertionFailedError; | ||||
import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||
import java.io.BufferedReader; | |||||
import java.io.PrintStream; | import java.io.PrintStream; | ||||
import java.io.PrintWriter; | |||||
import java.io.StringReader; | |||||
import java.io.StringWriter; | |||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
@@ -121,6 +125,24 @@ public class JUnitTestRunner implements TestListener { | |||||
*/ | */ | ||||
private TestResult res; | private TestResult res; | ||||
/** | |||||
* Do we filter junit.*.* stack frames out of failure and error exceptions. | |||||
*/ | |||||
private static boolean filtertrace = true; | |||||
private final static String[] DEFAULT_TRACE_FILTERS = new String[] { | |||||
"junit.framework.TestCase", | |||||
"junit.framework.TestResult", | |||||
"junit.framework.TestSuite", | |||||
"junit.framework.Assert.", // don't filter AssertionFailure | |||||
"junit.swingui.TestRunner", | |||||
"junit.awtui.TestRunner", | |||||
"junit.textui.TestRunner", | |||||
"java.lang.reflect.Method.invoke(", | |||||
"org.apache.tools.ant." | |||||
}; | |||||
/** | /** | ||||
* Do we stop on errors. | * Do we stop on errors. | ||||
*/ | */ | ||||
@@ -161,16 +183,18 @@ public class JUnitTestRunner implements TestListener { | |||||
* Constructor for fork=true or when the user hasn't specified a | * Constructor for fork=true or when the user hasn't specified a | ||||
* classpath. | * classpath. | ||||
*/ | */ | ||||
public JUnitTestRunner(JUnitTest test, boolean haltOnError, | |||||
public JUnitTestRunner(JUnitTest test, boolean haltOnError, boolean filtertrace, | |||||
boolean haltOnFailure) { | boolean haltOnFailure) { | ||||
this(test, haltOnError, haltOnFailure, null); | |||||
this(test, haltOnError, filtertrace, haltOnFailure, null); | |||||
} | } | ||||
/** | /** | ||||
* Constructor to use when the user has specified a classpath. | * Constructor to use when the user has specified a classpath. | ||||
*/ | */ | ||||
public JUnitTestRunner(JUnitTest test, boolean haltOnError, | |||||
public JUnitTestRunner(JUnitTest test, boolean haltOnError, boolean filtertrace, | |||||
boolean haltOnFailure, ClassLoader loader) { | boolean haltOnFailure, ClassLoader loader) { | ||||
//JUnitTestRunner.filtertrace = filtertrace; | |||||
this.filtertrace = filtertrace; | |||||
this.junitTest = test; | this.junitTest = test; | ||||
this.haltOnError = haltOnError; | this.haltOnError = haltOnError; | ||||
this.haltOnFailure = haltOnFailure; | this.haltOnFailure = haltOnFailure; | ||||
@@ -378,6 +402,7 @@ public class JUnitTestRunner implements TestListener { | |||||
boolean exitAtEnd = true; | boolean exitAtEnd = true; | ||||
boolean haltError = false; | boolean haltError = false; | ||||
boolean haltFail = false; | boolean haltFail = false; | ||||
boolean stackfilter = true; | |||||
Properties props = new Properties(); | Properties props = new Properties(); | ||||
if (args.length == 0) { | if (args.length == 0) { | ||||
@@ -390,6 +415,8 @@ public class JUnitTestRunner implements TestListener { | |||||
haltError = Project.toBoolean(args[i].substring(12)); | haltError = Project.toBoolean(args[i].substring(12)); | ||||
} else if (args[i].startsWith("haltOnFailure=")) { | } else if (args[i].startsWith("haltOnFailure=")) { | ||||
haltFail = Project.toBoolean(args[i].substring(14)); | haltFail = Project.toBoolean(args[i].substring(14)); | ||||
} else if (args[i].startsWith("filtertrace=")) { | |||||
stackfilter = Project.toBoolean(args[i].substring(12)); | |||||
} else if (args[i].startsWith("formatter=")) { | } else if (args[i].startsWith("formatter=")) { | ||||
try { | try { | ||||
createAndStoreFormatter(args[i].substring(10)); | createAndStoreFormatter(args[i].substring(10)); | ||||
@@ -405,7 +432,7 @@ public class JUnitTestRunner implements TestListener { | |||||
} | } | ||||
JUnitTest t = new JUnitTest(args[0]); | JUnitTest t = new JUnitTest(args[0]); | ||||
// Add/overlay system properties on the properties from the Ant project | // Add/overlay system properties on the properties from the Ant project | ||||
Hashtable p = System.getProperties(); | Hashtable p = System.getProperties(); | ||||
for (Enumeration enum = p.keys(); enum.hasMoreElements(); ) { | for (Enumeration enum = p.keys(); enum.hasMoreElements(); ) { | ||||
@@ -414,7 +441,7 @@ public class JUnitTestRunner implements TestListener { | |||||
} | } | ||||
t.setProperties(props); | t.setProperties(props); | ||||
JUnitTestRunner runner = new JUnitTestRunner(t, haltError, haltFail); | |||||
JUnitTestRunner runner = new JUnitTestRunner(t, haltError, stackfilter, haltFail); | |||||
transferFormatters(runner); | transferFormatters(runner); | ||||
runner.run(); | runner.run(); | ||||
System.exit(runner.getRetCode()); | System.exit(runner.getRetCode()); | ||||
@@ -443,5 +470,53 @@ public class JUnitTestRunner implements TestListener { | |||||
} | } | ||||
fromCmdLine.addElement(fe.createFormatter()); | fromCmdLine.addElement(fe.createFormatter()); | ||||
} | } | ||||
/** | |||||
* Returns a filtered stack trace. | |||||
* This is ripped out of junit.runner.BaseTestRunner. | |||||
* Scott M. Stirling. | |||||
*/ | |||||
public static String getFilteredTrace(Throwable t) { | |||||
StringWriter stringWriter = new StringWriter(); | |||||
PrintWriter writer = new PrintWriter(stringWriter); | |||||
t.printStackTrace(writer); | |||||
StringBuffer buffer = stringWriter.getBuffer(); | |||||
String trace = buffer.toString(); | |||||
return JUnitTestRunner.filterStack(trace); | |||||
} | |||||
/** | |||||
* Filters stack frames from internal JUnit and Ant classes | |||||
*/ | |||||
public static String filterStack(String stack) { | |||||
if (!filtertrace) { | |||||
return stack; | |||||
} | |||||
StringWriter sw = new StringWriter(); | |||||
PrintWriter pw = new PrintWriter(sw); | |||||
StringReader sr = new StringReader(stack); | |||||
BufferedReader br = new BufferedReader(sr); | |||||
String line; | |||||
try { | |||||
while ((line = br.readLine()) != null) { | |||||
if (!filterLine(line)) { | |||||
pw.println(line); | |||||
} | |||||
} | |||||
} catch (Exception IOException) { | |||||
return stack; // return the stack unfiltered | |||||
} | |||||
return sw.toString(); | |||||
} | |||||
private static boolean filterLine(String line) { | |||||
for (int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++) { | |||||
if (line.indexOf(DEFAULT_TRACE_FILTERS[i]) > 0) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
} // JUnitTestRunner | } // JUnitTestRunner |
@@ -248,7 +248,8 @@ public class PlainJUnitResultFormatter implements JUnitResultFormatter { | |||||
wri.println(type); | wri.println(type); | ||||
wri.println(t.getMessage()); | wri.println(t.getMessage()); | ||||
t.printStackTrace(wri); | |||||
String strace = JUnitTestRunner.getFilteredTrace(t); | |||||
wri.print(strace); | |||||
wri.println(""); | wri.println(""); | ||||
} | } | ||||
} | } | ||||
@@ -261,9 +261,8 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstan | |||||
} | } | ||||
nested.setAttribute(ATTR_TYPE, t.getClass().getName()); | nested.setAttribute(ATTR_TYPE, t.getClass().getName()); | ||||
StringWriter swr = new StringWriter(); | |||||
t.printStackTrace(new PrintWriter(swr, true)); | |||||
Text trace = doc.createTextNode(swr.toString()); | |||||
String strace = JUnitTestRunner.getFilteredTrace(t); | |||||
Text trace = doc.createTextNode(strace); | |||||
nested.appendChild(trace); | nested.appendChild(trace); | ||||
} | } | ||||