Fixes a sporadic RunStyleSelenium bug caused by not properly waiting for the URL to load before checking the title of the page. Also adds a thread lock to ensure that we don't get harmless but visible errors in the selenium console.
http://gwt-code-reviews.appspot.com/264801/show
Review by: mmendez@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7789 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/RunStyleSelenium.java b/user/src/com/google/gwt/junit/RunStyleSelenium.java
index d3259b7..afaa502 100644
--- a/user/src/com/google/gwt/junit/RunStyleSelenium.java
+++ b/user/src/com/google/gwt/junit/RunStyleSelenium.java
@@ -88,21 +88,25 @@
}
/**
- * The list of hosts that were interrupted.
+ * The list of hosts that were interrupted. Protected by {@link #lock}.
*/
private Set<String> interruptedHosts;
+ /**
+ * Indicates that testing has stopped, and we no longer need to run keep
+ * alive checks. Protected by {@link #lock}.
+ */
+ private boolean stopped;
+
private SeleniumWrapper remotes[];
/**
- * A separate lock to control access to {@link #interruptedHosts}. This keeps
- * the main thread calls into {@link #getInterruptedHosts()} from having to be
- * synchronized on the containing instance and potentially block on RPC calls.
- * It is okay to take the {@link #wasInterruptedLock} while locking the
- * containing instance; it is NOT okay to do the opposite or deadlock could
- * occur.
+ * A separate lock to control access to {@link Selenium}, {@link #stopped},
+ * {@link #remotes}, and {@link #interruptedHosts}. This ensures that the
+ * keepAlive thread doesn't call getTitle after the shutdown thread calls
+ * {@link Selenium#stop()}.
*/
- private final Object wasInterruptedLock = new Object();
+ private final Object lock = new Object();
public RunStyleSelenium(final JUnitShell shell) {
super(shell);
@@ -110,7 +114,7 @@
@Override
public String[] getInterruptedHosts() {
- synchronized (wasInterruptedLock) {
+ synchronized (lock) {
if (interruptedHosts == null) {
return null;
}
@@ -137,20 +141,28 @@
}
}
+ // We don't need a lock at this point because we haven't started the keep-
+ // alive thread.
this.remotes = targets;
// Install a shutdown hook that will close all of our outstanding Selenium
- // sessions.
+ // sessions. The hook is only executed if the JVM is exited normally. If the
+ // process is terminated, the shutdown hook will not run, which leaves
+ // browser instances open on the Selenium server. We'll need to modify
+ // Selenium Server to do its own cleanup after a timeout.
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
- for (SeleniumWrapper remote : remotes) {
- if (remote.getSelenium() != null) {
- try {
- remote.getSelenium().stop();
- } catch (SeleniumException se) {
- shell.getTopLogger().log(TreeLogger.WARN,
- "Error stopping selenium session", se);
+ synchronized (lock) {
+ stopped = true;
+ for (SeleniumWrapper remote : remotes) {
+ if (remote.getSelenium() != null) {
+ try {
+ remote.getSelenium().stop();
+ } catch (SeleniumException se) {
+ shell.getTopLogger().log(TreeLogger.WARN,
+ "Error stopping selenium session", se);
+ }
}
}
}
@@ -161,25 +173,27 @@
}
@Override
- public synchronized void launchModule(String moduleName) {
+ public void launchModule(String moduleName) {
// Get the localhost address.
String domain = "http://" + getLocalHostName() + ":" + shell.getPort()
+ "/";
// Startup all the selenia and point them at the module url.
- for (SeleniumWrapper remote : remotes) {
- try {
- String url = shell.getModuleUrl(moduleName);
- shell.getTopLogger().log(TreeLogger.TRACE,
- "Starting with domain: " + domain + " Opening URL: " + url);
- remote.createSelenium(domain);
- remote.getSelenium().start();
- remote.getSelenium().open(url);
- } catch (Exception e) {
- shell.getTopLogger().log(
- TreeLogger.ERROR,
- "Error launching browser via Selenium-RC at "
- + remote.getSpecifier(), e);
+ synchronized (lock) {
+ for (SeleniumWrapper remote : remotes) {
+ try {
+ String url = shell.getModuleUrl(moduleName);
+ shell.getTopLogger().log(TreeLogger.TRACE,
+ "Starting with domain: " + domain + " Opening URL: " + url);
+ remote.createSelenium(domain);
+ remote.getSelenium().start();
+ remote.getSelenium().open(url);
+ } catch (Exception e) {
+ shell.getTopLogger().log(
+ TreeLogger.ERROR,
+ "Error launching browser via Selenium-RC at "
+ + remote.getSpecifier(), e);
+ }
}
}
}
@@ -215,27 +229,38 @@
keepAliveThread.start();
}
- private synchronized boolean doKeepAlives() {
- if (remotes != null) {
- for (SeleniumWrapper remote : remotes) {
- // Use getTitle() as a cheap way to see if the Selenium server's still
- // responding (Selenium seems to provide no way to check the server
- // status directly).
- try {
- if (remote.getSelenium() != null) {
- remote.getSelenium().getTitle();
- }
- } catch (Throwable e) {
- synchronized (wasInterruptedLock) {
- if (interruptedHosts == null) {
- interruptedHosts = new HashSet<String>();
+ private boolean doKeepAlives() {
+ synchronized (lock) {
+ if (remotes != null) {
+ // If the shutdown thread has already executed, then we can stop this
+ // thread.
+ if (stopped) {
+ return false;
+ }
+
+ for (SeleniumWrapper remote : remotes) {
+ // Use getTitle() as a cheap way to see if the Selenium server's still
+ // responding (Selenium seems to provide no way to check the server
+ // status directly).
+ try {
+ if (remote.getSelenium() != null) {
+ remote.getSelenium().getTitle();
}
- interruptedHosts.add(remote.getSpecifier());
+ } catch (Throwable e) {
+ // If we ask for the title of the page while a new module is
+ // loading, IE will throw a permission denied exception.
+ String message = e.getMessage();
+ if (message != null
+ && message.toLowerCase().contains("permission denied")) {
+ if (interruptedHosts == null) {
+ interruptedHosts = new HashSet<String>();
+ }
+ interruptedHosts.add(remote.getSpecifier());
+ }
}
}
}
+ return interruptedHosts == null;
}
-
- return interruptedHosts == null;
}
}