diff options
Diffstat (limited to 'org.eclipse.swtbot.swt.finder.test/src/org/eclipse/swtbot/swt/finder/RunUIThreadRule.java')
-rw-r--r-- | org.eclipse.swtbot.swt.finder.test/src/org/eclipse/swtbot/swt/finder/RunUIThreadRule.java | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/org.eclipse.swtbot.swt.finder.test/src/org/eclipse/swtbot/swt/finder/RunUIThreadRule.java b/org.eclipse.swtbot.swt.finder.test/src/org/eclipse/swtbot/swt/finder/RunUIThreadRule.java new file mode 100644 index 00000000..2090e7aa --- /dev/null +++ b/org.eclipse.swtbot.swt.finder.test/src/org/eclipse/swtbot/swt/finder/RunUIThreadRule.java @@ -0,0 +1,150 @@ +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; + } + } + } +} |