blob: 2090e7aa6f7c81f1ecccba9a30d2657765082d00 [file] [log] [blame]
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;
}
}
}
}