Fixes two bugs in the lightweight events for runAsync,
and adds test cases for them.

Review by: bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5983 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 0560ad8..f58ce85 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.client;
 
+import com.google.gwt.core.client.impl.AsyncFragmentLoader;
 import com.google.gwt.core.client.impl.Impl;
 
 /**
@@ -211,6 +212,16 @@
      * <code>runAsync</code> in code that might or might not run in a web
      * browser.
      */
+    if (isScript()) {
+      /*
+       * It's possible that the code splitter does not run, even for a
+       * production build. Signal a lightweight event, anyway, just so that
+       * there isn't a complete lack of lightweight events for runAsync.
+       */
+      AsyncFragmentLoader.BROWSER_LOADER.logEventProgress("noDownloadNeeded", "begin");
+      AsyncFragmentLoader.BROWSER_LOADER.logEventProgress("noDownloadNeeded", "end");
+    }
+
     UncaughtExceptionHandler handler = sUncaughtExceptionHandler;
     if (handler == null) {
       callback.onSuccess();
diff --git a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
index 056cf51..fb03197 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -101,7 +101,7 @@
 
     private static final String LEFTOVERS_DOWNLOAD = "leftoversDownload";
 
-    private static String downloadGroup(int splitPoint) {
+    private static String downloadGroupForExclusive(int splitPoint) {
       return "download" + splitPoint;
     }
   }
@@ -445,8 +445,7 @@
        * The initial fragments has loaded. Immediately start loading the
        * requested code.
        */
-      logEventProgress(LwmLabels.downloadGroup(splitPoint), LwmLabels.BEGIN,
-          splitPoint, null);
+      logDownloadStart(splitPoint);
       startLoadingFragment(splitPoint, loadErrorHandler);
       return;
     }
@@ -488,6 +487,11 @@
     logEventProgress(eventGroup, type, null, null);
   }
 
+  private String downloadGroup(int fragment) {
+    return (fragment == leftoversFragment()) ? LwmLabels.LEFTOVERS_DOWNLOAD
+        : LwmLabels.downloadGroupForExclusive(fragment);
+  }
+
   /**
    * Return whether all initial fragments have completed loading.
    */
@@ -512,6 +516,10 @@
     return numEntries;
   }
 
+  private void logDownloadStart(int fragment) {
+    logEventProgress(downloadGroup(fragment), LwmLabels.BEGIN, fragment, null);
+  }
+
   /**
    * Log event progress via the {@link Logger} this instance was provided. The
    * <code>fragment</code> and <code>size</code> objects are allowed to be
@@ -523,8 +531,7 @@
   }
 
   private void logFragmentLoaded(int fragment) {
-    String logGroup = (fragment == leftoversFragment())
-        ? LwmLabels.LEFTOVERS_DOWNLOAD : LwmLabels.downloadGroup(fragment);
+    String logGroup = downloadGroup(fragment);
     logEventProgress(logGroup, LwmLabels.END, fragment, null);
   }
 
@@ -563,8 +570,7 @@
       // start loading the next initial fragment
       initialFragmentsLoading = true;
       int nextSplitPoint = remainingInitialFragments.peek();
-      logEventProgress(LwmLabels.downloadGroup(nextSplitPoint),
-          LwmLabels.BEGIN, nextSplitPoint, null);
+      logDownloadStart(nextSplitPoint);
       startLoadingFragment(nextSplitPoint, new InitialFragmentDownloadFailed());
       return;
     }
@@ -576,8 +582,10 @@
     // start loading any pending fragments
     assert (waitingForInitialFragments.size() == waitingForInitialFragmentsErrorHandlers.size());
     while (waitingForInitialFragments.size() > 0) {
-      startLoadingFragment(waitingForInitialFragments.remove(),
-          waitingForInitialFragmentsErrorHandlers.remove());
+      int nextSplitPoint = waitingForInitialFragments.remove();
+      LoadErrorHandler handler = waitingForInitialFragmentsErrorHandlers.remove();
+      logDownloadStart(nextSplitPoint);
+      startLoadingFragment(nextSplitPoint, handler);
     }
   }
 }
diff --git a/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java b/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
index 7e237e7..28cba81 100644
--- a/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
+++ b/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
@@ -26,6 +26,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 
 /** Tests the fragment loader. */
 public class AsyncFragmentLoaderTest extends TestCase {
@@ -97,14 +98,45 @@
     }
   }
 
