Revision: 5766
Author: amitman...@google.com
Date: Tue Jul 21 15:13:53 2009
Log: This patch adds "batch" execution of GWTTestCases, significantly  
reducing the
synchronization and network overhead.

Patch by: amitmanjhi
Review (and simplifications) by: scottb


http://code.google.com/p/google-web-toolkit/source/detail?r=5766

Added:
  /trunk/user/src/com/google/gwt/junit/BatchingStrategy.java
Modified:
  /trunk/common.ant.xml
  /trunk/user/src/com/google/gwt/junit/JUnitMessageQueue.java
  /trunk/user/src/com/google/gwt/junit/JUnitShell.java
  /trunk/user/src/com/google/gwt/junit/client/GWTTestCase.java
  /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
  /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
  /trunk/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
  /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
   
/trunk/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java

=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/junit/BatchingStrategy.java  Tue Jul 21  
15:13:53 2009
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.junit;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+
+import java.util.Set;
+
+/**
+ * An interface that specifies how tests should be batched.
+ */
+public interface BatchingStrategy {
+
+  /**
+   * Returns the list of tests that should be executed along with this  
test.
+   */
+  TestInfo[] getTestBlock(TestInfo currentTest);
+}
+
+/**
+ *
+ * Strategy that does not batch tests.
+ */
+class NoBatchingStrategy implements BatchingStrategy {
+  public TestInfo[] getTestBlock(TestInfo currentTest) {
+    return new TestInfo[] {currentTest};
+  }
+}
+
+/**
+ * Strategy that batches all tests belonging to one module.
+ */
+class ModuleBatchingStrategy implements BatchingStrategy {
+
+  /**
+   * Returns the list of all tests belonging to the module of
+   * <code>currentTest</code>.
+   */
+  public TestInfo[] getTestBlock(TestInfo currentTest) {
+    String moduleName = currentTest.getTestModule();
+    if (moduleName.endsWith(".JUnit")) {
+      moduleName = moduleName.substring(0, moduleName.length()
+          - ".JUnit".length());
+    }
+    Set<TestInfo> allTestsInModule =  
GWTTestCase.ALL_GWT_TESTS.get(moduleName);
+    if (allTestsInModule != null) {
+      assert allTestsInModule.size() > 0;
+      return allTestsInModule.toArray(new  
TestInfo[allTestsInModule.size()]);
+    }
+    // No data, default to just this test.
+    return new TestInfo[] {currentTest};
+  }
+}
=======================================
--- /trunk/common.ant.xml       Mon Jul 13 10:39:35 2009
+++ /trunk/common.ant.xml       Tue Jul 21 15:13:53 2009
@@ -160,7 +160,7 @@

    <macrodef name="gwt.junit">
      <!-- TODO: make this more generic / refactor so it can be used from  
dev/core -->
-    <attribute name="test.args" default="" />
+    <attribute name="test.args" default="-batch module" />
      <attribute name="test.out" default="" />
      <attribute name="test.reports" default="@{test.out}/reports" />
      <attribute name="test.cases" default="" />
