| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Anthony Dahanne <anthony.dahanne@compuware.com> - enhance ETF to be able to launch several tests in several bundles - https://bugs.eclipse.org/330613 |
| *******************************************************************************/ |
| package org.eclipse.test; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| 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.net.URISyntaxException; |
| import java.net.URL; |
| import java.text.SimpleDateFormat; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| import java.util.Vector; |
| |
| 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.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.ImageData; |
| import org.eclipse.swt.graphics.ImageLoader; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| |
| import junit.framework.AssertionFailedError; |
| import junit.framework.Test; |
| import junit.framework.TestListener; |
| import junit.framework.TestResult; |
| |
| /** |
| * 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 |
| */ |
| 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); |
| } |
| } |
| |
| static class ThreadDump extends Exception { |
| |
| private static final long serialVersionUID = 1L; |
| |
| ThreadDump(String message) { |
| super(message); |
| } |
| } |
| |
| static class StreamForwarder extends Thread { |
| private InputStream fProcessOutput; |
| |
| private PrintStream fStream; |
| |
| public StreamForwarder(InputStream processOutput, PrintStream stream) { |
| fProcessOutput= processOutput; |
| fStream= stream; |
| } |
| |
| @Override |
| public void run() { |
| try (BufferedReader reader= new BufferedReader(new InputStreamReader(fProcessOutput))) { |
| String line= null; |
| while ((line= reader.readLine()) != null) { |
| fStream.println(line); |
| } |
| reader.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * 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"; |
| /** |
| * SECONDS_BEFORE_TIMEOUT_BUFFER is the time we allow ourselves to take |
| * stack traces, get a screen shot, delay "SECONDS_BETWEEN_DUMPS", then do |
| * it again. On current build machine, it takes about 30 seconds to do all |
| * that, so 2 minutes should be suffiencient time allowed for most machines. |
| * Though, should increase, say, if we increase the "time beteen dumps" to a |
| * minute or more. |
| */ |
| private static final int SECONDS_BEFORE_TIMEOUT_BUFFER = 120; |
| |
| /** |
| * SECONDS_BETWEEN_DUMPS is the time we wait from first to second dump of |
| * stack trace and screenshots. In most cases, this should suffice to |
| * determine if still busy doing something, or, hung, or waiting for user |
| * input. |
| */ |
| private static final int SECONDS_BETWEEN_DUMPS = 5; |
| |
| /** |
| * 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<JUnitResultFormatter> fgFromCmdLine = new Vector<>(); |
| /** |
| * Holds the registered formatters. |
| */ |
| private Vector<JUnitResultFormatter> 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 classesNames = null; |
| String testPluginName = null; |
| String testPluginsNames = null; |
| String formatterString = null; |
| String timeoutString = null; |
| String junitReportOutput = 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("-classesnames")) { |
| if (i < args.length - 1) |
| classesNames = 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].toLowerCase().equals("-testpluginsnames")) { |
| if (i < args.length - 1) |
| testPluginsNames = args[i + 1]; |
| i++; |
| } else if (args[i].equals("-junitReportOutput")) { |
| if (i < args.length - 1) |
| junitReportOutput = 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=")) { |
| formatterString = args[i].substring(10); |
| } else if (args[i].startsWith("propsfile=")) { |
| try (FileInputStream in = new FileInputStream(args[i].substring(10))) { |
| props.load(in); |
| } |
| } else if (args[i].equals("-testlistener")) { |
| System.err.println("The -testlistener option is no longer supported\nuse the formatter= option instead"); |
| return ERRORS; |
| } else if (args[i].equals("-timeout")) { |
| if (i < args.length - 1) |
| timeoutString = args[i + 1]; |
| i++; |
| } |
| } |
| // Add/overlay system properties on the properties from the Ant project |
| Hashtable<Object, Object> p = System.getProperties(); |
| for (Enumeration<Object> _enum = p.keys(); _enum.hasMoreElements();) { |
| Object key = _enum.nextElement(); |
| props.put(key, p.get(key)); |
| } |
| |
| if (timeoutString == null || timeoutString.isEmpty()) { |
| System.err.println("INFO: optional timeout was not specified."); |
| } else { |
| String timeoutScreenOutputDir = null; |
| if (junitReportOutput == null || junitReportOutput.isEmpty()) { |
| timeoutScreenOutputDir = "timeoutScreens"; |
| } else { |
| timeoutScreenOutputDir = junitReportOutput + "/timeoutScreens"; |
| } |
| System.err.println("INFO: timeoutScreenOutputDir: " + timeoutScreenOutputDir); |
| System.err.println("INFO: timeout: " + timeoutString); |
| startStackDumpTimeoutTimer(timeoutString, new File( |
| timeoutScreenOutputDir), className); |
| } |
| |
| if (testPluginsNames != null && classesNames != null) { |
| // we have several plugins to look tests for, let's parse their |
| // names |
| String[] testPlugins = testPluginsNames.split(","); |
| String[] suiteClasses = classesNames.split(","); |
| try { |
| createAndStoreFormatter(formatterString, suiteClasses); |
| } catch (BuildException be) { |
| System.err.println(be.getMessage()); |
| return ERRORS; |
| } |
| int returnCode = 0; |
| int j = 0; |
| for (String oneClassName : suiteClasses) { |
| JUnitTest t = new JUnitTest(oneClassName); |
| t.setProperties(props); |
| EclipseTestRunner runner = new EclipseTestRunner(t, testPlugins[j], |
| haltError, haltFail); |
| transferFormatters(runner, j); |
| runner.run(); |
| j++; |
| if (runner.getRetCode() != 0) { |
| returnCode = runner.getRetCode(); |
| } |
| } |
| return returnCode; |
| } |
| try { |
| createAndStoreFormatter(formatterString); |
| } catch (BuildException be) { |
| System.err.println(be.getMessage()); |
| return ERRORS; |
| } |
| if (className == null) |
| throw new IllegalArgumentException("Test class name not specified"); |
| |
| JUnitTest t = new JUnitTest(className); |
| |
| t.setProperties(props); |
| |
| EclipseTestRunner runner= new EclipseTestRunner(t, testPluginName, haltError, haltFail); |
| transferFormatters(runner); |
| runner.run(); |
| return runner.getRetCode(); |
| } |
| |
| /** |
| * Starts a timer that dumps interesting debugging information shortly |
| * before the given timeout expires. |
| * |
| * @param timeoutArg |
| * the -timeout argument from the command line |
| * @param outputDirectory |
| * where the test results end up |
| * @param classname |
| * the class that is running the tests suite |
| */ |
| private static void startStackDumpTimeoutTimer(final String timeoutArg, |
| final File outputDirectory, final String classname) { |
| try { |
| /* |
| * The delay (in ms) is the sum of - the expected time it took for |
| * launching the current VM and reaching this method - the time it |
| * will take to run the garbage collection and dump all the infos |
| * (twice) |
| */ |
| int delay = SECONDS_BEFORE_TIMEOUT_BUFFER * 1000; |
| |
| int timeout = Integer.parseInt(timeoutArg) - delay; |
| String time0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", |
| Locale.US).format(new Date()); |
| System.err.println("starting EclipseTestRunner Timer with timeout=" |
| + timeout + " at " + time0); |
| if (timeout > 0) { |
| new Timer("EclipseTestRunner Timer", true).schedule( |
| new TimerTask() { |
| |
| volatile boolean assumeUiThreadIsResponsive; |
| |
| @Override |
| public void run() { |
| assumeUiThreadIsResponsive = true; |
| dump(0); |
| try { |
| Thread.sleep(SECONDS_BETWEEN_DUMPS * 1000); |
| } catch (InterruptedException e) { |
| // continue |
| } |
| dump(SECONDS_BETWEEN_DUMPS); |
| } |
| |
| /** |
| * |
| * @param num |
| * num is purely a lable used in naming the screen capture files. |
| * By convention, we pass in 0 or "SECONDS_BETWEEN_DUMPS" just as |
| * a subtle reminder of how much time as elapsed. |
| * Thus, files end up with names similar to |
| * <classname>_screen0.png, <classname>_screem5.png |
| * in a directory named "timeoutScreens" under "results", |
| * such as .../results/linux.gtk.x86_64/timeoutScreens/ |
| */ |
| private void dump(final int num) { |
| // Time elapsed time to do each dump, so we'll |
| // know if/when we get too close to the 2 |
| // minutes we allow |
| long start = System.currentTimeMillis(); |
| |
| // Dump all stacks: |
| dumpStackTraces(num, System.err); |
| dumpStackTraces(num, System.out); // System.err could be blocked, see https://bugs.eclipse.org/506304 |
| |
| if (!dumpSwtDisplay(num)) { |
| String screenshotFile= getScreenshotFile(num); |
| dumpAwtScreenshot(screenshotFile); |
| } |
| |
| // Elapsed time in milliseconds |
| long elapsedTimeMillis = System |
| .currentTimeMillis() - start; |
| |
| // Print in seconds |
| float elapsedTimeSec = elapsedTimeMillis / 1000F; |
| System.err.println("INFO: Seconds to do dump " |
| + num + ": " + elapsedTimeSec); |
| } |
| |
| private void dumpStackTraces(int num, PrintStream out) { |
| out |
| .println("EclipseTestRunner almost reached timeout '" |
| + timeoutArg + "'."); |
| out.println("totalMemory: " |
| + Runtime.getRuntime().totalMemory()); |
| out.println("freeMemory (before GC): " |
| + Runtime.getRuntime().freeMemory()); |
| out.flush(); // bug 420258: flush aggressively, we could be low on memory |
| System.gc(); |
| out.println("freeMemory (after GC): " |
| + Runtime.getRuntime().freeMemory()); |
| String time = new SimpleDateFormat( |
| "yyyy-MM-dd HH:mm:ss Z", Locale.US) |
| .format(new Date()); |
| out.println("Thread dump " + num |
| + " at " + time + ":"); |
| out.flush(); |
| Map<Thread, StackTraceElement[]> stackTraces = Thread |
| .getAllStackTraces(); |
| for (Entry<Thread, StackTraceElement[]> entry : stackTraces |
| .entrySet()) { |
| String name = entry.getKey().getName(); |
| StackTraceElement[] stack = entry |
| .getValue(); |
| ThreadDump exception = new ThreadDump("for thread \"" + name + "\""); |
| exception.setStackTrace(stack); |
| exception.printStackTrace(); |
| } |
| out.flush(); |
| } |
| |
| String getScreenshotFile(final int num) { |
| if (!outputDirectory.exists()) { |
| outputDirectory.mkdirs(); |
| } |
| String filename = outputDirectory.getAbsolutePath() |
| + "/" |
| + classname |
| + "_screen" |
| + num |
| + ".png"; |
| return filename; |
| } |
| |
| @SuppressWarnings("deprecation") |
| private boolean dumpSwtDisplay(final int num) { |
| try { |
| final Display display = Display.getDefault(); |
| |
| if (!assumeUiThreadIsResponsive) { |
| String message = "trying to make UI thread respond"; |
| IllegalStateException toThrow = new IllegalStateException(message); |
| Thread t = display.getThread(); |
| // Initialize the cause. Its stack trace will be that of the current thread. |
| toThrow.initCause(new RuntimeException(message)); |
| // Set the stack trace to that of the target thread. |
| toThrow.setStackTrace(t.getStackTrace()); |
| // Stop the thread using the specified throwable. |
| try { |
| t.stop(toThrow); |
| } catch (UnsupportedOperationException e) { |
| // Thread#stop(Throwable) doesn't work any more in JDK 8. Try stop0: |
| try { |
| Method stop0 = Thread.class.getDeclaredMethod("stop0", Object.class); |
| stop0.setAccessible(true); |
| stop0.invoke(t, toThrow); |
| } catch (Exception e1) { |
| e1.printStackTrace(); |
| } |
| } |
| } |
| |
| assumeUiThreadIsResponsive = false; |
| |
| display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| assumeUiThreadIsResponsive= true; |
| |
| dumpDisplayState(System.err); |
| dumpDisplayState(System.out); // System.err could be blocked, see https://bugs.eclipse.org/506304 |
| |
| // Take a screenshot: |
| GC gc = new GC(display); |
| final Image image = new Image(display, |
| display.getBounds()); |
| gc.copyArea(image, 0, 0); |
| gc.dispose(); |
| |
| ImageLoader loader = new ImageLoader(); |
| loader.data = new ImageData[] { image |
| .getImageData() }; |
| String filename= getScreenshotFile(num); |
| loader.save(filename, SWT.IMAGE_PNG); |
| System.err.println("Screenshot saved to: " + filename); |
| System.out.println("Screenshot saved to: " + filename); |
| image.dispose(); |
| } |
| |
| private void dumpDisplayState(PrintStream out) { |
| // Dump focus control, parents, and |
| // shells: |
| Control focusControl = display |
| .getFocusControl(); |
| if (focusControl != null) { |
| out.println("FocusControl: "); |
| StringBuilder indent = new StringBuilder(" "); |
| do { |
| out.println(indent |
| .toString() |
| + focusControl); |
| focusControl = focusControl |
| .getParent(); |
| indent.append(" "); |
| } while (focusControl != null); |
| } |
| Shell[] shells = display.getShells(); |
| if (shells.length > 0) { |
| out.println("Shells: "); |
| for (int i = 0; i < shells.length; i++) { |
| Shell shell = shells[i]; |
| out.println((shell |
| .isVisible() ? " visible: " |
| : " invisible: ") |
| + shell); |
| } |
| } |
| out.flush(); // for bug 420258 |
| } |
| }); |
| return true; |
| } catch (SWTException e) { |
| e.printStackTrace(); |
| return false; |
| } |
| } |
| |
| }, timeout); |
| } else { |
| System.err |
| .println("EclipseTestRunner argument error: '-timeout " |
| + timeoutArg |
| + "' was too short to accommodate time delay required (" |
| + delay + ")."); |
| } |
| } catch (NumberFormatException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| protected Test getTest(String suiteClassName) throws TestFailedException { |
| if (suiteClassName.isEmpty()) { |
| 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); |
| } catch (Exception e) { |
| // try to extract a test suite automatically |
| clearStatus(); |
| return new junit.framework.JUnit4TestAdapter(testClass); |
| } |
| if (!Modifier.isStatic(suiteMethod.getModifiers())) { |
| runFailed("suite() method must be static"); |
| return null; |
| } |
| Test test = null; |
| try { |
| test = (Test) suiteMethod.invoke(null); // 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<String, String> headers = bundle.getHeaders(); |
| String hostHeader = 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(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++) { |
| 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; |
| } |
| |
| @Override |
| public void startTest(Test t) { |
| } |
| |
| @Override |
| public void endTest(Test test) { |
| } |
| |
| @Override |
| public void addFailure(Test test, AssertionFailedError t) { |
| if (fHaltOnFailure) { |
| fTestResult.stop(); |
| } |
| } |
| |
| @Override |
| public void addError(Test test, Throwable t) { |
| if (fHaltOnError) { |
| fTestResult.stop(); |
| } |
| } |
| |
| private void fireStartTestSuite() { |
| for (int i = 0; i < formatters.size(); i++) { |
| formatters.elementAt(i).startTestSuite(fJunitTest); |
| } |
| } |
| |
| private void fireEndTestSuite() { |
| for (int i = 0; i < formatters.size(); i++) { |
| 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)); |
| } |
| |
| /** |
| * Line format is: formatter=<pathname> |
| */ |
| private static void createAndStoreFormatter(String line, |
| String... suiteClassesNames) throws BuildException { |
| String formatterClassName = null; |
| File formatterFile = null; |
| |
| int pos = line.indexOf(','); |
| if (pos == -1) { |
| formatterClassName = line; |
| } else { |
| formatterClassName = line.substring(0, pos); |
| } |
| File outputDirectory = new File(line.substring(pos + 1)); |
| outputDirectory.mkdir(); |
| for (String suiteClassName : suiteClassesNames) { |
| |
| String pathname = "TEST-" + suiteClassName + ".xml"; |
| if (outputDirectory.exists()) { |
| pathname = outputDirectory.getAbsolutePath() + "/" + pathname; |
| } |
| formatterFile = new File(pathname); |
| fgFromCmdLine.addElement(createFormatter(formatterClassName, |
| formatterFile)); |
| } |
| |
| } |
| |
| private static void transferFormatters(EclipseTestRunner runner, int j) { |
| runner.addFormatter(fgFromCmdLine.elementAt(j)); |
| } |
| |
| private static void transferFormatters(EclipseTestRunner runner) { |
| for (int i = 0; i < fgFromCmdLine.size(); i++) { |
| runner.addFormatter(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.getDeclaredConstructor().newInstance(); |
| } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException |
| | NoSuchMethodException | SecurityException 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; |
| } |
| |
| public static void dumpAwtScreenshot(String screenshotFile) { |
| try { |
| URL location= AwtScreenshot.class.getProtectionDomain().getCodeSource().getLocation(); |
| String cp= location.toURI().getPath(); |
| String javaHome= System.getProperty("java.home"); |
| String javaExe= javaHome + File.separatorChar + "bin" + File.separatorChar + "java"; |
| if (File.separatorChar == '\\') { |
| javaExe+= ".exe"; // assume it's Windows |
| } |
| String[] args = new String[] { javaExe, "-cp", cp, AwtScreenshot.class.getName(), screenshotFile }; |
| System.err.println("Start process: " + Arrays.asList(args)); |
| ProcessBuilder processBuilder = new ProcessBuilder(args); |
| if ("Mac OS X".equals(System.getProperty("os.name"))) { |
| processBuilder.environment().put("AWT_TOOLKIT", "CToolkit"); |
| } |
| Process process= processBuilder.start(); |
| new StreamForwarder(process.getErrorStream(), System.err).start(); |
| new StreamForwarder(process.getInputStream(), System.err).start(); |
| int screenshotTimeout= 15; |
| long end= System.currentTimeMillis() + screenshotTimeout * 1000; |
| boolean done= false; |
| do { |
| try { |
| process.exitValue(); |
| done= true; |
| } catch (IllegalThreadStateException e) { |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e1) { |
| } |
| } |
| } while (!done && System.currentTimeMillis() < end); |
| |
| if (done) { |
| System.err.println("AwtScreenshot VM finished with exit code " + process.exitValue() + "."); |
| } else { |
| process.destroy(); |
| System.err.println("Killed AwtScreenshot VM after " + screenshotTimeout + " seconds."); |
| } |
| } catch (URISyntaxException|IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void sendOutAndErr(String out, String err) { |
| for (int i = 0; i < formatters.size(); i++) { |
| JUnitResultFormatter formatter = 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); |
| } |
| } |
| } |