-  private static final LoadErrorHandler NULL_ERROR_HANDLER = new LoadErrorHandler() {
-    public void loadFailed(Throwable reason) {
-    }
-  };
+  private static class MockProgressEvent {
+    public final String eventGroup;
+    public final Integer fragment;
+    public final String type;
 
-  private static final Logger NULL_LOGGER = new Logger() {
+    public MockProgressEvent(String eventGroup, String type, Integer fragment) {
+      this.eventGroup = eventGroup;
+      this.type = type;
+      this.fragment = fragment;
+    }
+  }
+
+  private static class MockProgressLogger implements Logger {
+    private Queue<MockProgressEvent> events = new LinkedList<MockProgressEvent>();
+
+    public void assertEvent(String eventGroup, String type, Integer fragment) {
+      MockProgressEvent event = events.remove();
+      assertEquals(eventGroup, event.eventGroup);
+      assertEquals(type, event.type);
+      assertEquals(fragment, event.fragment);
+    }
+
+    public void assertNoEvents() {
+      assertTrue("Expected no more progress events, but there are "
+          + events.size(), events.size() == 0);
+    }
+
     public void logEventProgress(String eventGroup, String type,
         Integer fragment, Integer size) {
+      events.add(new MockProgressEvent(eventGroup, type, fragment));
+    }
+  }
+
+  private static final String BEGIN = "begin";
+  private static final String END = "end";
+  private static final String LEFTOVERS_DOWNLOAD = "leftoversDownload";
+
+  private static final LoadErrorHandler NULL_ERROR_HANDLER = new LoadErrorHandler() {
+    public void loadFailed(Throwable reason) {
     }
   };
 
@@ -114,19 +146,31 @@
 
   public void testBasics() {
     MockLoadStrategy reqs = new MockLoadStrategy();
+    MockProgressLogger progress = new MockProgressLogger();
     int numEntries = 5;
     AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
-        new int[] {}, reqs, NULL_LOGGER);
+        new int[] {}, reqs, progress);
 
     loader.inject(1, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(numEntries);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
+
     loader.leftoversFragmentHasLoaded();
     reqs.assertFragmentsRequested(1);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, END, numEntries);
+    progress.assertEvent("download1", BEGIN, 1);
+
     loader.fragmentHasLoaded(1);
+    progress.assertEvent("download1", END, 1);
 
     loader.inject(2, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(2);
+    progress.assertEvent("download2", BEGIN, 2);
+
     loader.fragmentHasLoaded(2);
+    progress.assertEvent("download2", END, 2);
+
+    progress.assertNoEvents();
   }
 
   /**
@@ -134,14 +178,16 @@
    */
   public void testDownloadFailures() {
     MockLoadStrategy reqs = new MockLoadStrategy();
+    MockProgressLogger progress = new MockProgressLogger();
     int numEntries = 6;
     AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
-        1, 2, 3}, reqs, NULL_LOGGER);
+        1, 2, 3}, reqs, progress);
 
     // request fragment 1
     MockErrorHandler error1try1 = new MockErrorHandler();
     loader.inject(1, error1try1);
     reqs.assertFragmentsRequested(1);
+    progress.assertEvent("download1", BEGIN, 1);
 
     // fragment 1 fails
     loadFailed(reqs, 1);
@@ -151,34 +197,42 @@
     MockErrorHandler error1try2 = new MockErrorHandler();
     loader.inject(1, error1try2);
     reqs.assertFragmentsRequested(1);
+    progress.assertEvent("download1", BEGIN, 1);
 
     // this time fragment 1 succeeds
     loader.fragmentHasLoaded(1);
     reqs.assertFragmentsRequested();
     assertFalse(error1try2.getWasCalled());
+    progress.assertEvent("download1", END, 1);
 
     // request a later initial fragment (3), and see what happens if an
     // intermediate download fails
     MockErrorHandler error3try1 = new MockErrorHandler();
     loader.inject(3, error3try1);
     reqs.assertFragmentsRequested(2);
+    progress.assertEvent("download2", BEGIN, 2);
 
     loadFailed(reqs, 2);
     assertTrue(error3try1.wasCalled);
 
-    // request both 3 and 5, an see what happens if
+    // request both 3 and 5, and see what happens if
     // the leftovers download fails
     MockErrorHandler error3try2 = new MockErrorHandler();
     MockErrorHandler error5try1 = new MockErrorHandler();
     loader.inject(3, error3try2);
     loader.inject(5, error5try1);
     reqs.assertFragmentsRequested(2);
+    progress.assertEvent("download2", BEGIN, 2);
 
     loader.fragmentHasLoaded(2);
     reqs.assertFragmentsRequested(3);