=======================================
--- /trunk/user/src/com/google/gwt/junit/JUnitMessageQueue.java Mon Jul 13  
10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/JUnitMessageQueue.java Tue Jul 21  
15:13:53 2009
@@ -23,10 +23,11 @@
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
+import java.util.Map.Entry;

  /**
- * A message queue to pass data between {...@link JUnitShell} and {...@link
- * com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe manner.
+ * A message queue to pass data between {...@link JUnitShell} and
+ * {...@link com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe  
manner.
   *
   * <p>
   * The public methods are called by the servlet to find out what test to  
execute
@@ -45,8 +46,10 @@
     */
    public static class ClientStatus {
      public final String clientId;
-
-    public JUnitResult currentTestResults = null;
+    /**
+     * Stores the testResults for the current block of tests.
+     */
+    public Map<TestInfo, JUnitResult> currentTestBlockResults = null;
      public boolean hasRequestedCurrentTest = false;
      public boolean isNew = true;

@@ -68,7 +71,7 @@
    /**
     * The current test to execute.
     */
-  private TestInfo currentTest;
+  private TestInfo[] currentBlock;

    /**
     * The number of TestCase clients executing in parallel.
@@ -88,14 +91,14 @@
    }

    /**
-   * Called by the servlet to query for for the next method to test.
+   * Called by the servlet to query for for the next block to test.
     *
     * @param timeout how long to wait for an answer
-   * @return the next test to run, or <code>null</code> if
-   *         <code>timeout</code> is exceeded or the next test does not  
match
+   * @return the next test to run, or <code>null</code> if  
<code>timeout</code>
+   *         is exceeded or the next test does not match
     *         <code>testClassName</code>
     */
-  public TestInfo getNextTestInfo(String clientId, long timeout)
+  public TestInfo[] getNextTestBlock(String clientId, long timeout)
        throws TimeoutException {
      synchronized (clientStatusesLock) {
        ClientStatus clientStatus = clientStatuses.get(clientId);
@@ -106,7 +109,7 @@

        long startTime = System.currentTimeMillis();
        long stopTime = startTime + timeout;
-      while (clientStatus.currentTestResults != null) {
+      while (clientStatus.currentTestBlockResults != null) {
          long timeToWait = stopTime - System.currentTimeMillis();
          if (timeToWait < 1) {
            double elapsed = (System.currentTimeMillis() - startTime) /  
1000.0;
@@ -130,20 +133,27 @@

        // Record that this client has retrieved the current test.
        clientStatus.hasRequestedCurrentTest = true;
-      return currentTest;
+      return currentBlock;
      }
    }
+
+  public void reportFatalLaunch(String clientId, JUnitResult result) {
+    // Fatal launch error, cause this client to fail the whole block.
+    Map<TestInfo, JUnitResult> results = new HashMap<TestInfo,  
JUnitResult>();
+    for (TestInfo testInfo : currentBlock) {
+      results.put(testInfo, result);
+    }
+    reportResults(clientId, results);
+  }

    /**
     * Called by the servlet to report the results of the last test to run.
     *
-   * @param testInfo the testInfo the result is for
-   * @param results the result of running the test
+   * @param results the result of running the test block
     */
-  public void reportResults(String clientId, TestInfo testInfo,
-      JUnitResult results) {
+  public void reportResults(String clientId, Map<TestInfo, JUnitResult>  
results) {
      synchronized (clientStatusesLock) {
-      if (testInfo != null && !testInfo.equals(currentTest)) {
+      if (results != null && !resultsMatchCurrentBlock(results)) {
          // A client is reporting results for the wrong test.
          return;
        }
@@ -157,7 +167,7 @@
          clientStatus = new ClientStatus(clientId);
          clientStatuses.put(clientId, clientStatus);
        }
-      clientStatus.currentTestResults = results;
+      clientStatus.currentTestBlockResults = results;
        clientStatusesLock.notifyAll();
      }
    }
@@ -168,10 +178,10 @@
     * @return Fetches a human-readable representation of the current test  
object
     */
    String getCurrentTestName() {
-    if (currentTest == null) {
+    if (currentBlock == null) {
        return "(no test)";
      }
-    return currentTest.toString();
+    return currentBlock[0].toString();
    }

    /**
@@ -212,11 +222,24 @@
     *
     * @return A map of results from all clients.
     */
-  Map<String, JUnitResult> getResults() {
+  Map<TestInfo, Map<String, JUnitResult>> getResults() {
      synchronized (clientStatusesLock) {
-      Map<String, JUnitResult> result = new HashMap<String, JUnitResult>();
+      /*
+       * All this overly complicated piece of code does is transform  
mappings
+       * keyed by clientId into mappings keyed by TestInfo.
+       */
+      Map<TestInfo, Map<String, JUnitResult>> result = new  
HashMap<TestInfo, Map<String, JUnitResult>>();
        for (ClientStatus clientStatus : clientStatuses.values()) {
-        result.put(clientStatus.clientId, clientStatus.currentTestResults);
+        for (Entry<TestInfo, JUnitResult> entry :  
clientStatus.currentTestBlockResults.entrySet()) {
+          TestInfo testInfo = entry.getKey();
+          JUnitResult clientResultForThisTest = entry.getValue();
+          Map<String, JUnitResult> targetMap = result.get(testInfo);
+          if (targetMap == null) {
+            targetMap = new HashMap<String, JUnitResult>();
+            result.put(testInfo, targetMap);
+          }
+          targetMap.put(clientStatus.clientId, clientResultForThisTest);
+        }
        }
        return result;
      }
@@ -272,7 +295,7 @@
        int itemCount = 0;
        for (ClientStatus clientStatus : clientStatuses.values()) {
          if (clientStatus.hasRequestedCurrentTest
-            && clientStatus.currentTestResults == null) {
+            && clientStatus.currentTestBlockResults == null) {
            if (itemCount > 0) {
              buf.append(", ");
            }
@@ -304,7 +327,7 @@
          return false;
        }
        for (ClientStatus clientStatus : clientStatuses.values()) {
-        if (clientStatus.currentTestResults == null) {
+        if (clientStatus.currentTestBlockResults == null) {
            return false;
          }
        }
@@ -315,12 +338,12 @@
    /**
     * Called by the shell to set the next test to run.
     */
-  void setNextTest(TestInfo testInfo) {
+  void setNextTestBlock(TestInfo[] testBlock) {
      synchronized (clientStatusesLock) {
-      this.currentTest = testInfo;
+      this.currentBlock = testBlock;
        for (ClientStatus clientStatus : clientStatuses.values()) {
          clientStatus.hasRequestedCurrentTest = false;
-        clientStatus.currentTestResults = null;
+        clientStatus.currentTestBlockResults = null;
        }
        clientStatusesLock.notifyAll();
      }
@@ -334,4 +357,14 @@
        }
      }
    }
-}
+
+  private boolean resultsMatchCurrentBlock(Map<TestInfo, JUnitResult>  
results) {
+    assert results.size() == currentBlock.length;
+    for (TestInfo testInfo : currentBlock) {
+      if (!results.containsKey(testInfo)) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/junit/JUnitShell.java        Mon Jul 20  
22:35:44 2009
+++ /trunk/user/src/com/google/gwt/junit/JUnitShell.java        Tue Jul 21  
15:13:53 2009
@@ -44,6 +44,7 @@
  import junit.framework.TestResult;

  import java.util.ArrayList;
+import java.util.HashMap;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.regex.Matcher;
@@ -66,10 +67,10 @@
   * </p>
   *
   * <p>
- * The client classes consist of the translatable version of {...@link
- * com.google.gwt.junit.client.GWTTestCase}, translatable JUnit classes,  
and the
- * user's own {...@link com.google.gwt.junit.client.GWTTestCase}-derived  
class.
- * The client communicates to the server via RPC.
+ * The client classes consist of the translatable version of
+ * {...@link com.google.gwt.junit.client.GWTTestCase}, translatable JUnit  
classes,
+ * and the user's own {...@link  
com.google.gwt.junit.client.GWTTestCase}-derived
+ * class. The client communicates to the server via RPC.
   * </p>
   *
   * <p>
@@ -238,6 +239,37 @@
          }
        });

+      // TODO: currently, only two values but soon may have multiple  
values.
+      registerHandler(new ArgHandlerString() {
+        @Override
+        public String getPurpose() {
+          return "Configure batch execution of tests";
+        }
+
+        @Override
+        public String getTag() {
+          return "-batch";
+        }
+
+        @Override
+        public String[] getTagArgs() {
+          return new String[] {"module"};
+        }
+
+        @Override
+        public boolean isUndocumented() {
+          return true;
+        }
+
+        @Override
+        public boolean setString(String str) {
+          if (str.equals("module")) {
+            batchingStrategy = new ModuleBatchingStrategy();
+          }
+          return true;
+        }
+      });
+
        registerHandler(new ArgHandler() {
          @Override
          public String[] getDefaultArgs() {
@@ -337,8 +369,8 @@

    /**
     * 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.
+   * 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;

@@ -365,8 +397,8 @@

    /**
     * Entry point for {...@link com.google.gwt.junit.client.GWTTestCase}. Gets 
 
or
-   * creates the singleton {...@link JUnitShell} and invokes its {...@link
-   * #runTestImpl(String, TestCase, TestResult, Strategy)}.
+   * creates the singleton {...@link JUnitShell} and invokes its
+   * {...@link #runTestImpl(String, TestCase, TestResult, Strategy)}.
     */
    public static void runTest(String moduleName, TestCase testCase,
        TestResult testResult) throws UnableToCompleteException {
@@ -443,6 +475,11 @@

      return unitTestShell;
    }
+
+  /**
+   * Determines how to batch up tests for execution.
+   */
+  private BatchingStrategy batchingStrategy = new NoBatchingStrategy();

    /**
     * When headless, all logging goes to the console.
@@ -510,6 +547,8 @@
     */
    private long testMethodTimeout;

+  private Map<TestInfo, Map<String, JUnitResult>> cachedResults = new  
HashMap<TestInfo, Map<String, JUnitResult>>();
+
    /**
     * Enforce the singleton pattern. The call to {...@link GWTShell}'s ctor  
forces
     * server mode and disables processing extra arguments as URLs to be  
shown.
@@ -636,6 +675,49 @@
      }
      super.compile(getTopLogger(), module);
    }
+
+  private void processTestResult(TestInfo testInfo, TestCase testCase,
+      TestResult testResult, Strategy strategy) {
+
+    Map<String, JUnitResult> results = cachedResults.get(testInfo);
+    assert results != null;
+
+    boolean parallelTesting = numClients > 1;
+
+    for (Entry<String, JUnitResult> entry : results.entrySet()) {
+      String clientId = entry.getKey();
+      JUnitResult result = entry.getValue();
+      assert (result != null);
+      Throwable exception = result.getException();
+      // In the case that we're running multiple clients at once, we need  
to
+      // let the user know the browser in which the failure happened
+      if (parallelTesting && exception != null) {
+        String msg = "Remote test failed at " + clientId;
+        if (exception instanceof AssertionFailedError) {
+          AssertionFailedError newException = new AssertionFailedError(msg
+              + "\n" + exception.getMessage());
+          newException.setStackTrace(exception.getStackTrace());
+          newException.initCause(exception.getCause());
+          exception = newException;
+        } else {
+          exception = new RuntimeException(msg, exception);
+        }
+      }
+
+      // A "successful" failure.
+      if (exception instanceof AssertionFailedError) {
+        testResult.addFailure(testCase, (AssertionFailedError) exception);
+      } else if (exception != null) {
+        // A real failure
+        if (exception instanceof JUnitFatalLaunchException) {
+          lastLaunchFailed = true;
+        }
+        testResult.addError(testCase, exception);
+      }
+
+      strategy.processResult(testCase, result);
+    }
+  }

    /**
     * Runs a particular test case.
@@ -682,8 +764,19 @@
        return;
      }

-    messageQueue.setNextTest(new TestInfo(currentModule.getName(),
-        testCase.getClass().getName(), testCase.getName()));
+    TestInfo testInfo = new TestInfo(currentModule.getName(),
+        testCase.getClass().getName(), testCase.getName());
+    if (cachedResults.containsKey(testInfo)) {
+      // Already have a result.
+      processTestResult(testInfo, testCase, testResult, strategy);
+      return;
+    }
+
+    /*
+     * Need to process test. Set up synchronization.
+     */
+    TestInfo[] testBlock = batchingStrategy.getTestBlock(testInfo);
+    messageQueue.setNextTestBlock(testBlock);

      try {
        if (firstLaunch) {
@@ -710,43 +803,9 @@
      }

      assert (messageQueue.hasResult());
-    Map<String, JUnitResult> results = messageQueue.getResults();
-
-    boolean parallelTesting = numClients > 1;
-
-    for (Entry<String, JUnitResult> entry : results.entrySet()) {
-      String clientId = entry.getKey();
-      JUnitResult result = entry.getValue();
-      assert (result != null);
-      Throwable exception = result.getException();
-      // In the case that we're running multiple clients at once, we need  
to
-      // let the user know the browser in which the failure happened
-      if (parallelTesting && exception != null) {
-        String msg = "Remote test failed at " + clientId;
-        if (exception instanceof AssertionFailedError) {
-          AssertionFailedError newException = new AssertionFailedError(msg
-              + "\n" + exception.getMessage());
-          newException.setStackTrace(exception.getStackTrace());
-          newException.initCause(exception.getCause());
-          exception = newException;
-        } else {
-          exception = new RuntimeException(msg, exception);
-        }
-      }
-
-      // A "successful" failure
-      if (exception instanceof AssertionFailedError) {
-        testResult.addFailure(testCase, (AssertionFailedError) exception);
-      } else if (exception != null) {
-        // A real failure
-        if (exception instanceof JUnitFatalLaunchException) {
-          lastLaunchFailed = true;
-        }
-        testResult.addError(testCase, exception);
-      }
-
-      strategy.processResult(testCase, result);
-    }
+    cachedResults = messageQueue.getResults();
+    assert cachedResults.containsKey(testInfo);
+    processTestResult(testInfo, testCase, testResult, strategy);
    }

    /**
=======================================
--- /trunk/user/src/com/google/gwt/junit/client/GWTTestCase.java        Mon Jul 
13  
10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/client/GWTTestCase.java        Tue Jul 
21  
15:13:53 2009
@@ -16,10 +16,16 @@
  package com.google.gwt.junit.client;

  import com.google.gwt.junit.JUnitShell;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;

  import junit.framework.TestCase;
  import junit.framework.TestResult;

+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
  /**
   * Acts as a bridge between the JUnit environment and the GWT environment.  
We
   * hook the run method and stash the TestResult object for later  
communication
@@ -36,6 +42,12 @@
   */
  public abstract class GWTTestCase extends TestCase {

+  /**
+   * Records all live GWTTestCases by module name so we can optimize run  
they
+   * are compiled and run.
+   */
+  public static final Map<String, Set<TestInfo>> ALL_GWT_TESTS = new  
HashMap<String, Set<TestInfo>>();
+
    /*
     * Object that collects the results of this test case execution.
     */
@@ -132,6 +144,22 @@
      testResult = result;
      super.run(result);
    }
+
+  @Override
+  public void setName(String name) {
+    super.setName(name);
+
+    // Once the name is set, we can add ourselves to the global set.
+    String moduleName = getModuleName();
+    Set<TestInfo> testsInThisModule = ALL_GWT_TESTS.get(moduleName);
+    if (testsInThisModule == null) {
+      // Preserve the order.
+      testsInThisModule = new LinkedHashSet<TestInfo>();
+      ALL_GWT_TESTS.put(moduleName, testsInThisModule);
+    }
+    testsInThisModule.add(new TestInfo(moduleName + ".JUnit",
+        getClass().getName(), getName()));
+  }

    /**
     * Put the current test in asynchronous mode. If the test method  
completes
@@ -218,8 +246,10 @@
    @Override
    protected void runTest() throws Throwable {
      if (this.getName() == null) {
-      throw new IllegalArgumentException("GWTTestCases require a name; \""  
+ this.toString()
-          + "\" has none.  Perhaps you used TestSuite.addTest() instead of  
addTestClass()?");
+      throw new IllegalArgumentException(
+          "GWTTestCases require a name; \""
+              + this.toString()
+              + "\" has none.  Perhaps you used TestSuite.addTest()  
instead of addTestClass()?");
      }

      String moduleName = getModuleName();
=======================================
--- /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHost.java     Mon Jul 
 
13 10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHost.java     Tue Jul 
 
21 15:13:53 2009
@@ -19,6 +19,8 @@
  import com.google.gwt.user.client.rpc.IsSerializable;
  import com.google.gwt.user.client.rpc.RemoteService;

+import java.util.HashMap;
+
  /**
   * An interface for {...@link com.google.gwt.junit.client.GWTTestCase} to
   * communicate with the test process through RPC.
@@ -85,17 +87,16 @@
     * @return the next test to run
     * @throws TimeoutException if the wait for the next method times out.
     */
-  TestInfo getFirstMethod() throws TimeoutException;
+  TestInfo[] getFirstMethod() throws TimeoutException;

    /**
     * Reports results for the last method run and gets the name of next  
method to
     * run.
     *
-   * @param testInfo the testInfo the result is for
-   * @param result the results of executing the test
+   * @param results the results of executing the test
     * @return the next test to run
     * @throws TimeoutException if the wait for the next method times out.
     */
-  TestInfo reportResultsAndGetNextMethod(TestInfo testInfo, JUnitResult  
result)
-      throws TimeoutException;
-}
+  TestInfo[] reportResultsAndGetNextMethod(
+      HashMap<TestInfo, JUnitResult> results) throws TimeoutException;
+}
=======================================
--- /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java        
 
Mon Jul 13 10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java        
 
Tue Jul 21 15:13:53 2009
@@ -18,6 +18,8 @@
  import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
  import com.google.gwt.user.client.rpc.AsyncCallback;

+import java.util.HashMap;
+
  /**
   * The asynchronous version of {...@link JUnitHost}.
   */
@@ -29,17 +31,16 @@
     * @param callBack the object that will receive the name of the next  
method to
     *          run
     */
-  void getFirstMethod(AsyncCallback<TestInfo> callBack);
+  void getFirstMethod(AsyncCallback<TestInfo[]> callBack);

    /**
     * Reports results for the last method run and gets the name of next  
method to
     * run.
     *
-   * @param testInfo the testInfo the result is for
-   * @param result the result of the test
+   * @param results the results of the tests
     * @param callBack the object that will receive the name of the next  
method to
     *          run
     */
-  void reportResultsAndGetNextMethod(TestInfo testInfo, JUnitResult result,
-      AsyncCallback<TestInfo> callBack);
-}
+  void reportResultsAndGetNextMethod(HashMap<TestInfo, JUnitResult>  
results,
+      AsyncCallback<TestInfo[]> callBack);
+}
=======================================
--- /trunk/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java Mon  
Jul 13 10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java Tue  
Jul 21 15:13:53 2009
@@ -27,6 +27,7 @@
  import com.google.gwt.core.ext.typeinfo.TypeOracle;
  import com.google.gwt.junit.client.GWTTestCase;
  import com.google.gwt.junit.client.impl.GWTRunner;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
  import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
  import com.google.gwt.user.rebind.SourceWriter;

@@ -38,7 +39,6 @@
   * This class generates a stub class for classes that derive from  
GWTTestCase.
   * This stub class provides the necessary bridge between our Hosted or  
Hybrid
   * mode classes and the JUnit system.
- *
   */
  public class GWTRunnerGenerator extends Generator {

@@ -77,9 +77,8 @@

      String moduleName;
      try {
-      ConfigurationProperty prop
-          = context.getPropertyOracle().getConfigurationProperty(
-              "junit.moduleName");
+      ConfigurationProperty prop =  
context.getPropertyOracle().getConfigurationProperty(
+          "junit.moduleName");
        moduleName = prop.getValues().get(0);
      } catch (BadPropertyValueException e) {
        logger.log(TreeLogger.ERROR,
@@ -97,9 +96,20 @@
          generatedClass, GWT_RUNNER_NAME);

      if (sourceWriter != null) {
-      JClassType[] allTestTypes =  
getAllPossibleTestTypes(context.getTypeOracle());
-      Set<String> testClasses = getTestTypesForModule(logger, moduleName,
-          allTestTypes);
+      // Check the global set of active tests for this module.
+      Set<TestInfo> moduleTests =  
GWTTestCase.ALL_GWT_TESTS.get(moduleName);
+      Set<String> testClasses;
+      if (moduleTests == null || moduleTests.isEmpty()) {
+        // Fall back to pulling in all types in the module.
+        JClassType[] allTestTypes =  
getAllPossibleTestTypes(context.getTypeOracle());
+        testClasses = getTestTypesForModule(logger, moduleName,  
allTestTypes);
+      } else {
+        // Must use sorted set to prevent nondeterminism.
+        testClasses = new TreeSet<String>();
+        for (TestInfo testInfo : moduleTests) {
+          testClasses.add(testInfo.getTestClass());
+        }
+      }
        writeCreateNewTestCaseMethod(testClasses, sourceWriter);
        sourceWriter.commit(logger);
      }
=======================================
--- /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java      Mon Jul 
 
13 10:39:35 2009
+++ /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java      Tue Jul 
 
21 15:13:53 2009
@@ -30,6 +30,7 @@
  import java.io.IOException;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
+import java.util.HashMap;

  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
@@ -80,20 +81,22 @@
      fld.set(obj, value);
    }

-  public TestInfo getFirstMethod() throws TimeoutException {
-    return getHost().getNextTestInfo(getClientId(getThreadLocalRequest()),
+  public TestInfo[] getFirstMethod() throws TimeoutException {
+    return getHost().getNextTestBlock(getClientId(getThreadLocalRequest()),
          TIME_TO_WAIT_FOR_TESTNAME);
    }

-  public TestInfo reportResultsAndGetNextMethod(TestInfo testInfo,
-      JUnitResult result) throws TimeoutException {
-    initResult(getThreadLocalRequest(), result);
-    ExceptionWrapper ew = result.getExceptionWrapper();
-    result.setException(deserialize(ew));
+  public TestInfo[] reportResultsAndGetNextMethod(
+      HashMap<TestInfo, JUnitResult> results) throws TimeoutException {
+    for (JUnitResult result : results.values()) {
+      initResult(getThreadLocalRequest(), result);
+      ExceptionWrapper ew = result.getExceptionWrapper();
+      result.setException(deserialize(ew));
+    }
      JUnitMessageQueue host = getHost();
      String clientId = getClientId(getThreadLocalRequest());
-    host.reportResults(clientId, testInfo, result);
-    return host.getNextTestInfo(clientId, TIME_TO_WAIT_FOR_TESTNAME);
+    host.reportResults(clientId, results);
+    return host.getNextTestBlock(clientId, TIME_TO_WAIT_FOR_TESTNAME);
    }

    @Override
@@ -105,7 +108,7 @@
        JUnitResult result = new JUnitResult();
        initResult(request, result);
        result.setException(new JUnitFatalLaunchException(requestPayload));
-      getHost().reportResults(getClientId(request), null, result);
+      getHost().reportFatalLaunch(getClientId(request), result);
      } else {
        super.service(request, response);
      }
=======================================
---  
/trunk/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
      
Mon Jul 13 10:39:35 2009
+++  
/trunk/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
      
Tue Jul 21 15:13:53 2009
@@ -19,11 +19,15 @@
  import com.google.gwt.core.client.GWT;
  import com.google.gwt.junit.client.GWTTestCase;
  import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
  import com.google.gwt.user.client.Timer;
  import com.google.gwt.user.client.Window;
  import com.google.gwt.user.client.rpc.AsyncCallback;
  import com.google.gwt.user.client.rpc.ServiceDefTarget;

+import java.util.HashMap;
+
  /**
   * The entry point class for GWTTestCases.
   *
@@ -37,7 +41,7 @@
     * The RPC callback object for {...@link GWTRunner#junitHost}. When
     * {...@link #onSuccess(Object)} is called, it's time to run the next test  
case.
     */
-  private final class JUnitHostListener implements AsyncCallback<TestInfo>  
{
+  private final class JUnitHostListener implements  
AsyncCallback<TestInfo[]> {

      /**
       * A call to junitHost failed.
@@ -55,9 +59,11 @@
      /**
       * A call to junitHost succeeded; run the next test case.
       */
-    public void onSuccess(TestInfo nextTest) {
-      currentTest = nextTest;
-      if (currentTest != null) {
+    public void onSuccess(TestInfo[] nextTestBlock) {
+      currentBlock = nextTestBlock;
+      currentBlockIndex = 0;
+      currentResults.clear();
+      if (currentBlock != null && currentBlock.length > 0) {
          doRunTest();
        }
      }
@@ -82,9 +88,20 @@
      return sInstance;
    }

-  private JUnitResult currentResult;
-
-  private TestInfo currentTest;
+  /**
+   * The current block of tests to execute.
+   */
+  private TestInfo currentBlock[];
+
+  /**
+   * Active test within current block of tests.
+   */
+  private int currentBlockIndex = 0;
+
+  /**
+   * Results for all test cases in the current block.
+   */
+  private HashMap<TestInfo, JUnitResult> currentResults = new  
HashMap<TestInfo, JUnitResult>();

    /**
     * The remote service to communicate with.
@@ -116,8 +133,8 @@
    }

    public void onModuleLoad() {
-    currentTest = checkForQueryParamTestToRun();
-    if (currentTest != null) {
+    currentBlock = checkForQueryParamTestToRun();
+    if (currentBlock != null) {
        /*
         * Just run a single test with no server-side interaction.
         */
@@ -137,8 +154,19 @@
        // That's it, we're done
        return;
      }
-    currentResult = result;
-    syncToServer();
+    TestInfo currentTest = getCurrentTest();
+    currentResults.put(currentTest, result);
+    ++currentBlockIndex;
+    if (currentBlockIndex < currentBlock.length) {
+      // Run the next test after a short delay.
+      DeferredCommand.addCommand(new Command() {
+        public void execute() {
+          doRunTest();
+        }
+      });
+    } else {
+      syncToServer();
+    }
    }

    /**
@@ -147,19 +175,21 @@
     */
    protected abstract GWTTestCase createNewTestCase(String testClass);

-  private TestInfo checkForQueryParamTestToRun() {
+  private TestInfo[] checkForQueryParamTestToRun() {
      String testClass = Window.Location.getParameter(TESTCLASS_QUERY_PARAM);
      String testMethod = Window.Location.getParameter(TESTFUNC_QUERY_PARAM);
      if (testClass == null || testMethod == null) {
        return null;
      }
-    return new TestInfo(GWT.getModuleName(), testClass, testMethod);
+    // TODO: support blocks of tests?
+    return new TestInfo[] {new TestInfo(GWT.getModuleName(), testClass,
+        testMethod)};
    }

    private void doRunTest() {
      // Make sure the module matches.
      String currentModule = GWT.getModuleName();
-    String newModule = currentTest.getTestModule();
+    String newModule = getCurrentTest().getTestModule();
      if (currentModule.equals(newModule)) {
        // The module is correct.
        runTest();
@@ -171,11 +201,18 @@
        String href = Window.Location.getHref();
        String newHref = href.replace(currentModule, newModule);
        Window.Location.replace(newHref);
+      currentBlock = null;
+      currentBlockIndex = 0;
      }
    }
+
+  private TestInfo getCurrentTest() {
+    return currentBlock[currentBlockIndex];
+  }

    private void runTest() {
      // Dynamically create a new test case.
+    TestInfo currentTest = getCurrentTest();
      GWTTestCase testCase = null;
      Throwable caught = null;
      try {
@@ -197,11 +234,10 @@
    }

    private void syncToServer() {
-    if (currentTest == null) {
+    if (currentBlock == null) {
        junitHost.getFirstMethod(junitHostListener);
      } else {
-      junitHost.reportResultsAndGetNextMethod(currentTest, currentResult,
-          junitHostListener);
+      junitHost.reportResultsAndGetNextMethod(currentResults,  
junitHostListener);
      }
    }



--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to