/******************************************************************************* * Copyright (c) 2012 Google, Inc. * 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: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.runner; 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.util.Dictionary; import java.lang.reflect.Modifier; 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; /** * 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 * * COPIED from a class of the same name in the org.eclipse.test plugin. */ 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"; /** * The current test result */ private TestResult fTestResult; /** * The name of the plugin containing the test */ private String fTestPluginName; /** * The corresponding testsuite. */ private Test fSuite; /** * Formatters from the command line. */ private static Vector fgFromCmdLine= new Vector(); /** * Holds the registered formatters. */ private 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 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("-")) { className= args[0]; startArgs++; } } for (int i= startArgs; i < args.length; i++) { if (args[i].toLowerCase().equals("-classname")) { if (i < args.length-1) className= args[i+1]; i++; } else if (args[i].toLowerCase().equals("-testpluginname")) { if (i < args.length-1) testPluginName= args[i+1]; i++; } else 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("formatter=")) { try { createAndStoreFormatter(args[i].substring(10)); } catch (BuildException be) { System.err.println(be.getMessage()); return ERRORS; } } else if (args[i].startsWith("propsfile=")) { FileInputStream in = new FileInputStream(args[i].substring(10)); props.load(in); in.close(); } else if (args[i].equals("-testlistener")) { System.err.println("The -testlistener option is no longer supported\nuse the formatter= option instead"); return ERRORS; } } if (className == null) throw new IllegalArgumentException("Test class name not specified"); 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+"\""); 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"); 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()); return null; } catch (IllegalAccessException e) { runFailed("Failed to invoke suite():" + e.toString()); 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 + "\"")); } //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); } 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"); } 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"); } 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); } } }