/*
* Copyright 2000-2005 The Apache Software Foundation
*
* Licensed 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.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Permissions;
import org.apache.tools.ant.util.StringUtils;
import org.apache.tools.ant.util.TeeOutputStream;
/**
* 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.
*
* @since Ant 1.2
*/
public class JUnitTestRunner implements TestListener {
/**
* No problems with this test.
*/
public static final int SUCCESS = 0;
/**
* Some tests failed.
*/
public static final int FAILURES = 1;
/**
* An error occurred.
*/
public static final int ERRORS = 2;
/**
* Used in formatter arguments as a placeholder for the basename
* of the output file (which gets replaced by a test specific
* output file name later).
*
* @since Ant 1.6.3
*/
public static final String IGNORED_FILE_NAME = "IGNORETHIS";
/**
* 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;
/**
* Do we send output to System.out/.err in addition to the formatters?
*/
private boolean showOutput = false;
/**
* The permissions set for the test to run.
*/
private Permissions perm = null;
private static final 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(",
"sun.reflect.",
"org.apache.tools.ant."
};
/**
* Do we stop on errors.
*/
private boolean haltOnError = false;
/**
* Do we stop on test failures.
*/
private boolean haltOnFailure = false;
/**
* 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;
/** is this runner running in forked mode? */
private boolean forked = false;
/** Running more than one test suite? */
private static boolean multipleTests = false;
/** ClassLoader passed in in non-forked mode. */
private ClassLoader loader;
/**
* 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, false);
}
/**
* Constructor for fork=true or when the user hasn't specified a
* classpath.
*/
public JUnitTestRunner(JUnitTest test, boolean haltOnError,
boolean filtertrace, boolean haltOnFailure,
boolean showOutput) {
this(test, haltOnError, filtertrace, haltOnFailure, showOutput, null);
}
/**
* Constructor to use when the user has specified a classpath.
*/
public JUnitTestRunner(JUnitTest test, boolean haltOnError,
boolean filtertrace, boolean haltOnFailure,
ClassLoader loader) {
this(test, haltOnError, filtertrace, haltOnFailure, false, loader);
}
/**
* Constructor to use when the user has specified a classpath.
*/
public JUnitTestRunner(JUnitTest test, boolean haltOnError,
boolean filtertrace, boolean haltOnFailure,
boolean showOutput, ClassLoader loader) {
this.filtertrace = filtertrace;
this.junitTest = test;
this.haltOnError = haltOnError;
this.haltOnFailure = haltOnFailure;
this.showOutput = showOutput;
this.loader = loader;
}
public void run() {
res = new TestResult();
res.addListener(this);
for (int i = 0; i < formatters.size(); i++) {
res.addListener((TestListener) formatters.elementAt(i));
}
ByteArrayOutputStream errStrm = new ByteArrayOutputStream();
systemError = new PrintStream(errStrm);
ByteArrayOutputStream outStrm = new ByteArrayOutputStream();
systemOut = new PrintStream(outStrm);
PrintStream savedErr = null;
PrintStream savedOut = null;
if (forked) {
savedOut = System.out;
savedErr = System.err;
if (!showOutput) {
System.setOut(systemOut);
System.setErr(systemError);
} else {
System.setOut(new PrintStream(
new TeeOutputStream(savedOut, systemOut)
)
);
System.setErr(new PrintStream(
new TeeOutputStream(savedErr,
systemError)
)
);
}
perm = null;
} else {
if (perm != null) {
perm.setSecurityManager();
}
}
Test suite = null;
Throwable exception = null;
try {
try {
Class testClass = null;
if (loader == null) {
testClass = Class.forName(junitTest.getName());
} else {
testClass = Class.forName(junitTest.getName(), true,
loader);
}
Method suiteMethod = null;
try {
// check if there is a suite method
suiteMethod = testClass.getMethod("suite", new Class[0]);
} catch (NoSuchMethodException e) {
// 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
// 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 (Throwable e) {
retCode = ERRORS;
exception = e;
}
long start = System.currentTimeMillis();
fireStartTestSuite();
if (exception != null) { // had an exception constructing suite
for (int i = 0; i < formatters.size(); i++) {
((TestListener) formatters.elementAt(i))
.addError(null, exception);
}
junitTest.setCounts(1, 0, 1);
junitTest.setRunTime(0);
} else {
try {
suite.run(res);
} finally {
junitTest.setCounts(res.runCount(), res.failureCount(),
res.errorCount());
junitTest.setRunTime(System.currentTimeMillis() - start);
}
}
} finally {
if (perm != null) {
perm.restoreSecurityManager();
}
if (savedOut != null) {
System.setOut(savedOut);
}
if (savedErr != null) {
System.setErr(savedErr);
}
systemError.close();
systemError = null;
systemOut.close();
systemOut = null;
sendOutAndErr(new String(outStrm.toByteArray()),
new String(errStrm.toByteArray()));
}
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 <= 3.4.
*
* <p>A Test failed.
*/
public void addFailure(Test test, Throwable t) {
if (haltOnFailure) {
res.stop();
}
}
/**
* Interface TestListener for JUnit > 3.4.
*
* <p>A Test failed.
*/
public void addFailure(Test test, AssertionFailedError t) {
addFailure(test, (Throwable) t);
}
/**
* Interface TestListener.
*
* <p>An error occurred while running the test.
*/
public void addError(Test test, Throwable t) {
if (haltOnError) {
res.stop();
}
}
/**
* Permissions for the test run.
* @since Ant 1.6
* @param permissions
*/
public void setPermissions(Permissions permissions) {
perm = permissions;
}
protected void handleOutput(String output) {
if (systemOut != null) {
systemOut.print(output);
}
}
/**
* @see org.apache.tools.ant.Task#handleInput(byte[], int, int)
*
* @since Ant 1.6
*/
protected int handleInput(byte[] buffer, int offset, int length)
throws IOException {
return -1;
}
protected void handleErrorOutput(String output) {
if (systemError != null) {
systemError.print(output);
}
}
protected void handleFlush(String output) {
if (systemOut != null) {
systemOut.print(output);
}
}
protected void handleErrorFlush(String output) {
if (systemError != null) {
systemError.print(output);
}
}
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>
*
* <tr><td>showoutput</td><td>send output to System.err/.out as
* well as to the formatters?</td><td>false</td></tr>
*
* </table>
*/
public static void main(String[] args) throws IOException {
boolean haltError = false;
boolean haltFail = false;
boolean stackfilter = true;
Properties props = new Properties();
boolean showOut = false;
if (args.length == 0) {
System.err.println("required argument TestClassName missing");
System.exit(ERRORS);
}
if (args[0].startsWith("testsfile=")) {
multipleTests = true;
args[0] = args[0].substring(10 /* "testsfile=".length() */);
}
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();
} else if (args[i].startsWith("showoutput=")) {
showOut = Project.toBoolean(args[i].substring(11));
}
}
// Add/overlay system properties on the properties from the Ant project
Hashtable p = System.getProperties();
for (Enumeration e = p.keys(); e.hasMoreElements();) {
Object key = e.nextElement();
props.put(key, p.get(key));
}
int returnCode = SUCCESS;
if (multipleTests) {
try {
java.io.BufferedReader reader =
new java.io.BufferedReader(new java.io.FileReader(args[0]));
String testCaseName;
int code = 0;
boolean errorOccured = false;
boolean failureOccured = false;
String line = null;
while ((line = reader.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line, ",");
testCaseName = st.nextToken();
JUnitTest t = new JUnitTest(testCaseName);
t.setTodir(new File(st.nextToken()));
t.setOutfile(st.nextToken());
code = launch(t, haltError, stackfilter, haltFail,
showOut, props);
errorOccured = (code == ERRORS);
failureOccured = (code != SUCCESS);
if (errorOccured || failureOccured ) {
if ((errorOccured && haltError)
|| (failureOccured && haltFail)) {
System.exit(code);
} else {
if (code > returnCode) {
returnCode = code;
}
System.out.println("TEST " + t.getName()
+ " FAILED");
}
}
}
} catch(IOException e) {
e.printStackTrace();
}
} else {
returnCode = launch(new JUnitTest(args[0]), haltError,
stackfilter, haltFail, showOut, props);
}
System.exit(returnCode);
}
private static Vector fromCmdLine = new Vector();
private static void transferFormatters(JUnitTestRunner runner,
JUnitTest test) {
for (int i = 0; i < fromCmdLine.size(); i++) {
FormatterElement fe = (FormatterElement) fromCmdLine.elementAt(i);
if (multipleTests && fe.getUseFile()) {
File destFile =
new File(test.getTodir(),
test.getOutfile() + fe.getExtension());
fe.setOutfile(destFile);
}
runner.addFormatter(fe.createFormatter());
}
}
/**
* 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);
fe.setUseFile(false);
} else {
fe.setClassname(line.substring(0, pos));
fe.setUseFile(true);
if (!multipleTests) {
fe.setOutfile(new File(line.substring(pos + 1)));
} else {
int fName = line.indexOf(IGNORED_FILE_NAME);
if (fName > -1) {
fe.setExtension(line
.substring(fName
+ IGNORED_FILE_NAME.length()));
}
}
}
fromCmdLine.addElement(fe);
}
/**
* Returns a filtered stack trace.
* This is ripped out of junit.runner.BaseTestRunner.
*/
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;
}
/**
* @since Ant 1.6.2
*/
private static int launch(JUnitTest t, boolean haltError,
boolean stackfilter, boolean haltFail,
boolean showOut, Properties props) {
t.setProperties(props);
JUnitTestRunner runner =
new JUnitTestRunner(t, haltError, stackfilter, haltFail, showOut);
runner.forked = true;
transferFormatters(runner, t);
runner.run();
return runner.getRetCode();
}
} // JUnitTestRunner