Adding the host path to the error message when a remote browser dies during a test.  Also fixing an NPE that occurs when a RunStyle cannot be initialized.

Patch by: jlabanca
Review by: amitmanjhi




git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6707 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index 0137956..4c06882 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -75,7 +75,7 @@
   /**
    * The number of TestCase clients executing in parallel.
    */
-  private final int numClients;
+  private int numClients = 1;
 
   /**
    * Maps the TestInfo to the results from each clientId. If JUnitResult is
@@ -97,14 +97,8 @@
 
   /**
    * Only instantiable within this package.
-   * 
-   * @param numClients The number of parallel clients being served by this
-   *          queue.
    */
-  JUnitMessageQueue(int numClients) {
-    synchronized (clientStatusesLock) {
-      this.numClients = numClients;
-    }
+  JUnitMessageQueue() {
   }
 
   /**
@@ -428,6 +422,22 @@
     }
   }
 
+  /**
+   * Set the number of clients. This must be called before adding any tests.
+   * 
+   * @param numClients The number of parallel clients being served by this
+   *          queue.
+   */
+  void setNumClients(int numClients) {
+    synchronized (clientStatusesLock) {
+      if (testBlocks.size() > 0) {
+        throw new IllegalStateException(
+            "Cannot change number of clients after adding tests");
+      }
+      this.numClients = numClients;
+    }
+  }
+
   void waitForResults(int millis) {
     synchronized (clientStatusesLock) {
       try {
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index eeb8e9a..5af0e79 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -240,7 +240,8 @@
 
         @Override
         public boolean setString(String runStyleArg) {
-          return createRunStyle(runStyleArg);
+          runStyleName = runStyleArg;
+          return true;
         }
       });
 
@@ -568,11 +569,7 @@
       if (!argProcessor.processArgs(args)) {
         throw new JUnitFatalLaunchException("Error processing shell arguments");
       }
-      if (unitTestShell.runStyle == null) {
-        unitTestShell.createRunStyle("HtmlUnit");
-      }
-      unitTestShell.messageQueue = new JUnitMessageQueue(
-          unitTestShell.numClients);
+      unitTestShell.messageQueue = new JUnitMessageQueue();
 
       if (!unitTestShell.startUp()) {
         throw new JUnitFatalLaunchException("Shell failed to start");
@@ -670,6 +667,12 @@
    */
   private RunStyle runStyle = null;
 
+  /**
+   * The argument passed to -runStyle.  This is parsed later so we can pass in
+   * a logger.
+   */
+  private String runStyleName = "HtmlUnit";
+
   private boolean shouldAutoGenerateResources = true;
   
   private boolean standardsMode = false;
@@ -732,6 +735,11 @@
     if (!super.doStartup()) {
       return false;
     }
+    if (!createRunStyle(runStyleName)) {
+      // RunStyle already logged reasons for its failure
+      return false;
+    }
+
     if (!runStyle.setupMode(getTopLogger(), developmentMode)) {
       getTopLogger().log(TreeLogger.ERROR, "Run style does not support "
           + (developmentMode ? "development" : "production") + " mode");
@@ -816,8 +824,16 @@
               + "\n Actual time elapsed: " + elapsed + " seconds.\n");
     }
 
-    if (runStyle.wasInterrupted()) {
-      throw new TimeoutException("A remote browser died a mysterious death.");
+    // Check that we haven't lost communication with a remote host.
+    String[] interruptedHosts = runStyle.getInterruptedHosts();
+    if (interruptedHosts != null) {
+      StringBuilder msg = new StringBuilder();
+      msg.append("A remote browser died a mysterious death.\n");
+      msg.append("  We lost communication with:");
+      for (String host : interruptedHosts) {
+        msg.append("\n  ").append(host);
+      }
+      throw new TimeoutException(msg.toString());
     }
 
     if (messageQueue.hasResults(currentTestInfo)) {
@@ -884,6 +900,7 @@
    */
   void setNumClients(int numClients) {
     this.numClients = numClients;
+    messageQueue.setNumClients(numClients);
   }
 
   void setStandardsMode(boolean standardsMode) {
diff --git a/user/src/com/google/gwt/junit/RunStyle.java b/user/src/com/google/gwt/junit/RunStyle.java
index e6d7b94..2208d36 100644
--- a/user/src/com/google/gwt/junit/RunStyle.java
+++ b/user/src/com/google/gwt/junit/RunStyle.java
@@ -40,6 +40,15 @@
   }
 
   /**
+   * Tests whether the test was interrupted.
+   * 
+   * @return the interrupted hosts, or null if not interrupted
+   */
+  public String[] getInterruptedHosts() {
+    return null;
+  }
+
+  /**
    * Returns the number of times this test should be tried to run. A test
    * succeeds if it succeeds even once.
    */
@@ -91,15 +100,6 @@
   }
 
   /**
-   * Tests whether the test was interrupted.
-   * 
-   * @return <code>true</code> if the test has been interrupted.
-   */
-  public boolean wasInterrupted() {
-    return false;
-  }
-
-  /**
    * Gets the shell logger.
    */
   protected TreeLogger getLogger() {
diff --git a/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java b/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
index 73442d8..12239ca 100644
--- a/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
+++ b/user/src/com/google/gwt/junit/RunStyleExternalBrowser.java
@@ -18,6 +18,9 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Runs in web mode via browsers managed as an external process. This feature is
  * experimental and is not officially supported.
@@ -74,6 +77,26 @@
   }
 
   @Override
+  public String[] getInterruptedHosts() {
+    // Make sure all browsers are still running
+    Set<String> toRet = null;
+    for (ExternalBrowser browser : externalBrowsers) {
+      try {
+        browser.getProcess().exitValue();
+
+        // The host exited, so return its path.
+        if (toRet == null) {
+          toRet = new HashSet<String>();
+        }
+        toRet.add(browser.getPath());
+      } catch (IllegalThreadStateException e) {
+        // The process is still active, keep looking.
+      }
+    }
+    return toRet == null ? null : toRet.toArray(new String[toRet.size()]);
+  }
+
+  @Override
   public boolean initialize(String args) {
     if (args == null || args.length() == 0) {
       getLogger().log(TreeLogger.ERROR, "ExternalBrowser runstyle requires an "
@@ -82,6 +105,7 @@
       return false;
     }
     String browsers[] = args.split(",");
+    shell.setNumClients(browsers.length);
     synchronized (this) {
       this.externalBrowsers = new ExternalBrowser[browsers.length];
       for (int i = 0; i < browsers.length; ++i) {
@@ -118,20 +142,4 @@
       browser.setProcess(child);
     }
   }
-
-  @Override
-  public boolean wasInterrupted() {
-    
-    // Make sure all browsers are still running 
-    for (ExternalBrowser browser : externalBrowsers) {
-      try {
-        browser.getProcess().exitValue();
-      } catch (IllegalThreadStateException e) {
-        // The process is still active, keep looking.
-        continue;
-      }
-      return true;
-    }
-    return false;
-  }
 }
diff --git a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
index 795762d..e870a3a 100644
--- a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
@@ -27,6 +27,8 @@
 import java.net.SocketTimeoutException;
 import java.rmi.Naming;
 import java.rmi.server.RMISocketFactory;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Runs in web mode via browsers managed over RMI. This feature is experimental
@@ -134,13 +136,13 @@
   private RemoteBrowser[] remoteBrowsers;
 
   /**
-   * Whether one of the remote browsers was interrupted.
+   * The list of hosts that were interrupted.
    */
-  private boolean wasInterrupted;
+  private Set<String> interruptedHosts;
 
   /**
-   * A separate lock to control access to {@link #wasInterrupted}. This keeps
-   * the main thread calls into {@link #wasInterrupted()} from having to
+   * A separate lock to control access to {@link #interruptedHosts}. This keeps
+   * the main thread calls into {@link #getInterruptedHosts()} from having to
    * 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
@@ -154,7 +156,17 @@
   public RunStyleRemoteWeb(JUnitShell shell) {
     super(shell);
   }
-  
+
+  @Override
+  public String[] getInterruptedHosts() {
+    synchronized (wasInterruptedLock) {
+      if (interruptedHosts == null) {
+        return null;
+      }
+      return interruptedHosts.toArray(new String[interruptedHosts.size()]);
+    }
+  }
+
   @Override
   public boolean initialize(String args) {
     if (args == null || args.length() == 0) {
@@ -247,13 +259,6 @@
     keepAliveThread.start();
   }
 
-  @Override
-  public boolean wasInterrupted() {
-    synchronized (wasInterruptedLock) {
-      return wasInterrupted;
-    }
-  }
-
   private synchronized boolean doKeepAlives() {
     for (RemoteBrowser remoteBrowser : remoteBrowsers) {
       if (remoteBrowser.getToken() > 0) {
@@ -278,7 +283,10 @@
           }
           remoteBrowser.setToken(0);
           synchronized (wasInterruptedLock) {
-            wasInterrupted = true;
+            if (interruptedHosts == null) {
+              interruptedHosts = new HashSet<String>();
+            }
+            interruptedHosts.add(remoteBrowser.getRmiUrl());
           }
           break;
         }
@@ -286,7 +294,7 @@
     }
 
     synchronized (wasInterruptedLock) {
-      return !wasInterrupted;
+      return interruptedHosts == null;
     }
   }
 }
diff --git a/user/src/com/google/gwt/junit/RunStyleSelenium.java b/user/src/com/google/gwt/junit/RunStyleSelenium.java
index 1833c30..b1f883d 100644
--- a/user/src/com/google/gwt/junit/RunStyleSelenium.java
+++ b/user/src/com/google/gwt/junit/RunStyleSelenium.java
@@ -24,6 +24,8 @@
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -68,13 +70,13 @@
   private RCSelenium remotes[];
 
   /**
-   * Whether one of the remote browsers was interrupted.
+   * The list of hosts that were interrupted.
    */
-  private boolean wasInterrupted;
+  private Set<String> interruptedHosts;
 
   /**
-   * A separate lock to control access to {@link #wasInterrupted}. This keeps
-   * the main thread calls into {@link #wasInterrupted()} from having to be
+   * 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
@@ -85,7 +87,17 @@
   public RunStyleSelenium(final JUnitShell shell) {
     super(shell);
   }
-  
+
+  @Override
+  public String[] getInterruptedHosts() {
+    synchronized (wasInterruptedLock) {
+      if (interruptedHosts == null) {
+        return null;
+      }
+      return interruptedHosts.toArray(new String[interruptedHosts.size()]);
+    }
+  }
+
   @Override
   public boolean initialize(String args) {
     if (args == null || args.length() == 0) {
@@ -160,13 +172,6 @@
     }
   }
 
-  @Override
-  public boolean wasInterrupted() {
-    synchronized (wasInterruptedLock) {
-      return wasInterrupted;
-    }
-  }
-
   /**
    * Create the keep-alive thread.
    */
@@ -199,17 +204,17 @@
             remote.getSelenium().getTitle();
           }
         } catch (Throwable e) {
-          setWasInterrupted(true);
+          synchronized (wasInterruptedLock) {
+            if (interruptedHosts == null) {
+              interruptedHosts = new HashSet<String>();
+            }
+            interruptedHosts.add(remote.getHost() + ":" + remote.getPort()
+                + "/" + remote.getBrowser());
+          }
         }
       }
     }
 
