Test the scenario where GWT.runAsync() fails to load a code fragment due to a
server or network problem. We use a custom servlet and linker to inject the
errors.
Review by: spoon
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5286 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index b63b22f..e11d31b 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -93,6 +93,15 @@
return ".cache.html";
}
+ /**
+ * Returns the subdirectory name to be used by getModulPrefix when requesting
+ * a runAsync module. The default implementation returns the value of
+ * FRAGMENT_SUDBIR. This has been factored out for test cases.
+ */
+ protected String getFragmentSubdir() {
+ return FRAGMENT_SUBDIR;
+ }
+
@Override
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
String strongName) {
@@ -123,7 +132,7 @@
return out.toString();
}
-
+
@Override
protected String getSelectionScriptTemplate(TreeLogger logger,
LinkerContext context) {
@@ -157,7 +166,7 @@
out.print("function __gwtStartLoadingFragment(frag) {");
out.indentIn();
out.newlineOpt();
- out.print(" return $moduleBase + '" + FRAGMENT_SUBDIR
+ out.print(" return $moduleBase + '" + getFragmentSubdir()
+ "/' + $strongName + '/' + frag + '" + FRAGMENT_EXTENSION + "';");
out.indentOut();
out.newlineOpt();
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 9f0c603..db89ae3 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -39,6 +39,7 @@
import com.google.gwt.dev.jjs.test.MiscellaneousTest;
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.RunAsyncTest;
import com.google.gwt.dev.jjs.test.SingleJsoImplTest;
import com.google.gwt.dev.jjs.test.UnstableGeneratorTest;
@@ -80,6 +81,7 @@
suite.addTestSuite(MiscellaneousTest.class);
suite.addTestSuite(NativeLongTest.class);
suite.addTestSuite(ObjectIdentityTest.class);
+ suite.addTestSuite(RunAsyncFailureTest.class);
suite.addTestSuite(RunAsyncTest.class);
suite.addTestSuite(SingleJsoImplTest.class);
suite.addTestSuite(UnstableGeneratorTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/RunAsyncFailure.gwt.xml b/user/test/com/google/gwt/dev/jjs/RunAsyncFailure.gwt.xml
new file mode 100644
index 0000000..29935b6
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/RunAsyncFailure.gwt.xml
@@ -0,0 +1,22 @@
+<!-- -->
+<!-- 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>
+ <source path='test' />
+ <servlet path="/runAsyncFailure"
+ class="com.google.gwt.user.server.runasync.RunAsyncFailureServlet"/>
+ <define-linker name="runAsyncFailure"
+ class="com.google.gwt.user.server.runasync.RunAsyncFailureIFrameLinker"/>
+ <add-linker name="runAsyncFailure"/>
+</module>
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
new file mode 100644
index 0000000..bbf7b92
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.RunAsyncCallback;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+
+/**
+ * Tests runAsync server/network failure handling.
+ */
+public class RunAsyncFailureTest extends GWTTestCase {
+
+ abstract static class MyRunAsyncCallback implements RunAsyncCallback {
+ private static int sToken = 0;
+ private int token;
+
+ public MyRunAsyncCallback() {
+ token = sToken++;
+ }
+
+ public int getToken() {
+ return token;
+ }
+ }
+
+ private static final int RUNASYNC_TIMEOUT = 30000;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.dev.jjs.RunAsyncFailure";
+ }
+
+ // Repeated runAsync using a Timer
+ public void runAsync1(final int attempt) {
+ log("runAsync1: attempt = " + attempt);
+ GWT.runAsync(new MyRunAsyncCallback() {
+ public void onFailure(Throwable caught) {
+ // Fail the test if too many attempts have taken place.
+ if (attempt > 20) {
+ fail();
+ }
+
+ int token = getToken();
+ log("onFailure: attempt = " + attempt + ", token = " + token
+ + ", caught = " + caught);
+ new Timer() {
+ @Override
+ public void run() {
+ runAsync1(attempt + 1);
+ }
+ }.schedule(100);
+ }
+
+ public void onSuccess() {
+ int token = getToken();
+ log("onSuccess: attempt = " + attempt + ", token = " + token);
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Test the basic functionality of retrying runAsync until is succeeds.
+ * A Timer is used to avoid nesting GWT.runAsync calls.
+ */
+ public void testHttpFailureRetries() {
+ delayTestFinish(RUNASYNC_TIMEOUT);
+ runAsync1(0);
+ }
+
+ private native void log(String message) /*-{
+ $wnd.console.log(message);
+ }-*/;
+}
diff --git a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java
new file mode 100644
index 0000000..33c5acc
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureIFrameLinker.java
@@ -0,0 +1,33 @@
+/*
+ * 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.user.server.runasync;
+
+import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.linker.IFrameLinker;
+
+/**
+ * Load modules from a custom servlet in order to test download failure
+ * behavior.
+ */
+@LinkerOrder(Order.PRE)
+public class RunAsyncFailureIFrameLinker extends IFrameLinker {
+
+ @Override
+ protected String getFragmentSubdir() {
+ return "runAsyncFailure/" + FRAGMENT_SUBDIR;
+ }
+}
diff --git a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java
new file mode 100644
index 0000000..a4a28e5
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java
@@ -0,0 +1,120 @@
+/*
+ * 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.user.server.runasync;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet for {@link com.google.gwt.dev.jjs.test.RunAsyncFailureTest}.
+ */
+public class RunAsyncFailureServlet extends HttpServlet {
+
+ private static final boolean DEBUG = false;
+ private static final HashSet<String> errorFragments = new HashSet<String>();
+ private static final int[] responses = {
+ HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ HttpServletResponse.SC_GATEWAY_TIMEOUT,
+ HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ HttpServletResponse.SC_GATEWAY_TIMEOUT,
+ HttpServletResponse.SC_OK // must be last
+ };
+
+ static {
+ errorFragments.add("2.cache.js");
+ }
+
+ private static void debug(String message) {
+ if (DEBUG) {
+ System.out.println(message);
+ }
+ }
+
+ HashMap<String,Integer> triesMap = new HashMap<String,Integer>();
+
+ private int sSerial = 0;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ String originalUri = req.getRequestURI();
+ debug("doGet: " + originalUri);
+ String uri = originalUri.replace("/runAsyncFailure", "");
+
+ int response = getDesiredResponse(uri);
+ String fragment = uri.substring(uri.lastIndexOf('/') + 1);
+ if (!errorFragments.contains(fragment)
+ || response == HttpServletResponse.SC_OK) {
+ // Delegate the actual data fetch to the main servlet
+ String host = req.getLocalName();
+ int port = req.getLocalPort();
+ String realUrl = "http://" + host + ":" + port + uri;
+ debug("Fetching: " + realUrl);
+
+ int bytes = 0;
+ try {
+ URL url = new URL(realUrl);
+ InputStream is = url.openStream();
+ OutputStream os = resp.getOutputStream();
+
+ byte[] data = new byte[8192];
+ int nbytes;
+ while ((nbytes = is.read(data)) != -1) {
+ os.write(data, 0, nbytes);
+ bytes += nbytes;
+ }
+ is.close();
+ os.close();
+ } catch (IOException e) {
+ debug("IOException fetching real data: " + e);
+ throw e;
+ }
+
+ resp.setHeader("Cache-Control", "no-cache");
+ resp.setContentLength(bytes);
+ resp.setContentType("text/html");
+ resp.setStatus(HttpServletResponse.SC_OK);
+
+ debug("doGet: served " + uri + " (" + bytes + " bytes)");
+ } else {
+ resp.setHeader("Cache-Control", "no-cache");
+ resp.sendError(response, "serial=" + getNextSerial());
+
+ debug("doGet: sent error " + response + " for " + uri);
+ }
+ }
+
+ private int getDesiredResponse(String resource) {
+ Integer t = triesMap.get(resource);
+ int tries = t == null ? 0 : t.intValue();
+ triesMap.put(resource, tries + 1);
+
+ return responses[tries % responses.length];
+ }
+
+ private synchronized int getNextSerial() {
+ return sSerial++;
+ }
+}