| /******************************************************************************* |
| * Copyright (c) 2010 SAP AG, Walldorf. |
| * 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: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.platform.discovery.testutils.utils.abbot;
|
|
|
| import java.awt.AWTException;
|
| import java.awt.Robot;
|
| import java.util.Timer;
|
| import java.util.TimerTask;
|
| import java.util.concurrent.ExecutorService;
|
| import java.util.concurrent.Executors;
|
|
|
| import junit.framework.AssertionFailedError;
|
| import junit.framework.Test;
|
| import junit.framework.TestCase;
|
| import junit.framework.TestResult;
|
| import junit.framework.TestSuite;
|
|
|
| import org.eclipse.platform.discovery.testutils.internal.plugin.TestPlugin;
|
| import org.eclipse.platform.discovery.testutils.utils.abbot.util.TimerUtils;
|
| import org.eclipse.platform.discovery.testutils.utils.abbot.util.internal.UICleanupManager;
|
| import org.eclipse.swt.SWTException;
|
| import org.eclipse.swt.widgets.Display;
|
| import org.eclipse.swt.widgets.Shell;
|
|
|
|
|
| /**
|
| * Suite for UI tests in SWT.
|
| * <p>
|
| * Forks the test execution out of the UI thread in order to avoid that a test
|
| * is blocked because of modal dialogs. The suite itself has to be started in
|
| * the UI thread. Consequently one cannot nest <code>ActiveSWTTestSuite</code>s.
|
| * </p>
|
| * <p>
|
| * By default remaining dialogs will be closed by hitting escape a number of
|
| * times. You can change that behavior by calling
|
| * <code>setCloseShells(true)</code> in {@link #setUpPDE()} or earlier. As a
|
| * consequence all shells that were not present before the test run will be
|
| * closed by calling {@link Shell#close()}. This may however cause problems in
|
| * subsequent tests if a shell is meant to be reused (e.g. GEF holds a shell in
|
| * a static member).
|
| * </p>
|
| *
|
| * @author Richard Birenheide
|
| */
|
| public class ActiveSWTTestSuite extends TestSuite {
|
|
|
| private class RunTestSuiteTask extends TimerTask {
|
| private final TestResult result;
|
| private final ExecutorService service;
|
|
|
|
|
| public RunTestSuiteTask(TestResult result, ExecutorService service) {
|
| super();
|
| this.result = result;
|
| this.service = service;
|
| }
|
|
|
| @Override
|
| public void run() {
|
| this.result.addError(ActiveSWTTestSuite.this, new Throwable("Test execution terminated to avoid JDTD timeout"));
|
| try {
|
| ActiveSWTTestSuite.this.suiteTearDown();
|
| } catch (AssertionFailedError th) {
|
| this.result.addFailure(ActiveSWTTestSuite.this, th);
|
| } catch (Exception ex) {
|
| this.result.addError(ActiveSWTTestSuite.this, ex);
|
| }
|
| ActiveSWTTestSuite.this.testsFinished = true;
|
| ActiveSWTTestSuite.this.display.wake();
|
| service.shutdownNow();
|
| //System.exit(0);
|
| }
|
|
|
|
|
| }
|
|
|
| private final UICleanupManager cleanupManager = new UICleanupManager();
|
|
|
| // private final static long JDTD_TIME =
|
| // System.getProperty("testrun.activeTimeout")
|
| // == null ? 100000 :
|
| // Long.parseLong(System.getProperty("testrun.activeTimeout"));
|
|
|
| private volatile boolean testsFinished = false;
|
|
|
| /**
|
| * The display associated with this run.
|
| */
|
| protected Display display;
|
| /**
|
| * The shell being active when starting this run.
|
| */
|
| protected Shell rootShell;
|
|
|
| /**
|
| * Hold the number of tests in a test case that are already executed
|
| */
|
| protected int executedTestsCount;
|
|
|
| private TimerUtils timeController = TimerUtils.getInstance();
|
|
|
| // a little less, than 15 mins, defined by JDTD itself
|
| private final static long JDTD_TIMEOUT = 14 * 60 * 1000;
|
|
|
| public TimerUtils getTimeController() {
|
| return timeController;
|
| }
|
|
|
| public void setTimeController(TimerUtils timeController) {
|
| this.timeController = timeController;
|
| }
|
|
|
| /**
|
| * Default constructor.
|
| * <p/>
|
| * The name associated with this class is given the Class name.
|
| */
|
| public ActiveSWTTestSuite() {
|
| super(ActiveSWTTestSuite.class.getName());
|
| initJDTDExecutionTimeControlling();
|
| }
|
|
|
| /**
|
| * Constructs with a test class.
|
| * <p/>
|
| * The name associated with this class is given the Class name.
|
| *
|
| * @param theClass
|
| * a test class.
|
| */
|
| public ActiveSWTTestSuite(final Class<? extends TestCase> theClass) {
|
| super(theClass, ActiveSWTTestSuite.class.getName());
|
| initJDTDExecutionTimeControlling();
|
| }
|
|
|
| /**
|
| * Constructs with a name containing no test.
|
| * <p/>
|
| *
|
| * @param name
|
| * the name. This name will be given to the separate thread
|
| * running.
|
| */
|
| public ActiveSWTTestSuite(final String name) {
|
| super(name);
|
| initJDTDExecutionTimeControlling();
|
| }
|
|
|
| /**
|
| * Constructs with a name and containing the test class given.
|
| * <p/>
|
| *
|
| * @param theClass
|
| * a test class.
|
| * @param name
|
| * the name. This name will be given to the separate thread
|
| * running.
|
| */
|
| public ActiveSWTTestSuite(final Class<? extends TestCase> theClass, final String name) {
|
| super(theClass, name);
|
| initJDTDExecutionTimeControlling();
|
| }
|
|
|
| @Override
|
| public final void run(final TestResult result) {
|
| this.display = Display.getCurrent();
|
| if (this.display == null) {
|
| throw new IllegalStateException(
|
| "The TestSuite must be run from an SWT UI thread");
|
| }
|
| this.rootShell = display.getActiveShell();
|
| this.cleanupManager.registerUIState();
|
|
|
| final ExecutorService service = Executors.newSingleThreadScheduledExecutor();
|
| // handle JDTD timeout: stop the test before JDTD timeout is thrown
|
| Timer timeoutTimer = new Timer();
|
| TimerTask task = new RunTestSuiteTask(result, service);
|
| timeoutTimer.schedule(task, TimerUtils.getInstance().getRemainingTime());
|
| service.execute(new TestSuiteRunnerTask(result));
|
|
|
| waitUntilFinished();
|
|
|
| }
|
|
|
| private void initJDTDExecutionTimeControlling() {
|
| executedTestsCount = 0;
|
| if (this.timeController.getJDTD_TIME() == 0) {
|
| String jdtdProperty = System.getProperty("testrun.activeTimeout");
|
| this.timeController
|
| .setJDTD_TIME(jdtdProperty == null ? JDTD_TIMEOUT : Long
|
| .parseLong(jdtdProperty) * 1000);
|
| }
|
| System.out.println("******** Expected end of test run: "
|
| + this.timeController.formatNowTime(this.timeController
|
| .getJDTD_TIME()) + " ***********");
|
| this.timeController.setSTART_TIME(System.currentTimeMillis());
|
| }
|
|
|
| private boolean shouldSkipNextTestExecution() {
|
| long[] timeCheck = this.timeController.getRequiredTime(this
|
| .countTestCases(), this.executedTestsCount);
|
| return timeCheck[0] < timeCheck[1];
|
| }
|
|
|
| @Override
|
| public final void runTest(final Test test, final TestResult result) {
|
| try {
|
| // inlined due to limitation in VA/Java
|
| // ActiveSWTTestSuite.super.runTest(test, result);
|
| test.run(result);
|
| } finally {
|
| ActiveSWTTestSuite.this.runFinished();
|
| }
|
| }
|
|
|
| private void waitUntilFinished() {
|
| while (!this.testsFinished) {
|
| try {
|
| if (!display.readAndDispatch()) {
|
| display.sleep();
|
| }
|
| } catch (final SWTException ex) {
|
| TestPlugin
|
| .logError(
|
| "A SWTException ocurred during waiting for the tests being finished in thread: "
|
| + Thread.currentThread().getName(), ex);
|
| /*
|
| * Do nothing: rethrowing errors leads to premature end of the
|
| * WorkbenchTestable thread and the IDE subsequently not being
|
| * shut down.
|
| */
|
| } catch (final RuntimeException ex) {
|
| TestPlugin
|
| .logError(
|
| "A RuntimeException ocurred during waiting for the tests being finished in thread: "
|
| + Thread.currentThread().getName(), ex);
|
| }
|
| }
|
| }
|
|
|
| /**
|
| * Closes all shells when finished.
|
| */
|
| private void runFinished() {
|
|
|
| cleanupManager.cleanUp();
|
|
|
| }
|
|
|
| /**
|
| * Retrieves the display associated with this test run.
|
| * <p/>
|
| *
|
| * @return the display associated with this test run. Only valid after the
|
| * test has been started.
|
| */
|
| public Display getDisplay() {
|
| return this.display;
|
| }
|
|
|
| /**
|
| * Runs a set up prior to executing the entirety of the tests within this
|
| * suite.
|
| * <p>
|
| * The method will run called in the non-UI thread. The default
|
| * implementation does nothing.
|
| * </p>
|
| *
|
| * @throws Exception
|
| * convenience signature to ensure correct reporting on
|
| * Exceptions in set up.
|
| * {@link junit.framework.AssertionFailedError} is permissible
|
| * as well and will be reported accordingly as failure.
|
| */
|
| protected void suiteSetUp() throws Exception {
|
| }
|
|
|
| /**
|
| * Runs a tear down after executing all tests within this suite.
|
| * <p>
|
| * The method will run called in the non-UI thread. The default
|
| * implementation does nothing.
|
| * </p>
|
| *
|
| * @throws Exception
|
| * convenience signature to ensure correct reporting on
|
| * Exceptions in tear down.
|
| * {@link junit.framework.AssertionFailedError} is permissible
|
| * as well and will be reported accordingly as failure.
|
| */
|
| protected void suiteTearDown() throws Exception {
|
| }
|
|
|
| /**
|
| * Closes all shells and child shells of the given array recursively.
|
| * <p>
|
| * This is called after each TestCase to guarantee that no (blocking)
|
| * dialogs are still open. Does currently not work perfect and it is thus
|
| * highly recommended that this is done properly in TestCase.tearDown().
|
| *
|
| * @param shells
|
| * the shells to close.
|
| */
|
| public static void closeShells(final Shell[] shells) {
|
| for (int i = 0; i < shells.length; i++) {
|
| if (!shells[i].isDisposed()) {
|
| closeShells(shells[i].getShells());
|
| }
|
| if (!shells[i].isDisposed()) {
|
| shells[i].close();
|
| // shells[i].dispose();
|
| }
|
| }
|
| }
|
|
|
| /**
|
| * Convenience method for {@link Display#syncExec(java.lang.Runnable)}
|
| * catching {@link SWTException} and rethrowing {@link AssertionFailedError}
|
| * if appropriate.
|
| * <p>
|
| * Should be used from TestCase.testXXX() methods when asserting within the
|
| * SWT thread in order to guarantee that a test failure is displayed
|
| * correctly.
|
| *
|
| * @param display
|
| * the display to run the runnable in.
|
| * @param runnable
|
| * the Runnable to execute.
|
| * @throws AssertionFailedError
|
| * if an assertion failed in the display thread.
|
| * @throws RuntimeException
|
| * either a RuntimeException has been issued by the Runnable or
|
| * the Runnable has thrown a Throwable not being a
|
| * RuntimeException. In that case the RuntimeException carries
|
| * the original Exception as cause.
|
| */
|
| public static void syncExec(final Display display, final Runnable runnable) {
|
| try {
|
| display.syncExec(runnable);
|
| } catch (final SWTException swtEx) {
|
| if (swtEx.throwable instanceof AssertionFailedError) {
|
| throw (AssertionFailedError) swtEx.throwable;
|
| } else {
|
| throw swtEx;
|
| }
|
| }
|
| }
|
|
|
| /**
|
| * Convenience method for {@link Display#asyncExec(java.lang.Runnable)}
|
| * catching {@link SWTException} and rethrowing {@link AssertionFailedError}
|
| * if appropriate.
|
| * <p>
|
| * Should be used from TestCase.testXXX() methods when asserting within the
|
| * SWT thread in order to guarantee that a test failure is displayed
|
| * correctly.
|
| * <p/>
|
| * NOTE that exception handling with this method cannot be guaranteed to
|
| * work since exceptions are thrown asynchronously. Currently I have no idea
|
| * how to notify the caller of a test being failed. But generally I have no
|
| * idea why one should like to run _tests_ asynchronously. Possibly one
|
| * could introduce an ErrorListener here but I am not sure. Ideal would be
|
| * to have knowledge about the actual {@link Test} and {@link TestResult}
|
| * when this method is called. Then one could feed the result with
|
| * {@link TestResult#addError(junit.framework.Test, java.lang.Throwable)} or
|
| * {@link TestResult#addFailure(junit.framework.Test, junit.framework.AssertionFailedError)}
|
| * . Actually I do not know how to get the correct Test. Unfortunately it is
|
| * not the one issued by {@link #runTest(Test, TestResult)}.
|
| *
|
| * @param display
|
| * the display to run the runnable in.
|
| * @param runnable
|
| * the Runnable to execute.
|
| */
|
| public static void asyncExec(final Display display, final Runnable runnable) {
|
| try {
|
| display.asyncExec(runnable);
|
| } catch (final SWTException swtEx) {
|
| if (swtEx.throwable instanceof AssertionFailedError) {
|
| throw (AssertionFailedError) swtEx.throwable;
|
| } else {
|
| throw swtEx;
|
| }
|
| }
|
| }
|
|
|
| /**
|
| * Runs the test in the separate thread.
|
| * <p/>
|
| *
|
| * @author Richard Birenheide
|
| */
|
| private class TestSuiteRunnerTask implements Runnable {
|
|
|
| private final TestResult result;
|
|
|
| /**
|
| * Constructs with name and the TestResult given.
|
| * <p/>
|
| *
|
| * @param name
|
| * the threads name.
|
| * @param result
|
| * the test result.
|
| */
|
| private TestSuiteRunnerTask(final TestResult result) {
|
| this.result = result;
|
| // Prestart the AWT threads so that they will not be started in our
|
| // thread group
|
| try {
|
| final Robot r = new Robot();
|
| TestPlugin.logInfo("Robot created: " + r.toString());
|
| } catch (final AWTException ex) {
|
| TestPlugin.logError(ex);
|
| }
|
| }
|
|
|
| public void run() {
|
|
|
| // Safeguard the setting of the finish flag against errors.
|
| // Otherwise the Test may block infinitely.
|
| try {
|
| ActiveSWTTestSuite.this.suiteSetUp();
|
| if (!shouldSkipNextTestExecution()) {
|
| ActiveSWTTestSuite.super.run(this.result);
|
| } else {
|
| throw new TimeoutExceededException(
|
| "The remaining time is less than expected execution time");
|
| }
|
| } catch (final AssertionFailedError th) {
|
| this.result.addFailure(ActiveSWTTestSuite.this, th);
|
| } catch (final Exception ex) {
|
| this.result.addError(ActiveSWTTestSuite.this, ex);
|
| } finally {
|
| try {
|
| ActiveSWTTestSuite.this.suiteTearDown();
|
| } catch (final Exception ex) {
|
| this.result.addError(ActiveSWTTestSuite.this, ex);
|
| } catch (final AssertionFailedError th) {
|
| this.result.addFailure(ActiveSWTTestSuite.this, th);
|
| }
|
| ActiveSWTTestSuite.this.testsFinished = true;
|
| ActiveSWTTestSuite.this.display.wake();
|
| }
|
| }
|
| }
|
| }
|