/* * Created on 31.03.2005 * by Richard Birenheide (D035816) * * Copyright SAP AG 2005 */ package junit.extensions; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestResult; import junit.framework.TestSuite; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.SWTException; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** * A test suite for running PDE tests in a separate workbench job. * <p/> * Forks the entire test run to run in a workbench job.<br> * Intended for usage with Eclipse jUnit run/debug configurations. These * run normally in the UI thread which blocks on showing dialogs or popup menues. * Eclipse can recognize classes having a method <code>public static Test suite()</code> * Within that one can code:<br/><code><pre> * public static Test suite() { * ActiveWorkbenchTestSuite suite = new ActiveWorkbenchTestSuite("name"); * //Testclasses derived from org.junit.TestCase * suite.addTestSuite(FirstTestClass.class); * suite.addTestSuite(SecondTestClass.class); * . * . * . * return suite; * } * </pre> * </code> * @author Richard Birenheide (D035816) */ public class ActiveWorkbenchTestSuite extends TestSuite { /** * The family of this job. */ public static final String JOB_FAMILY = "WorkbenchTestSuite"; private volatile boolean testsFinished = false; /** * The job which runs this TestSuite. */ protected Job runner = null; /** * The display associated with this run. */ protected Display display; /** * The shell being active when starting this run. */ protected Shell rootShell; /** * Default constructor. * <p/> * The name associated with this class is given the Class name. */ public ActiveWorkbenchTestSuite() { super(ActiveWorkbenchTestSuite.class.getName()); } /** * Constructs with a test class. * <p/> * The name associated with this class is given the Class name. * @param theClass a test class. */ public ActiveWorkbenchTestSuite(Class theClass) { super(theClass, ActiveWorkbenchTestSuite.class.getName()); } /** * Constructs with a name containing no test. * <p/> * @param name the name. This name will be given to the separate thread running. */ public ActiveWorkbenchTestSuite(String name) { super (name); } /** * 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 ActiveWorkbenchTestSuite(Class theClass, String name) { super(theClass, name); } public 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 = this.display.getActiveShell(); this.runner = new Job(this.getName()) { public IStatus run(IProgressMonitor monitor) { try { ActiveWorkbenchTestSuite.this.suiteSetUp(); ActiveWorkbenchTestSuite.super.run(result); ActiveWorkbenchTestSuite.this.suiteTearDown(); return MultiStatus.OK_STATUS; } finally { ActiveWorkbenchTestSuite.this.testsFinished = true; display.wake(); } } public boolean belongsTo(Object family) { return (JOB_FAMILY.equals(family)); } }; this.runner.setSystem(true); this.configureJob(this.runner); this.runner.schedule(); waitUntilFinished(); } public void runTest(final Test test, final TestResult result) { try { // inlined due to limitation in VA/Java //ActiveTestSuite.super.runTest(test, result); test.run(result); } finally { ActiveWorkbenchTestSuite.this.runFinished(); } } private void waitUntilFinished() { int ctr = 0; while (!this.testsFinished) { try { if(!display.readAndDispatch()) display.sleep(); } //if an assertion happened inside an #syncExec(Runnable) or #asyncExec(Runnable) //this may lead to an SWTException thrown here. Therefore catch and rethrow //applicable Exception. catch (SWTException ex) { if (ex.throwable instanceof AssertionFailedError) { throw (AssertionFailedError) ex.throwable; } else { throw ex; } } if(ctr++%100000==0) System.err.print("."); } } /** * Closes all shells when finished. */ private void runFinished() { Runnable closeShells = new Runnable() { public void run() { if (!rootShell.isDisposed()) { //Close all blocking dialogs. Necessary, otherwise the junit //thread does not proceed. Shell[] shells = rootShell.getShells(); ActiveWorkbenchTestSuite.closeShells(shells); } //TODO close all open stuff eg. menues etc. } }; display.syncExec(closeShells); } /** * 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 entire tests within this suite. * <p/> * The method will run in the separate thread.<br/> * The default implementation does nothing. */ protected void suiteSetUp() {} /** * Runs a tear down after executing the entire tests within this suite. * <p/> * The method will run in the separate thread.<br/> * The default implementation does nothing. */ protected void suiteTearDown() {} /** * Possibility to configure the Job the tests will be run in. * <p/> * Note that this will run in the initiating thread prior to the job being * scheduled.<br/> * One should not schedule the job from here since the tests would then * be run twice.<br/> * The default implementation does nothing. * @param runner the Job running the tests. */ protected void configureJob(Job runner) {} /** * 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 runnable the Runnable to execute. */ public static void syncExec(Display display, Runnable runnable) { try { display.syncExec(runnable); } catch (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. * @param runnable the Runnable to execute. */ public static void asyncExec(Display display, Runnable runnable) { try { display.asyncExec(runnable); } catch (SWTException swtEx) { if (swtEx.throwable instanceof AssertionFailedError) { throw (AssertionFailedError) swtEx.throwable; } else { throw swtEx; } } } }