blob: d7b123a741e40af6f117034d4103eeebf7c62133 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.junit;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.util.StringUtils;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.framework.Test;
import junit.framework.TestSuite;
import junit.framework.AssertionFailedError;
import java.lang.reflect.Method;
import java.io.BufferedReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;
/**
* Simple Testrunner for JUnit that runs all tests of a testsuite.
*
* <p>This TestRunner expects a name of a TestCase class as its
* argument. If this class provides a static suite() method it will be
* called and the resulting Test will be run. So, the signature should be
* <pre><code>
* public static junit.framework.Test suite()
* </code></pre>
*
* <p> If no such method exists, all public methods starting with "test" and taking no
* argument will be run.
*
* <p> Summary output is generated at the end.
*
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @author <a href="mailto:ehatcher@apache.org">Erik Hatcher</a>
*/
public class JUnitTestRunner implements TestListener {
/**
* No problems with this test.
*/
public final static int SUCCESS = 0;
/**
* Some tests failed.
*/
public final static int FAILURES = 1;
/**
* An error occured.
*/
public final static int ERRORS = 2;
/**
* Holds the registered formatters.
*/
private Vector formatters = new Vector();
/**
* Collects TestResults.
*/
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.
*/
private boolean haltOnError = false;
/**
* Do we stop on test failures.
*/
private boolean haltOnFailure = false;
/**
* The corresponding testsuite.
*/
private Test suite = null;
/**
* Exception caught in constructor.
*/
private Exception exception;
/**
* Returncode
*/
private int retCode = SUCCESS;
/**
* The TestSuite we are currently running.
*/
private JUnitTest junitTest;
/** output written during the test */
private PrintStream systemError;
/** Error output during the test */
private PrintStream systemOut;
/**
* Constructor for fork=true or when the user hasn't specified a
* classpath.
*/
public JUnitTestRunner(JUnitTest test, boolean haltOnError, boolean filtertrace,
boolean haltOnFailure) {
this(test, haltOnError, filtertrace, haltOnFailure, null);
}
/**
* Constructor to use when the user has specified a classpath.
*/
public JUnitTestRunner(JUnitTest test, boolean haltOnError, boolean filtertrace,
boolean haltOnFailure, ClassLoader loader) {
this.filtertrace = filtertrace;
this.junitTest = test;
this.haltOnError = haltOnError;
this.haltOnFailure = haltOnFailure;
try {
Class testClass = null;
if (loader == null) {
testClass = Class.forName(test.getName());
} else {
testClass = loader.loadClass(test.getName());
AntClassLoader.initializeClass(testClass);
}
Method suiteMethod = null;
try {
// check if there is a suite method
suiteMethod= testClass.getMethod("suite", new Class[0]);
} catch(Exception e) {
// no appropriate suite method found. We don't report any
// error here since it might be perfectly normal. We don't
// know exactly what is the cause, but we're doing exactly
// the same as JUnit TestRunner do. We swallow the exceptions.
}
if (suiteMethod != null){
// if there is a suite method available, then try
// to extract the suite from it. If there is an error
// here it will be caught below and reported.
suite = (Test)suiteMethod.invoke(null, new Class[0]);
} else {
// try to extract a test suite automatically
// this will generate warnings if the class is no suitable Test
suite= new TestSuite(testClass);
}
} catch(Exception e) {
retCode = ERRORS;
exception = e;
}
}
public void run() {
res = new TestResult();
res.addListener(this);
for (int i=0; i < formatters.size(); i++) {
res.addListener((TestListener)formatters.elementAt(i));
}
long start = System.currentTimeMillis();
fireStartTestSuite();
if (exception != null) { // had an exception in the constructor
for (int i=0; i < formatters.size(); i++) {
((TestListener)formatters.elementAt(i)).addError(null,
exception);
}
junitTest.setCounts(1, 0, 1);
junitTest.setRunTime(0);
} else {
ByteArrayOutputStream errStrm = new ByteArrayOutputStream();
systemError = new PrintStream(errStrm);
ByteArrayOutputStream outStrm = new ByteArrayOutputStream();
systemOut = new PrintStream(outStrm);
try {
suite.run(res);
} finally {
systemError.close();
systemError = null;
systemOut.close();
systemOut = null;
sendOutAndErr(new String(outStrm.toByteArray()),
new String(errStrm.toByteArray()));
junitTest.setCounts(res.runCount(), res.failureCount(),
res.errorCount());
junitTest.setRunTime(System.currentTimeMillis() - start);
}
}
fireEndTestSuite();
if (retCode != SUCCESS || res.errorCount() != 0) {
retCode = ERRORS;
} else if (res.failureCount() != 0) {
retCode = FAILURES;
}
}
/**
* Returns what System.exit() would return in the standalone version.
*
* @return 2 if errors occurred, 1 if tests failed else 0.
*/
public int getRetCode() {
return retCode;
}
/**
* Interface TestListener.
*
* <p>A new Test is started.
*/
public void startTest(Test t) {}
/**
* Interface TestListener.
*
* <p>A Test is finished.
*/
public void endTest(Test test) {}
/**
* Interface TestListener for JUnit &lt;= 3.4.
*
* <p>A Test failed.
*/
public void addFailure(Test test, Throwable t) {
if (haltOnFailure) {
res.stop();
}
}
/**
* Interface TestListener for JUnit &gt; 3.4.
*
* <p>A Test failed.
*/
public void addFailure(Test test, AssertionFailedError t) {
addFailure(test, (Throwable) t);
}
/**
* Interface TestListener.
*
* <p>An error occured while running the test.
*/
public void addError(Test test, Throwable t) {
if (haltOnError) {
res.stop();
}
}
protected void handleOutput(String line) {
if (systemOut != null) {
systemOut.println(line);
}
}
protected void handleErrorOutput(String line) {
if (systemError != null) {
systemError.println(line);
}
}
private void sendOutAndErr(String out, String err) {
for (int i=0; i<formatters.size(); i++) {
JUnitResultFormatter formatter =
((JUnitResultFormatter)formatters.elementAt(i));
formatter.setSystemOutput(out);
formatter.setSystemError(err);
}
}
private void fireStartTestSuite() {
for (int i=0; i<formatters.size(); i++) {
((JUnitResultFormatter)formatters.elementAt(i)).startTestSuite(junitTest);
}
}
private void fireEndTestSuite() {
for (int i=0; i<formatters.size(); i++) {
((JUnitResultFormatter)formatters.elementAt(i)).endTestSuite(junitTest);
}
}
public void addFormatter(JUnitResultFormatter f) {
formatters.addElement(f);
}
/**
* Entry point for standalone (forked) mode.
*
* Parameters: testcaseclassname plus parameters in the format
* key=value, none of which is required.
*
* <table cols="4" border="1">
* <tr><th>key</th><th>description</th><th>default value</th></tr>
*
* <tr><td>haltOnError</td><td>halt test on
* errors?</td><td>false</td></tr>
*
* <tr><td>haltOnFailure</td><td>halt test on
* failures?</td><td>false</td></tr>
*
* <tr><td>formatter</td><td>A JUnitResultFormatter given as
* classname,filename. If filename is ommitted, System.out is
* assumed.</td><td>none</td></tr>
*
* </table>
*/
public static void main(String[] args) throws IOException {
boolean exitAtEnd = true;
boolean haltError = false;
boolean haltFail = false;
boolean stackfilter = true;
Properties props = new Properties();
if (args.length == 0) {
System.err.println("required argument TestClassName missing");
System.exit(ERRORS);
}
for (int i=1; i<args.length; i++) {
if (args[i].startsWith("haltOnError=")) {
haltError = Project.toBoolean(args[i].substring(12));
} else if (args[i].startsWith("haltOnFailure=")) {
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=")) {
try {
createAndStoreFormatter(args[i].substring(10));
} catch (BuildException be) {
System.err.println(be.getMessage());
System.exit(ERRORS);
}
} else if (args[i].startsWith("propsfile=")) {
FileInputStream in = new FileInputStream(args[i].substring(10));
props.load(in);
in.close();
}
}
JUnitTest t = new JUnitTest(args[0]);
// Add/overlay system properties on the properties from the Ant project
Hashtable p = System.getProperties();
for (Enumeration enum = p.keys(); enum.hasMoreElements(); ) {
Object key = enum.nextElement();
props.put(key, p.get(key));
}
t.setProperties(props);
JUnitTestRunner runner = new JUnitTestRunner(t, haltError, stackfilter, haltFail);
transferFormatters(runner);
runner.run();
System.exit(runner.getRetCode());
}
private static Vector fromCmdLine = new Vector();
private static void transferFormatters(JUnitTestRunner runner) {
for (int i=0; i<fromCmdLine.size(); i++) {
runner.addFormatter((JUnitResultFormatter) fromCmdLine.elementAt(i));
}
}
/**
* Line format is: formatter=<classname>(,<pathname>)?
*/
private static void createAndStoreFormatter(String line)
throws BuildException {
FormatterElement fe = new FormatterElement();
int pos = line.indexOf(',');
if (pos == -1) {
fe.setClassname(line);
} else {
fe.setClassname(line.substring(0, pos));
fe.setOutfile( new File(line.substring(pos + 1)) );
}
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) {
String trace = StringUtils.getStackTrace(t);
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