-    return !wasInterrupted();
-  }
-
-  private void setWasInterrupted(boolean wasInterrupted) {
-    synchronized (wasInterruptedLock) {
-      this.wasInterrupted = wasInterrupted;
-    }
+    return interruptedHosts == null;
   }
 }
diff --git a/user/test/com/google/gwt/junit/CompileStrategyTest.java b/user/test/com/google/gwt/junit/CompileStrategyTest.java
index d04689c..df25b4a 100644
--- a/user/test/com/google/gwt/junit/CompileStrategyTest.java
+++ b/user/test/com/google/gwt/junit/CompileStrategyTest.java
@@ -63,7 +63,7 @@
     private boolean isLastBlock;
 
     public MockJUnitMessageQueue() {
-      super(1);
+      super();
     }
 
     @Override
diff --git a/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
index f8cd726..d7f7058 100644
--- a/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
+++ b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
@@ -36,7 +36,8 @@
   private final static int ONE_TEST_PER_BLOCK = 1;
 
   public void testAddTestBlocks() {
-    JUnitMessageQueue queue = new JUnitMessageQueue(10);
+    JUnitMessageQueue queue = new JUnitMessageQueue();
+    queue.setNumClients(10);
     assertEquals(0, queue.getTestBlocks().size());
     List<TestInfo[]> expectedBlocks = new ArrayList<TestInfo[]>();
 
@@ -458,6 +459,17 @@
     assertTrue(queue.getResults(testInfo).get("client0").getException() == null);
   }
 
+  public void testSetNumClients() {
+    JUnitMessageQueue queue = new JUnitMessageQueue();
+    queue.addTestBlocks(createTestBlocks(ONE_BLOCK, ONE_TEST_PER_BLOCK), true);
+    try {
+      queue.setNumClients(2);
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) {
+      // expected.
+    }
+  }
+
   /**
    * Assert that two arrays are the same size and contain the same elements.
    * Ordering does not matter.
@@ -489,7 +501,8 @@
    */
   private JUnitMessageQueue createQueue(int numClients, int numBlocks,
       int testsPerBlock) {
-    JUnitMessageQueue queue = new JUnitMessageQueue(numClients);
+    JUnitMessageQueue queue = new JUnitMessageQueue();
+    queue.setNumClients(numClients);
     queue.addTestBlocks(createTestBlocks(numBlocks, testsPerBlock), true);
     return queue;
   }