/*******************************************************************************
* Copyright (c) 2008 Ketan Padegaonkar and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ketan Padegaonkar - initial API and implementation
*******************************************************************************/
package org.eclipse.swtbot.eclipse.junit3.headless;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
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.taskdefs.optional.junit.JUnitResultFormatter;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
/**
* An exact copy of {@link org.eclipse.test.EclipseTestRunner}. A TestRunner for JUnit that supports Ant
* JUnitResultFormatters and running tests inside Eclipse. Example call: EclipseTestRunner -classname
* junit.samples.SimpleTest formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter
*/
@SuppressWarnings("all")
public class EclipseTestRunner implements TestListener {
class TestFailedException extends Exception {
private static final long serialVersionUID = 6009335074727417445L;
TestFailedException(String message) {
super(message);
}
TestFailedException(Throwable e) {
super(e);
}
}
/**
* No problems with this test.
*/
public static final int SUCCESS = 0;
/**
* Some tests failed.
*/
public static final int FAILURES = 1;
/**
* An error occured.
*/
public static final int ERRORS = 2;
private static final String SUITE_METHODNAME = "suite"; //$NON-NLS-1$
/**
* The current test result
*/
private TestResult fTestResult;
/**
* The name of the plugin containing the test
*/
private final String fTestPluginName;
/**
* The corresponding testsuite.
*/
private Test fSuite;
/**
* Formatters from the command line.
*/
private static Vector fgFromCmdLine = new Vector();
/**
* Holds the registered formatters.
*/
private final Vector formatters = new Vector();
/**
* Do we stop on errors.
*/
private boolean fHaltOnError = false;
/**
* Do we stop on test failures.
*/
private boolean fHaltOnFailure = false;
/**
* The TestSuite we are currently running.
*/
private final JUnitTest fJunitTest;
/**
* output written during the test
*/
private PrintStream fSystemError;
/**
* Error output during the test
*/
private PrintStream fSystemOut;
/**
* Exception caught in constructor.
*/
private Exception fException;
/**
* Returncode
*/
private int fRetCode = SUCCESS;
/**
* The main entry point (the parameters are not yet consistent with the Ant JUnitTestRunner, but eventually they
* should be). Parameters
*
* <pre>
* -className: the name of the testSuite
* -testPluginName: the name of the containing plugin
* haltOnError: halt test on errors?
* haltOnFailure: halt test on failures?
* -testlistener listenerClass: deprecated
* print a warning that this option is deprecated
* formatter: a JUnitResultFormatter given as classname,filename.
* If filename is ommitted, System.out is assumed.
* </pre>
*/
public static void main(String[] args) throws IOException {
System.exit(run(args));
}
public static int run(String[] args) throws IOException {
String className = null;
String testPluginName = null;
boolean haltError = false;
boolean haltFail = false;
Properties props = new Properties();
int startArgs = 0;
if (args.length > 0)
// support the JUnit task commandline syntax where
// the first argument is the name of the test class
if (!args[0].startsWith("-")) { //$NON-NLS-1$
className = args[0];
startArgs++;
}
for (int i = startArgs; i < args.length; i++)
if (args[i].toLowerCase().equals("-classname")) { //$NON-NLS-1$
if (i < args.length - 1)
className = args[i + 1];
i++;
} else if (args[i].toLowerCase().equals("-testpluginname")) { //$NON-NLS-1$
if (i < args.length - 1)
testPluginName = args[i + 1];
i++;
} else if (args[i].startsWith("haltOnError=")) //$NON-NLS-1$
haltError = Project.toBoolean(args[i].substring(12));
else if (args[i].startsWith("haltOnFailure=")) //$NON-NLS-1$
haltFail = Project.toBoolean(args[i].substring(14));
else if (args[i].startsWith("formatter=")) //$NON-NLS-1$
try {
createAndStoreFormatter(args[i].substring(10));
} catch (BuildException be) {
System.err.println(be.getMessage());
return ERRORS;
}
else if (args[i].startsWith("propsfile=")) { //$NON-NLS-1$
FileInputStream in = new FileInputStream(args[i].substring(10));
props.load(in);
in.close();
} else if (args[i].equals("-testlistener")) { //$NON-NLS-1$
System.err.println("The -testlistener option is no longer supported\nuse the formatter= option instead"); //$NON-NLS-1$
return ERRORS;
}
if (className == null)
throw new IllegalArgumentException("Test class name not specified"); //$NON-NLS-1$
JUnitTest t = new JUnitTest(className);
// 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);
EclipseTestRunner runner = new EclipseTestRunner(t, testPluginName, haltError, haltFail);
transferFormatters(runner);
runner.run();
return runner.getRetCode();
}
/**
*
*/
public EclipseTestRunner(JUnitTest test, String testPluginName, boolean haltOnError, boolean haltOnFailure) {
fJunitTest = test;
fTestPluginName = testPluginName;
fHaltOnError = haltOnError;
fHaltOnFailure = haltOnFailure;
try {
fSuite = getTest(test.getName());
} catch (Exception e) {
fRetCode = ERRORS;
fException = e;
}
}
/**
* Returns the Test corresponding to the given suite.
*/
protected Test getTest(String suiteClassName) throws TestFailedException {
if (suiteClassName.length() <= 0) {
clearStatus();
return null;
}
Class testClass = null;
try {
testClass = loadSuiteClass(suiteClassName);
} catch (ClassNotFoundException e) {
if (e.getCause() != null)
runFailed(e.getCause());
String clazz = e.getMessage();
if (clazz == null)
clazz = suiteClassName;
runFailed("Class not found \"" + clazz + "\""); //$NON-NLS-1$ //$NON-NLS-2$
return null;
} catch (Exception e) {
runFailed(e);
return null;
}
Method suiteMethod = null;
try {
suiteMethod = testClass.getMethod(SUITE_METHODNAME, new Class[0]);
} catch (Exception e) {
// try to extract a test suite automatically
clearStatus();
return new TestSuite(testClass);
}
if (!Modifier.isStatic(suiteMethod.getModifiers())) {
runFailed("suite() method must be static"); //$NON-NLS-1$
return null;
}
Test test = null;
try {
test = (Test) suiteMethod.invoke(null, new Class[0]); // static method
if (test == null)
return test;
} catch (InvocationTargetException e) {
runFailed("Failed to invoke suite():" + e.getTargetException().toString()); //$NON-NLS-1$
return null;
} catch (IllegalAccessException e) {
runFailed("Failed to invoke suite():" + e.toString()); //$NON-NLS-1$
return null;
}
clearStatus();
return test;
}
protected void runFailed(String message) throws TestFailedException {
System.err.println(message);
throw new TestFailedException(message);
}
protected void runFailed(Throwable e) throws TestFailedException {
e.printStackTrace();
throw new TestFailedException(e);
}
protected void clearStatus() {
}
/**
* Loads the class either with the system class loader or a plugin class loader if a plugin name was specified
*/
protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
if (fTestPluginName == null)
return Class.forName(suiteClassName);
Bundle bundle = Platform.getBundle(fTestPluginName);
if (bundle == null)
throw new ClassNotFoundException(suiteClassName, new Exception("Could not find plugin \"" + fTestPluginName + "\"")); //$NON-NLS-1$ //$NON-NLS-2$
// is the plugin a fragment?
Dictionary headers = bundle.getHeaders();
String hostHeader = (String) headers.get(Constants.FRAGMENT_HOST);
if (hostHeader != null) {
// we are a fragment for sure
// we need to find which is our host
ManifestElement[] hostElement = null;
try {
hostElement = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, hostHeader);
} catch (BundleException e) {
throw new RuntimeException("Could not find host for fragment:" + fTestPluginName, e); //$NON-NLS-1$
}
Bundle host = Platform.getBundle(hostElement[0].getValue());
// we really want to get the host not the fragment
bundle = host;
}
return bundle.loadClass(suiteClassName);
}
public void run() {
// IPerformanceMonitor pm = PerfMsrCorePlugin.getPerformanceMonitor(true);
fTestResult = new TestResult();
fTestResult.addListener(this);
for (int i = 0; i < formatters.size(); i++)
fTestResult.addListener((TestListener) formatters.elementAt(i));
long start = System.currentTimeMillis();
fireStartTestSuite();
if (fException != null) { // had an exception in the constructor
for (int i = 0; i < formatters.size(); i++)
((TestListener) formatters.elementAt(i)).addError(null, fException);
fJunitTest.setCounts(1, 0, 1);
fJunitTest.setRunTime(0);
} else {
ByteArrayOutputStream errStrm = new ByteArrayOutputStream();
fSystemError = new PrintStream(errStrm);
ByteArrayOutputStream outStrm = new ByteArrayOutputStream();
fSystemOut = new PrintStream(outStrm);
try {
// pm.snapshot(1); // before
fSuite.run(fTestResult);
} finally {
// pm.snapshot(2); // after
fSystemError.close();
fSystemError = null;
fSystemOut.close();
fSystemOut = null;
sendOutAndErr(new String(outStrm.toByteArray()), new String(errStrm.toByteArray()));
fJunitTest.setCounts(fTestResult.runCount(), fTestResult.failureCount(), fTestResult.errorCount());
fJunitTest.setRunTime(System.currentTimeMillis() - start);
}
}
fireEndTestSuite();
if ((fRetCode != SUCCESS) || (fTestResult.errorCount() != 0))
fRetCode = ERRORS;
else if (fTestResult.failureCount() != 0)
fRetCode = FAILURES;
// pm.upload(getClass().getName());
}
/**
* 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 fRetCode;
}
/*
* @see TestListener.addFailure
*/
public void startTest(Test t) {
}
/*
* @see TestListener.addFailure
*/
public void endTest(Test test) {
}
/*
* @see TestListener.addFailure
*/
public void addFailure(Test test, AssertionFailedError t) {
if (fHaltOnFailure)
fTestResult.stop();
}
/*
* @see TestListener.addError
*/
public void addError(Test test, Throwable t) {
if (fHaltOnError)
fTestResult.stop();
}
private void fireStartTestSuite() {
for (int i = 0; i < formatters.size(); i++)
((JUnitResultFormatter) formatters.elementAt(i)).startTestSuite(fJunitTest);
}
private void fireEndTestSuite() {
for (int i = 0; i < formatters.size(); i++)
((JUnitResultFormatter) formatters.elementAt(i)).endTestSuite(fJunitTest);
}
public void addFormatter(JUnitResultFormatter f) {
formatters.addElement(f);
}
/**
* Line format is: formatter=<classname>(,<pathname>)?
*/
private static void createAndStoreFormatter(String line) throws BuildException {
String formatterClassName = null;
File formatterFile = null;
int pos = line.indexOf(',');
if (pos == -1)
formatterClassName = line;
else {
formatterClassName = line.substring(0, pos);
formatterFile = new File(line.substring(pos + 1)); // the method is package visible
}
fgFromCmdLine.addElement(createFormatter(formatterClassName, formatterFile));
}
private static void transferFormatters(EclipseTestRunner runner) {
for (int i = 0; i < fgFromCmdLine.size(); i++)
runner.addFormatter((JUnitResultFormatter) fgFromCmdLine.elementAt(i));
}
/*
* DUPLICATED from FormatterElement, since it is package visible only
*/
private static JUnitResultFormatter createFormatter(String classname, File outfile) throws BuildException {
OutputStream out = System.out;
if (classname == null)
throw new BuildException("you must specify type or classname"); //$NON-NLS-1$
Class f = null;
try {
f = EclipseTestRunner.class.getClassLoader().loadClass(classname);
} catch (ClassNotFoundException e) {
throw new BuildException(e);
}
Object o = null;
try {
o = f.newInstance();
} catch (InstantiationException e) {
throw new BuildException(e);
} catch (IllegalAccessException e) {
throw new BuildException(e);
}
if (!(o instanceof JUnitResultFormatter))
throw new BuildException(classname + " is not a JUnitResultFormatter"); //$NON-NLS-1$
JUnitResultFormatter r = (JUnitResultFormatter) o;
if (outfile != null)
try {
out = new FileOutputStream(outfile);
} catch (java.io.IOException e) {
throw new BuildException(e);
}
r.setOutput(out);
return r;
}
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);
}
}
protected void handleOutput(String line) {
if (fSystemOut != null)
fSystemOut.println(line);
}
protected void handleErrorOutput(String line) {
if (fSystemError != null)
fSystemError.println(line);
}
}