+    progress.assertEvent("download2", END, 2);
+    progress.assertEvent("download3", BEGIN, 3);
 
     loader.fragmentHasLoaded(3);
     reqs.assertFragmentsRequested(numEntries);
+    progress.assertEvent("download3", END, 3);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
 
     loadFailed(reqs, numEntries); // leftovers fails!
     assertFalse(error3try2.getWasCalled()); // 3 should have succeeded
@@ -189,13 +243,19 @@
     MockErrorHandler error5try2 = new MockErrorHandler();
     loader.inject(5, error5try2);
     reqs.assertFragmentsRequested(numEntries);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
 
     loader.leftoversFragmentHasLoaded();
     reqs.assertFragmentsRequested(5);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, END, numEntries);
+    progress.assertEvent("download5", BEGIN, 5);
 
     loader.fragmentHasLoaded(5);
     reqs.assertFragmentsRequested();
     assertFalse(error5try2.getWasCalled());
+    progress.assertEvent("download5", END, 5);
+
+    progress.assertNoEvents();
   }
 
   /**
@@ -204,38 +264,51 @@
    */
   public void testLoadingPartOfInitialSequence() {
     MockLoadStrategy reqs = new MockLoadStrategy();
+    MockProgressLogger progress = new MockProgressLogger();
     int numEntries = 6;
     AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
-        1, 2, 3}, reqs, NULL_LOGGER);
+        1, 2, 3}, reqs, progress);
 
     loader.inject(1, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(1);
+    progress.assertEvent("download1", BEGIN, 1);
 
     loader.fragmentHasLoaded(1);
     reqs.assertFragmentsRequested(); // should stop
+    progress.assertEvent("download1", END, 1);
 
     loader.inject(2, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(2);
+    progress.assertEvent("download2", BEGIN, 2);
 
     loader.fragmentHasLoaded(2);
     reqs.assertFragmentsRequested(); // again, should stop
+    progress.assertEvent("download2", END, 2);
 
     loader.inject(3, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(3);
+    progress.assertEvent("download3", BEGIN, 3);
 
     loader.fragmentHasLoaded(3);
-    reqs.assertFragmentsRequested(numEntries); // last initial, so it should
-    // request the leftovers
+    // last initial, so it should now request the leftovers
+    reqs.assertFragmentsRequested(numEntries);
+    progress.assertEvent("download3", END, 3);
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, BEGIN, numEntries);
 
     loader.fragmentHasLoaded(numEntries);
     reqs.assertFragmentsRequested();
+    progress.assertEvent(LEFTOVERS_DOWNLOAD, END, numEntries);
 
     // check that exclusives now load
     loader.inject(5, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(5);
+    progress.assertEvent("download5", BEGIN, 5);
 
     loader.fragmentHasLoaded(5);
     reqs.assertFragmentsRequested();
+    progress.assertEvent("download5", END, 5);
+
+    progress.assertNoEvents();
   }
 
   /**
@@ -245,9 +318,10 @@
    */
   public void testOverflowInWaitingForInitialFragments() {
     MockLoadStrategy reqs = new MockLoadStrategy();
+    MockProgressLogger progress = new MockProgressLogger();
     int numEntries = 6;
     AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
-        1, 2, 3}, reqs, NULL_LOGGER);
+        1, 2, 3}, reqs, progress);
 
     /*
      * Repeatedly queue up extra downloads waiting on an initial and then fail.
@@ -256,9 +330,11 @@
       MockErrorHandler error = new MockErrorHandler();
       loader.inject(4, error);
       reqs.assertFragmentsRequested(1);
+      progress.assertEvent("download1", BEGIN, 1);
 
       loadFailed(reqs, 1);
       assertTrue(error.getWasCalled());
+      progress.assertNoEvents();
     }
   }
 
@@ -267,40 +343,58 @@
    */
   public void testWithInitialLoadSequence() {
     MockLoadStrategy reqs = new MockLoadStrategy();
+    MockProgressLogger progress = new MockProgressLogger();
     int numEntries = 6;
     AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new int[] {
-        1, 2, 3}, reqs, NULL_LOGGER);
+        1, 2, 3}, reqs, progress);
 
     loader.inject(1, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(1);
+    progress.assertEvent("download1", BEGIN, 1);
 
     loader.inject(3, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(); // still waiting on fragment 1
+    progress.assertNoEvents();
 
     loader.inject(5, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(); // still waiting on fragment 1
+    progress.assertNoEvents();
 
     // say that 1 loads, which should trigger a chain of backlogged downloads
     loader.fragmentHasLoaded(1);
     reqs.assertFragmentsRequested(2); // next initial
+    progress.assertEvent("download1", END, 1);
+    progress.assertEvent("download2", BEGIN, 2);
 
     loader.fragmentHasLoaded(2);
     reqs.assertFragmentsRequested(3); // next initial
+    progress.assertEvent("download2", END, 2);
+    progress.assertEvent("download3", BEGIN, 3);
 
     loader.fragmentHasLoaded(3);
     reqs.assertFragmentsRequested(numEntries); // leftovers
+    progress.assertEvent("download3", END, 3);
+    progress.assertEvent("leftoversDownload", BEGIN, numEntries);
 
     loader.leftoversFragmentHasLoaded();
     reqs.assertFragmentsRequested(5);
+    progress.assertEvent("leftoversDownload", END, numEntries);
+    progress.assertEvent("download5", BEGIN, 5);
 
     loader.fragmentHasLoaded(5);
     reqs.assertFragmentsRequested(); // quiescent
+    progress.assertEvent("download5", END, 5);
+    progress.assertNoEvents();
 
     // check that new exclusive fragment requests work
     loader.inject(4, NULL_ERROR_HANDLER);
     reqs.assertFragmentsRequested(4);
+    progress.assertEvent("download4", BEGIN, 4);
+
     loader.fragmentHasLoaded(4);
     reqs.assertFragmentsRequested();
+    progress.assertEvent("download4", END, 4);
+    progress.assertNoEvents();
   }
 
   private void loadFailed(MockLoadStrategy reqs, int fragment) {
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 6396c90..3cba452 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -42,6 +42,7 @@
 import com.google.gwt.dev.jjs.test.NativeLongTest;
 import com.google.gwt.dev.jjs.test.ObjectIdentityTest;
 import com.google.gwt.dev.jjs.test.RunAsyncFailureTest;
+import com.google.gwt.dev.jjs.test.RunAsyncMetricsIntegrationTest;
 import com.google.gwt.dev.jjs.test.RunAsyncTest;
 import com.google.gwt.dev.jjs.test.SingleJsoImplTest;
 import com.google.gwt.dev.jjs.test.UnstableGeneratorTest;
@@ -86,6 +87,7 @@
     suite.addTestSuite(NativeLongTest.class);
     suite.addTestSuite(ObjectIdentityTest.class);
     suite.addTestSuite(RunAsyncFailureTest.class);
+    suite.addTestSuite(RunAsyncMetricsIntegrationTest.class);
     suite.addTestSuite(RunAsyncTest.class);
     suite.addTestSuite(ScriptOnlyTest.class);
     suite.addTestSuite(SingleJsoImplTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/RunAsyncMetricsIntegrationTest.gwt.xml b/user/test/com/google/gwt/dev/jjs/RunAsyncMetricsIntegrationTest.gwt.xml
new file mode 100644
index 0000000..622cf88
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/RunAsyncMetricsIntegrationTest.gwt.xml
@@ -0,0 +1,21 @@
+<!--                                                                        -->
+<!-- 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- This module is separate because the assocatied test calls runAsync -->
+<!-- and checks that the right lightweight metrics are signalled.       -->
+<!-- If this were combined with other tests that call runAsync, the     -->
+<!-- order of those events could change.                                -->
+<module>
+    <inherits name="com.google.gwt.dev.jjs.CompilerSuite"/>
+</module>
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest.java
new file mode 100644
index 0000000..3592003
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.dev.jjs.test;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * A simple test that the
+ * {@link GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback) runAsync}
+ * lightweight metrics make it all the way out to the JavaScript LWM system. A
+ * number of more detailed tests are in
+ * {@link com.google.gwt.core.client.impl.AsyncFragmentLoaderTest}.
+ */
+public class RunAsyncMetricsIntegrationTest extends GWTTestCase {
+  private static final class LightweightMetricsEvent extends JavaScriptObject {
+    protected LightweightMetricsEvent() {
+    }
+
+    public native String getEvtGroup() /*-{
+      return this.evtGroup;
+    }-*/;
+
+    public native int getFragment() /*-{
+      return this.fragment;
+    }-*/;
+
+    public native int getMillis() /*-{
+      return this.millis;
+    }-*/;
+
+    public native String getModuleName() /*-{
+      return this.moduleName;
+    }-*/;
+
+    public native int getSize() /*-{
+      return this.size;
+    }-*/;
+
+    public native String getSubSystem() /*-{
+      return this.subSystem;
+    }-*/;
+
+    public native String getType() /*-{
+      return this.type;
+    }-*/;
+  }
+
+  private static class LightweightMetricsObserver {
+    public final Queue<LightweightMetricsEvent> events = new LinkedList<LightweightMetricsEvent>();
+    @SuppressWarnings("unused")
+    private JavaScriptObject previousObserver;
+
+    /**
+     * Install this observer and start watching events.
+     */
+    public native void install() /*-{
+      var self = this;
+      this.@com.google.gwt.dev.jjs.test.RunAsyncMetricsIntegrationTest.LightweightMetricsObserver::previousObserver
+        = $stats;
+      $stats = function(evt) {
+        self.@com.google.gwt.dev.jjs.test.RunAsyncMetricsIntegrationTest.LightweightMetricsObserver::recordEvent(Lcom/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest$LightweightMetricsEvent;)(evt);
+      }
+    }-*/;
+
+    /**
+     * No more to do; uninstall this observer.
+     */
+    public native void uninstall() /*-{
+      $stats = this.@com.google.gwt.dev.jjs.test.RunAsyncMetricsIntegrationTest.LightweightMetricsObserver::previousObserver
+    }-*/;
+
+    @SuppressWarnings("unused")
+    private void recordEvent(LightweightMetricsEvent event) {
+      if (event.getSubSystem().equals("runAsync")) {
+        events.add(event);
+      }
+    }
+  }
+
+  private static final int TIMEOUT = 10000;
+
+  private final LightweightMetricsObserver lwmObserver = new LightweightMetricsObserver();
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.RunAsyncMetricsIntegrationTest";
+  }
+
+  @Override
+  public void gwtSetUp() {
+    lwmObserver.events.clear();
+    lwmObserver.install();
+  }
+
+  @Override
+  public void gwtTearDown() {
+    lwmObserver.uninstall();
+  }
+
+  public void testMetricsSignalled() {
+    if (!GWT.isScript()) {
+      // There are no runAsync lightweight metrics in hosted mode
+      return;
+    }
+    delayTestFinish(TIMEOUT);
+    GWT.runAsync(new RunAsyncCallback() {
+      public void onFailure(Throwable reason) {
+        fail();
+      }
+
+      public void onSuccess() {
+        DeferredCommand.addCommand(new Command() {
+
+          public void execute() {
+            checkMetrics();
+            finishTest();
+          }
+
+        });
+      }
+    });
+  }
+
+  /**
+   * This should be called after a runAsync has been called and completed. It
+   * checks that all lightweight metrics have occurred and are well formatted.
+   */
+  private void checkMetrics() {
+    assertTrue(!lwmObserver.events.isEmpty());
+
+    if (lwmObserver.events.peek().getEvtGroup().equals("noDownloadNeeded")) {
+      checkMetricsWithoutCodeSplitting();
+    } else {
+      checkMetricsWithCodeSplitting();
+    }
+
+    assertTrue(lwmObserver.events.isEmpty());
+  }
+
+  /**
+   * Check the LWM when code splitting occurred and so downloads are necessary.
+   */
+  private void checkMetricsWithCodeSplitting() {
+    int lastMillis;
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("leftoversDownload", event.getEvtGroup());
+      assertEquals("begin", event.getType());
+      assertEquals(2, event.getFragment());
+      assertTrue(event.getMillis() != 0);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("leftoversDownload", event.getEvtGroup());
+      assertEquals("end", event.getType());
+      assertEquals(2, event.getFragment());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("download1", event.getEvtGroup());
+      assertEquals("begin", event.getType());
+      assertEquals(1, event.getFragment());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("download1", event.getEvtGroup());
+      assertEquals("end", event.getType());
+      assertEquals(1, event.getFragment());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("runCallbacks1", event.getEvtGroup());
+      assertEquals("begin", event.getType());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("runCallbacks1", event.getEvtGroup());
+      assertEquals("end", event.getType());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+  }
+
+  /**
+   * Check the LWM assuming no code splitting happened.
+   */
+  private void checkMetricsWithoutCodeSplitting() {
+    int lastMillis;
+
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("noDownloadNeeded", event.getEvtGroup());
+      assertEquals("begin", event.getType());
+      assertTrue(event.getMillis() != 0);
+      lastMillis = event.getMillis();
+    }
+    {
+      LightweightMetricsEvent event = lwmObserver.events.remove();
+      assertEquals(getJunitModuleName(), event.getModuleName());
+      assertEquals("noDownloadNeeded", event.getEvtGroup());
+      assertEquals("end", event.getType());
+      assertTrue(event.getMillis() >= lastMillis);
+      lastMillis = event.getMillis();
+    }
+  }
+
+  private String getJunitModuleName() {
+    return getModuleName() + ".JUnit";
+  }
+}