Fixes issue #2109.  This change toJUnit catches errors that occur during module load.  JUnitHostImpl generates a host page which defines the GWT error handling functions.  If one of these functions is called, an XHR is made back to the servlet, reporting the failure.  The servlet then terminates the test.

Review by: bruce (TBR)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1922 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index aca4ab0..21d4960 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -186,11 +186,7 @@
       RequestParts parts = new RequestParts(request);
 
       // See if the request references a module we know.
-      // Note that we do *not* actually try to load the module here, because
-      // we're only looking for servlet invocations, which can only happen
-      // when we have *already* loaded the destination module to serve up the
-      // client code in the first place.
-      moduleDef = loadedModulesByName.get(parts.moduleName);
+      moduleDef = getModuleDef(logger, parts.moduleName);
       if (moduleDef != null) {
         // Okay, we know this module. Do we know this servlet path?
         // It is right to prepend the slash because (1) ModuleDefSchema requires
@@ -339,7 +335,7 @@
     writer.println(".nocache.js'></script>");
 
     // Create a property for each query param.
-    Map<String, String[]> params = request.getParameterMap();
+    Map<String, String[]> params = getParameterMap(request);
     for (Map.Entry<String, String[]> entry : params.entrySet()) {
       String[] values = entry.getValue();
       if (values.length > 0) {
@@ -608,6 +604,11 @@
     return outDir;
   }
 
+  @SuppressWarnings("unchecked")
+  private Map<String, String[]> getParameterMap(HttpServletRequest request) {
+    return request.getParameterMap();
+  }
+
   private String guessMimeType(String fullPath) {
     int dot = fullPath.lastIndexOf('.');
     if (dot != -1) {
diff --git a/user/src/com/google/gwt/junit/JUnitFatalLaunchException.java b/user/src/com/google/gwt/junit/JUnitFatalLaunchException.java
new file mode 100644
index 0000000..bfa602d
--- /dev/null
+++ b/user/src/com/google/gwt/junit/JUnitFatalLaunchException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * When thrown, no test in the current module can possibly succeed.
+ */
+public class JUnitFatalLaunchException extends RuntimeException {
+
+  public JUnitFatalLaunchException() {
+  }
+
+  public JUnitFatalLaunchException(String message) {
+    super(message);
+  }
+
+  public JUnitFatalLaunchException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public JUnitFatalLaunchException(Throwable cause) {
+    super(cause);
+  }
+
+}
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 99806dd..41c11d3 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -450,7 +450,7 @@
       runStyle.maybeLaunchModule(currentModuleName, !sameTest);
     } catch (UnableToCompleteException e) {
       lastLaunchFailed = true;
-      testResult.addError(testCase, e);
+      testResult.addError(testCase, new JUnitFatalLaunchException(e));
       return;
     }
 
@@ -498,6 +498,9 @@
         testResult.addFailure(testCase, (AssertionFailedError) exception);
       } else if (exception != null) {
         // A real failure
+        if (exception instanceof JUnitFatalLaunchException) {
+          lastLaunchFailed = true;
+        }
         testResult.addError(testCase, exception);
       }
 
diff --git a/user/src/com/google/gwt/junit/RunStyle.java b/user/src/com/google/gwt/junit/RunStyle.java
index 6f19ebf..91b8c21 100644
--- a/user/src/com/google/gwt/junit/RunStyle.java
+++ b/user/src/com/google/gwt/junit/RunStyle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -25,7 +25,7 @@
   /**
    * Possibly launches a browser window to run the specified module.
    * 
-   * @param moduleName The module to run.
+   * @param moduleName the module to run
    * @param forceLaunch If <code>true</code>, forces a new browser window to
    *          be launched (because <code>testCaseClassName</code> changed)
    * @throws UnableToCompleteException
@@ -41,4 +41,14 @@
   public boolean wasInterrupted() {
     return false;
   }
+
+  /**
+   * Gets the suffix of the URL to load.
+   * 
+   * @param moduleName the module to run
+   * @return a URL suffix that should be loaded
+   */
+  protected String getUrlSuffix(String moduleName) {
+    return moduleName + "/junithost/junit.html";
+  }
 }
diff --git a/user/src/com/google/gwt/junit/RunStyleLocalHosted.java b/user/src/com/google/gwt/junit/RunStyleLocalHosted.java
index 38097c8..5959f7a 100644
--- a/user/src/com/google/gwt/junit/RunStyleLocalHosted.java
+++ b/user/src/com/google/gwt/junit/RunStyleLocalHosted.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -44,7 +44,7 @@
   public void maybeLaunchModule(String moduleName, boolean forceLaunch)
       throws UnableToCompleteException {
     if (forceLaunch) {
-      launchUrl(moduleName + "/");
+      launchUrl(getUrlSuffix(moduleName));
     }
   }
 
diff --git a/user/src/com/google/gwt/junit/RunStyleLocalWeb.java b/user/src/com/google/gwt/junit/RunStyleLocalWeb.java
index cb5dde4..ce771b9 100644
--- a/user/src/com/google/gwt/junit/RunStyleLocalWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleLocalWeb.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -41,7 +41,7 @@
     if (forceLaunch) {
       BrowserWidget browserWindow = getBrowserWindow();
       shell.compileForWebMode(moduleName, browserWindow.getUserAgent());
-      launchUrl(moduleName + "/?" + PROP_GWT_HYBRID_MODE);
+      launchUrl(getUrlSuffix(moduleName) + "?" + PROP_GWT_HYBRID_MODE);
     }
   }
 
