Added method-level timeouts to GWTTestCases, triggered by the need for 
handling linker bugs (which otherwise stalled indefinitely, until killed
by the 2hr <limit> in the ant test target).

Review by: spoon (pair prog.)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4108 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 373d004..e412721 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -88,6 +88,50 @@
   }
 
   /**
+   * Gets a human-readable string.
+   * @return Fetches a human-readable representation of the current test object 
+   */
+  public String getCurrentTestName() {
+    if (currentTest == null) {
+      return "(no test)";
+    }
+    return currentTest.toString();
+  }
+
+  /**
+   * Returns a human-formatted message identifying what clients have connected
+   * but have not yet reported results for this test.  It is used in a timeout
+   * condition, to identify what we're still waiting on.
+   * 
+   * @return human readable message
+   */
+  public String getWorkingClients() {
+    synchronized (clientStatusesLock) {
+      StringBuilder buf = new StringBuilder();
+      int itemCount = 0;
+      for (ClientStatus clientStatus : clientStatuses.values()) {
+        if (clientStatus.hasRequestedCurrentTest 
+            && clientStatus.currentTestResults == null) {
+          if (itemCount > 0) {
+            buf.append(", ");
+          }
+          buf.append(clientStatus.clientId);
+          ++itemCount;
+        }
+      }
+      int difference = numClients - itemCount;
+      if (difference > 0) {
+        if (itemCount > 0) {
+          buf.append('\n');
+        }
+        buf.append(difference + 
+            " other client(s) haven't responded back to JUnitShell since the start of the test.");
+      }
+      return buf.toString();
+    }
+  }
+
+  /**
    * Called by the servlet to query for for the next method to test.
    * 
    * @param timeout how long to wait for an answer
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index cd21132..45eecf2 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -116,11 +116,19 @@
 
   /**
    * The amount of time to wait for all clients to have contacted the server and
-   * begin running the test.
+   * begin running the test.  "Contacted" does not necessarily mean "the test
+   * has begun," e.g. for linker errors stopping the test initialization.
    */
   private static final int TEST_BEGIN_TIMEOUT_MILLIS = 60000;
 
   /**
+   * The amount of time to wait for all clients to complete a single test
+   * method, in milliseconds, measured from when the <i>last</i> client 
+   * connects (and thus starts the test).  5 minutes.
+   */
+  private static final long TEST_METHOD_TIMEOUT_MILLIS = 300000;
+
+  /**
    * Singleton object for hosting unit tests. All test case instances executed
    * by the TestRunner will use the single unitTestShell.
    */
@@ -270,6 +278,14 @@
   private long testBeginTimeout;
 
   /**
+   * Timeout for individual test method.  If System.currentTimeMillis() is later 
+   * than this timestamp, then we need to pack up and go home.  Zero for "not 
+   * yet set" (at the start of a test).  This interval begins when the 
+   * testBeginTimeout interval is done.
+   */
+  private long testMethodTimeout;
+  
+  /**
    * We need to keep a hard reference to the last module that was launched until
    * all client browsers have successfully transitioned to the current module.
    * Failure to do so allows the last module to be GC'd, which transitively
@@ -545,6 +561,17 @@
        * clients have transitioned to the current module.
        */
       lastModule = currentModule;
+      if (testMethodTimeout == 0) {
+        testMethodTimeout = currentTimeMillis + TEST_METHOD_TIMEOUT_MILLIS;
+      } else if (testMethodTimeout < currentTimeMillis){
+        double elapsed = (currentTimeMillis - testBeginTime) / 1000.0;
+        throw new TimeoutException(
+            "The browser did not complete the test method " 
+                + messageQueue.getCurrentTestName() + " in "
+                + TEST_METHOD_TIMEOUT_MILLIS + "ms.\n  We have no results from: "
+                + messageQueue.getWorkingClients()
+                + "\n Actual time elapsed: " + elapsed + " seconds.\n");
+      }
     } else if (testBeginTimeout < currentTimeMillis) {
       double elapsed = (currentTimeMillis - testBeginTime) / 1000.0;
       throw new TimeoutException(
@@ -650,6 +677,7 @@
       // contacted; something probably went wrong (the module failed to load?)
       testBeginTime = System.currentTimeMillis();
       testBeginTimeout = testBeginTime + TEST_BEGIN_TIMEOUT_MILLIS;
+      testMethodTimeout = 0;  // wait until test execution begins
       pumpEventLoop();
     } catch (TimeoutException e) {
       lastLaunchFailed = true;