Revision: 5906 Author: [email protected] Date: Thu Aug 6 13:55:18 2009 Log: Fixes the two known problems with runAsync lightweight metrics events. Also, adds test cases that could have caught those problems.
Review by: bobv http://code.google.com/p/google-web-toolkit/source/detail?r=5906 Added: /trunk/user/test/com/google/gwt/dev/jjs/RunAsyncMetricsIntegrationTest.gwt.xml /trunk/user/test/com/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest.java Modified: /trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java /trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java ======================================= --- /dev/null +++ /trunk/user/test/com/google/gwt/dev/jjs/RunAsyncMetricsIntegrationTest.gwt.xml Thu Aug 6 13:55:18 2009 @@ -0,0 +1,17 @@ +<!-- --> +<!-- 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. --> + +<module> + <inherits name="com.google.gwt.dev.jjs.CompilerSuite"/> +</module> ======================================= --- /dev/null +++ /trunk/user/test/com/google/gwt/dev/jjs/test/RunAsyncMetricsIntegrationTest.java Thu Aug 6 13:55:18 2009 @@ -0,0 +1,211 @@ +/* + * 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; + th...@com.google.gwt.dev.jjs.test.runasyncmetricsintegrationtest.lightweightmetricsobserver::previousObserver + = $stats; + $stats = function(evt) { + se...@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 = th...@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() { + 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(); + } + + assertTrue(lwmObserver.events.isEmpty()); + } + + private String getJunitModuleName() { + return getModuleName() + ".JUnit"; + } +} ======================================= --- /trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java Fri Jul 31 14:23:27 2009 +++ /trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java Thu Aug 6 13:55:18 2009 @@ -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; } @@ -487,6 +486,11 @@ public void logEventProgress(String eventGroup, String type) { 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. @@ -511,6 +515,10 @@ private int leftoversFragment() { 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 @@ -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); } } } ======================================= --- /trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java Fri Jul 31 14:23:27 2009 +++ /trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java Thu Aug 6 13:55:18 2009 @@ -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 final Logger NULL_LOGGER = new Logger() { + private static class MockProgressEvent { + public final String eventGroup; + public final Integer fragment; + public final String type; + + 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) { ======================================= --- /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java Fri Jun 19 09:06:11 2009 +++ /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java Thu Aug 6 13:55:18 2009 @@ -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); --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