diff --git a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
index 5e55ff9..ca5835d 100644
--- a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
@@ -76,7 +76,7 @@
         throw new RuntimeException("Unable to determine my ip address", e);
       }
       String url = "http://" + localhost + ":" + shell.getPort() + "/"
-          + moduleName;
+          + getUrlSuffix(moduleName);
 
       try {
         for (int i = 0; i < remoteTokens.length; ++i) {
diff --git a/user/src/com/google/gwt/junit/junit.html b/user/src/com/google/gwt/junit/junit.html
new file mode 100644
index 0000000..4efef11
--- /dev/null
+++ b/user/src/com/google/gwt/junit/junit.html
@@ -0,0 +1,50 @@
+<!--
+Copyright 2008 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.
+-->
+<html>
+<head>
+<meta name='gwt:onLoadErrorFn' content='junitOnLoadErrorFn'>
+<meta name='gwt:onPropertyErrorFn' content='junitOnPropertyErrorFn'>
+</head>
+<body>
+<script language='javascript'>
+function junitOnLoadErrorFn(moduleName) {
+  junitError('Failed to load module "' + moduleName +
+    '".\nPlease see the log for details.');
+}
+
+function junitOnPropertyErrorFn(propName, allowedValues, badValue) {
+  alert(propName);
+  var msg = 'While attempting to load the module, property "' + propName;
+  if (badValue != null) {
+    msg += '" was set to the unexpected value "' + badValue + '"';
+  } else {
+    msg += '" was not specified';
+  }
+  msg += 'Allowed values: ' + allowedValues;
+  junitError(msg);
+}
+
+function junitError(msg) {
+  var xmlHttpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Msxml2.XMLHTTP");
+  xmlHttpRequest.open('POST', 'loadError', true);
+  xmlHttpRequest.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
+  xmlHttpRequest.send(msg);
+}
+</script>
+<script language='javascript' src='__MODULE_NAME__.nocache.js'></script>
+<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
+</body>
+</html>
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 2ce11c5..e9103a3 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -15,21 +15,28 @@
  */
 package com.google.gwt.junit.server;
 
+import com.google.gwt.junit.JUnitFatalLaunchException;
 import com.google.gwt.junit.JUnitMessageQueue;
 import com.google.gwt.junit.JUnitShell;
+import com.google.gwt.junit.client.TestResults;
+import com.google.gwt.junit.client.Trial;
 import com.google.gwt.junit.client.impl.ExceptionWrapper;
 import com.google.gwt.junit.client.impl.JUnitHost;
 import com.google.gwt.junit.client.impl.StackTraceWrapper;
-import com.google.gwt.junit.client.TestResults;
-import com.google.gwt.junit.client.Trial;
 import com.google.gwt.user.client.rpc.InvocationException;
+import com.google.gwt.user.server.rpc.RPCServletUtils;
 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+import com.google.gwt.util.tools.Utility;
 
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.util.List;
 
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * An RPC servlet that serves as a proxy to JUnitTestShell. Enables
@@ -99,6 +106,41 @@
         TIME_TO_WAIT_FOR_TESTNAME);
   }
 
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    String requestURI = request.getRequestURI();
+    if (requestURI.endsWith("/junithost/junit.html")) {
+      String prefix = getPrefix(requestURI);
+      String moduleName = getModuleName(prefix);
+      response.setContentType("text/html");
+      PrintWriter writer = response.getWriter();
+      String htmlSrc = Utility.getFileFromClassPath("com/google/gwt/junit/junit.html");
+      htmlSrc = htmlSrc.replace("__MODULE_NAME__", prefix + "/" + moduleName);
+      writer.write(htmlSrc);
+      return;
+    }
+    response.setContentType("text/plain");
+    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+  }
+
+  @Override
+  protected void service(HttpServletRequest request,
+      HttpServletResponse response) throws ServletException, IOException {
+    String requestURI = request.getRequestURI();
+    if (requestURI.endsWith("/junithost/loadError")) {
+      String moduleName = getModuleName(getPrefix(requestURI));
+      String requestPayload = RPCServletUtils.readContentAsUtf8(request);
+      TestResults results = new TestResults();
+      Trial trial = new Trial();
+      trial.setException(new JUnitFatalLaunchException(requestPayload));
+      results.getTrials().add(trial);
+      getHost().reportResults(moduleName, results);
+    } else {
+      super.service(request, response);
+    }
+  }
+
   /**
    * Deserializes an ExceptionWrapper back into a Throwable.
    */
@@ -214,4 +256,16 @@
     String machine = request.getRemoteHost();
     return machine + " / " + agent;
   }
+
+  private String getModuleName(String prefix) {
+    int pos = prefix.lastIndexOf('/');
+    String moduleName = prefix.substring(pos + 1);
+    return moduleName;
+  }
+
+  private String getPrefix(String requestURI) {
+    int pos = requestURI.indexOf("/junithost");
+    String prefix = requestURI.substring(0, pos);
+    return prefix;
+  }
 }