package org.eclipse.swtbot.swt.finder; import java.util.List; import org.eclipse.swt.widgets.Display; import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; public class RunUIThreadRule implements MethodRule { private final Thread uiThread = Thread.currentThread(); private Thread testThread; private Display display; private boolean waitForDisplay = true; private Throwable testException; private final Object testObject; public RunUIThreadRule(Object testObject) { this.testObject = testObject; } public Statement apply(final Statement testStatement, FrameworkMethod method, final Object target) { return new Statement() { @Override public void evaluate() throws Throwable { // Fork Test Thread testThread = new Thread(createTestRunnable(testStatement), "test"); testThread.start(); // Run UI thread runUiThread(); } }; } private Runnable createTestRunnable(final Statement testStatement) { return new Runnable() { public void run() { runTestThread(testStatement); } }; } // Test Thread private void runTestThread(final Statement testStatement) { try { waitForDisplay(); waitForEventLoop(); testStatement.evaluate(); } catch (Throwable e) { testException = e; } finally { disposeDisplay(); } } private void waitForDisplay() throws InterruptedException { while ((display = Display.findDisplay(uiThread)) == null && waitForDisplay) { Thread.sleep(10); } if (display == null) { throw new RuntimeException("@UIThread methods need to create a Display!"); } } private void waitForEventLoop() { display.syncExec(new Runnable() { public void run() { // no-op, wait for sync } }); } private void disposeDisplay() { if (display != null && !display.isDisposed()) { display.syncExec(new Runnable() { public void run() { display.dispose(); } }); } } // UI Thread private void runUiThread() throws Throwable { try { // Run all methods annotated with @UIThread for (FrameworkMethod frameworkMethod : uiThreadMethods()) { frameworkMethod.invokeExplosively(testObject); } } finally { // Get the Display created by the @UIThread methods display = Display.getCurrent(); // If the UI thread never created a Display, we need to tell the // test thread to stop looking for it. if (display == null) { waitForDisplay = false; } // Running the event loop in the @UIThread method is optional. If // the test doesn't run it, we do. It can also happen that the // @UIThread method finishes with an undisposed display. In this // case we also need to run the event loop to dispose the // Display properly. runEventLoop(); } // Wait for the test thread to finish testThread.join(); // Rethrow any test exception that could not be thrown directly if (testException != null) { throw testException; } } private List<FrameworkMethod> uiThreadMethods() { return new TestClass(testObject.getClass()).getAnnotatedMethods(UIThread.class); } /** * Runs the event loop very carefully to make sure the Display can be disposed in every case. This can never throw * an Exception, if an exception bubbles up to the Event thread, it's only stored. */ private void runEventLoop() { while (display != null && !display.isDisposed()) { try { // Rescue Measure: if the test thread dies and leaves the // Display behind if (!testThread.isAlive()) { display.dispose(); } if (!display.readAndDispatch()) { display.sleep(); } } catch (Exception e) { // An Exception that occurs from the event loop is recorded. // It's not allowed to disrupt the event loop, because an event // loop needs to be present to properly dispose the Display. testException = e; } } } }