Goktug Gokdogan has uploaded a new change for review.

  https://gwt-review.googlesource.com/2290


Change subject: Refactors c.g.gwt.junit to use common SerializableThrowable & StacktraceDeobfuscator.
......................................................................

Refactors c.g.gwt.junit to use common SerializableThrowable & StacktraceDeobfuscator.

- Gets rid of dublicate code in junit for deobfuscation of stack traces
- Improves the messaging of unserializable exceptions
- Minor improvements to the reporting of test infra failures

Change-Id: I1e1021bc99ac88ea6d9d47c3d23c83e79a896213
---
M user/src/com/google/gwt/core/client/JavaScriptException.java
M user/src/com/google/gwt/junit/JUnitMessageQueue.java
M user/src/com/google/gwt/junit/JUnitShell.java
D user/src/com/google/gwt/junit/client/impl/ExceptionWrapper.java
M user/src/com/google/gwt/junit/client/impl/JUnitResult.java
M user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
M user/src/com/google/gwt/junit/server/JUnitHostImpl.java
M user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
A user/test/com/google/gwt/junit/DefaultExceptionAsserter.java
M user/test/com/google/gwt/junit/GwtTestSuiteWithExpectedFailures.java
M user/test/com/google/gwt/junit/JUnitSuite.java
M user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
M user/test/com/google/gwt/junit/TestSuiteWithOrder.java
A user/test/com/google/gwt/junit/client/ExceptionAsserter.java
R user/test/com/google/gwt/junit/client/ExpectedFailure.java
M user/test/com/google/gwt/junit/client/GWTTestCaseAsyncTest.java
M user/test/com/google/gwt/junit/client/GWTTestCaseSetupTearDownTest.java
A user/test/com/google/gwt/junit/client/GWTTestCaseStackTraceTest.java
M user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
M user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java
20 files changed, 365 insertions(+), 280 deletions(-)



diff --git a/user/src/com/google/gwt/core/client/JavaScriptException.java b/user/src/com/google/gwt/core/client/JavaScriptException.java
index 5c44f48..8f15edd 100644
--- a/user/src/com/google/gwt/core/client/JavaScriptException.java
+++ b/user/src/com/google/gwt/core/client/JavaScriptException.java
@@ -128,7 +128,7 @@
       StackTraceCreator.createStackTrace(this);
     }
   }
-
+
   public JavaScriptException(String name, String description) {
     this.message = "JavaScript " + name + " exception: " + description;
     this.name = name;
@@ -137,10 +137,8 @@
   }

   /**
-   * Used for server-side instantiation during JUnit runs. Exceptions are
-   * manually marshaled through
- * <code>com.google.gwt.junit.client.impl.ExceptionWrapper</code> objects.
-   *
+   * Used for testing instantiations.
+   *
    * @param message the detail message
    */
   protected JavaScriptException(String message) {
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index e2c4bf7..c03a03a 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -15,11 +15,12 @@
  */
 package com.google.gwt.junit;

+import com.google.gwt.core.shared.SerializableThrowable;
 import com.google.gwt.junit.client.TimeoutException;
-import com.google.gwt.junit.client.impl.JUnitResult;
 import com.google.gwt.junit.client.impl.JUnitHost.ClientInfo;
 import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
 import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+import com.google.gwt.junit.client.impl.JUnitResult;

 import java.util.ArrayList;
 import java.util.HashMap;
@@ -27,8 +28,8 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;

 /**
  * A message queue to pass data between {@link JUnitShell} and
@@ -463,12 +464,21 @@
       if (result == null) {
         return true;
       }
-      Throwable exception = result.getException();
- if (exception != null && !isMember(exception, THROWABLES_NOT_RETRIED)) {
+
+      Class<?> thrown = getExceptionClass(result.getException());
+      if (thrown != null && !isMember(thrown, THROWABLES_NOT_RETRIED)) {
         return true;
       }
     }
     return false;
+  }
+
+  private Class<?> getExceptionClass(SerializableThrowable throwable) {
+    try {
+ return throwable == null ? null : Class.forName(throwable.getDesignatedType());
+    } catch (Exception e) {
+      return null;
+    }
   }

   void removeResults(TestInfo testInfo) {
@@ -521,10 +531,10 @@
     return results;
   }

-  private boolean isMember(Throwable exception,
+  private boolean isMember(Class<?> throwableClass,
       Set<Class<? extends Throwable>> throwableSet) {
     for (Class<? extends Throwable> throwable : throwableSet) {
-      if (throwable.isInstance(exception)) {
+      if (throwable.isAssignableFrom(throwableClass)) {
         return true;
       }
     }
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 3ae26a9..7328075 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.core.shared.SerializableThrowable;
 import com.google.gwt.dev.ArgProcessorBase;
 import com.google.gwt.dev.Compiler;
 import com.google.gwt.dev.DevMode;
@@ -1224,38 +1225,35 @@
         + " != " + messageQueue.getNumClients();

     for (Entry<ClientStatus, JUnitResult> entry : results.entrySet()) {
-      ClientStatus client = entry.getKey();
       JUnitResult result = entry.getValue();
       assert (result != null);
-      Throwable exception = result.getException();

-      // Let the user know the browser in which the failure happened.
-      if (exception != null) {
-        String msg = "Remote test failed at " + client.getDesc();
-        if (exception instanceof AssertionFailedError) {
-          String oldMessage = exception.getMessage();
-          if (oldMessage != null) {
-            msg += "\n" + exception.getMessage();
-          }
- AssertionFailedError newException = new AssertionFailedError(msg);
-          newException.setStackTrace(exception.getStackTrace());
-          newException.initCause(exception.getCause());
-          exception = newException;
+      SerializableThrowable thrown = result.getException();
+      if (thrown != null) {
+        if (isAssertionFailedError(thrown)) {
+          testResult.addFailure(testCase, toAssertionFailedError(thrown));
         } else {
-          exception = new RuntimeException(msg, exception);
+          testResult.addError(testCase, thrown);
         }
       }
+    }
+  }

-      // 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);
-      }
+ private AssertionFailedError toAssertionFailedError(SerializableThrowable thrown) { + AssertionFailedError error = new AssertionFailedError(thrown.getMessage());
+    error.setStackTrace(thrown.getStackTrace());
+    if (thrown.getCause() != null) {
+      error.initCause(thrown.getCause());
+    }
+    return error;
+  }
+
+  private boolean isAssertionFailedError(SerializableThrowable throwable) {
+    try {
+      return AssertionFailedError.class.isAssignableFrom(
+          Class.forName(throwable.getDesignatedType()));
+    } catch (Exception e) {
+      return false;
     }
   }

diff --git a/user/src/com/google/gwt/junit/client/impl/ExceptionWrapper.java b/user/src/com/google/gwt/junit/client/impl/ExceptionWrapper.java
deleted file mode 100644
index 8a46f3f..0000000
--- a/user/src/com/google/gwt/junit/client/impl/ExceptionWrapper.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2006 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.client.impl;
-
-import java.io.Serializable;
-
-/**
- * Wraps a {@link Throwable}, and explicitly serializes cause and stack trace.
- */
-final class ExceptionWrapper implements Serializable {
-
-  /**
-   * Stand-in for the transient {@link Throwable#getCause()} in GWT JRE.
-   */
-  ExceptionWrapper causeWrapper;
-
-  /**
-   * The wrapped exception.
-   */
-  Throwable exception;
-
-  /**
- * Stand-in for the transient {@link Throwable#getStackTrace()} in GWT JRE.
-   */
-  StackTraceElement[] stackTrace;
-
-  /**
- * If true, the exception's inner stack trace and cause have been initialized.
-   * Defaults to false immediate after deserialization.
-   */
-  private transient boolean isExceptionInitialized;
-
-  /**
- * Creates an {@link ExceptionWrapper} around an existing {@link Throwable}.
-   *
-   * @param exception the {@link Throwable} to wrap.
-   */
-  public ExceptionWrapper(Throwable exception) {
-    this.exception = exception;
-    this.stackTrace = exception.getStackTrace();
-    Throwable cause = exception.getCause();
-    if (cause != null) {
-      this.causeWrapper = new ExceptionWrapper(cause);
-    }
-    this.isExceptionInitialized = true;
-  }
-
-  /**
-   * Deserialization constructor.
-   */
-  ExceptionWrapper() {
-    this.isExceptionInitialized = false;
-  }
-
-  public Throwable getException() {
-    if (!isExceptionInitialized) {
-      exception.setStackTrace(stackTrace);
-      if (causeWrapper != null) {
-        exception.initCause(causeWrapper.getException());
-      }
-      isExceptionInitialized = true;
-    }
-    return exception;
-  }
-}
-
diff --git a/user/src/com/google/gwt/junit/client/impl/JUnitResult.java b/user/src/com/google/gwt/junit/client/impl/JUnitResult.java
index 8d777d9..e8253ba 100644
--- a/user/src/com/google/gwt/junit/client/impl/JUnitResult.java
+++ b/user/src/com/google/gwt/junit/client/impl/JUnitResult.java
@@ -15,6 +15,11 @@
  */
 package com.google.gwt.junit.client.impl;

+import com.google.gwt.core.shared.SerializableThrowable;
+import com.google.gwt.junit.client.TimeoutException;
+
+import junit.framework.AssertionFailedError;
+
 import java.io.Serializable;

 /**
@@ -33,7 +38,7 @@
   /**
    * If non-null, an exception that occurred during the run.
    */
-  ExceptionWrapper exceptionWrapper;
+  SerializableThrowable thrown;

   // Computed at the server, via HTTP header.
   private transient String agent;
@@ -45,8 +50,8 @@
     return agent;
   }

-  public Throwable getException() {
- return (exceptionWrapper == null) ? null : exceptionWrapper.getException();
+  public SerializableThrowable getException() {
+    return thrown;
   }

   public String getHost() {
@@ -58,7 +63,11 @@
   }

   public void setException(Throwable exception) {
-    this.exceptionWrapper = new ExceptionWrapper(exception);
+    thrown = SerializableThrowable.fromThrowable(exception);
+ // Try to improve exception message if there is no class metadata available
+    if (!thrown.isExactDesignatedTypeKnown()) {
+      improveDesignatedType(thrown, exception);
+    }
   }

   public void setHost(String host) {
@@ -67,11 +76,19 @@

   @Override
   public String toString() {
-    return "TestResult {" + toStringInner() + "}";
+ return "TestResult {thrown: " + thrown + ", agent: " + agent + ", host: " + host + "}";
   }

-  protected String toStringInner() {
-    return "exceptionWrapper: " + exceptionWrapper + ", agent: " + agent
-        + ", host: " + host;
+  /**
+ * Returns best effort type info by checking against some common exceptions for unit tests.
+   */
+ private static void improveDesignatedType(SerializableThrowable t, Throwable designatedType) {
+    if (designatedType instanceof AssertionFailedError) {
+      String className = "junit.framework.AssertionFailedError";
+ t.setDesignatedType(className, AssertionFailedError.class == designatedType.getClass());
+    } else if (designatedType instanceof TimeoutException) {
+      String className = "com.google.gwt.junit.client.TimeoutException";
+ t.setDesignatedType(className, TimeoutException.class == designatedType.getClass());
+    }
   }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
index 574ae25..dbc4e47 100644
--- a/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
+++ b/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
@@ -114,7 +114,7 @@
     SourceWriter sourceWriter =
getSourceWriter(logger, context, packageName, generatedBaseClass, null, null);
     if (sourceWriter != null) {
- writeMethodCreateTestAccessor(logger, context, moduleName, sourceWriter); + writeMethodCreateTestAccessor(sourceWriter, getTestClasses(logger, context, moduleName));
       sourceWriter.commit(logger);
     }

@@ -156,9 +156,7 @@
    * }-{@literal*}/;
    * </pre>
    */
-  private void writeMethodCreateTestAccessor(
- TreeLogger logger, GeneratorContext context, String moduleName, SourceWriter sw) { - Set<JClassType> testClasses = getTestClasses(logger, context, moduleName); + private void writeMethodCreateTestAccessor(SourceWriter sw, Set<JClassType> testClasses) { sw.println("public native final %s createTestAccessor() /*-{", JSNI_TEST_ACCESSOR);
     sw.indent();
     sw.println("return {");
@@ -194,7 +192,8 @@
   }

   private Set<JClassType> getTestClasses(
-      TreeLogger logger, GeneratorContext context, String moduleName) {
+      TreeLogger logger, GeneratorContext context, String moduleName)
+      throws UnableToCompleteException {
     // Check the global set of active tests for this module.
     TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName);
Set<TestInfo> moduleTests = (moduleInfo == null) ? null : moduleInfo.getTests();
@@ -205,7 +204,12 @@
     } else {
       Set<JClassType> testClasses = new LinkedHashSet<JClassType>();
       for (TestInfo testInfo : moduleTests) {
- testClasses.add(context.getTypeOracle().findType(testInfo.getTestClass()));
+        try {
+ testClasses.add(context.getTypeOracle().getType(testInfo.getTestClass()));
+        } catch (NotFoundException e) {
+          logger.log(TreeLogger.ERROR, "Could not find test class", e);
+          throw new UnableToCompleteException();
+        }
       }
       return testClasses;
     }
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 4708d35..1cbade3 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.junit.server;

-import com.google.gwt.dev.util.JsniRef;
-import com.google.gwt.dev.util.StringKey;
+import com.google.gwt.core.server.impl.StackTraceDeobfuscator;
+import com.google.gwt.core.shared.SerializableThrowable;
 import com.google.gwt.junit.JUnitFatalLaunchException;
 import com.google.gwt.junit.JUnitMessageQueue;
 import com.google.gwt.junit.JUnitMessageQueue.ClientInfoExt;
@@ -24,16 +24,13 @@
 import com.google.gwt.junit.client.TimeoutException;
 import com.google.gwt.junit.client.impl.JUnitHost;
 import com.google.gwt.junit.client.impl.JUnitResult;
+import com.google.gwt.junit.linker.JUnitSymbolMapsLinker;
 import com.google.gwt.user.client.rpc.InvocationException;
 import com.google.gwt.user.server.rpc.HybridServiceServlet;
 import com.google.gwt.user.server.rpc.RPCServletUtils;

-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;

 import javax.servlet.ServletException;
@@ -46,18 +43,6 @@
  * test process.
  */
public class JUnitHostImpl extends HybridServiceServlet implements JUnitHost {
-
-  private static class StrongName extends StringKey {
-    protected StrongName(String value) {
-      super(value);
-    }
-  }
-
-  private static class SymbolName extends StringKey {
-    protected SymbolName(String value) {
-      super(value);
-    }
-  }

   /**
    * A hook into GWTUnitTestShell, the underlying unit test process.
@@ -91,7 +76,7 @@
     return sHost;
   }

- private Map<StrongName, Map<SymbolName, String>> symbolMaps = new HashMap<StrongName, Map<SymbolName, String>>();
+  private StackTraceDeobfuscator deobfuscator;

public InitialResponse getTestBlock(int blockIndex, ClientInfo clientInfo)
       throws TimeoutException {
@@ -113,7 +98,6 @@
       ClientInfo clientInfo) throws TimeoutException {
     for (JUnitResult result : results.values()) {
       initResult(getThreadLocalRequest(), result);
-      resymbolize(result.getException());
     }
     JUnitMessageQueue host = getHost();
     ClientInfoExt clientInfoExt = createClientInfo(clientInfo,
@@ -172,83 +156,28 @@
   }

   private void initResult(HttpServletRequest request, JUnitResult result) {
-    String agent = request.getHeader("User-Agent");
-    result.setAgent(agent);
-    String machine = request.getRemoteHost();
-    result.setHost(machine);
+    result.setAgent(request.getHeader("User-Agent"));
+    result.setHost(request.getRemoteHost());
+    SerializableThrowable throwable = result.getException();
+    if (throwable != null) {
+      deobfuscateStackTrace(throwable);
+    }
   }

-  private synchronized Map<SymbolName, String> loadSymbolMap(
-      StrongName strongName) {
-    Map<SymbolName, String> toReturn = symbolMaps.get(strongName);
-    if (toReturn != null) {
-      return toReturn;
-    }
-    toReturn = new HashMap<SymbolName, String>();
-
-    /*
- * Collaborate with SymbolMapsLinker for the location of the symbol data
-     * because the -aux directory isn't accessible via the servlet context.
-     */
-    String path = getRequestModuleBasePath() + "/.junit_symbolMaps/"
-        + strongName.get() + ".symbolMap";
-    InputStream in = getServletContext().getResourceAsStream(path);
-    if (in == null) {
-      symbolMaps.put(strongName, null);
-      return null;
-    }
-
-    BufferedReader bin = new BufferedReader(new InputStreamReader(in));
-    String line;
+  private void deobfuscateStackTrace(SerializableThrowable throwable) {
     try {
-      try {
-        while ((line = bin.readLine()) != null) {
-          if (line.charAt(0) == '#') {
-            continue;
-          }
-          int idx = line.indexOf(',');
-          toReturn.put(new SymbolName(line.substring(0, idx)),
-                       line.substring(idx + 1));
-        }
-      } finally {
-        bin.close();
-      }
+ getDeobfuscator().deobfuscateStackTrace(throwable, getPermutationStrongName());
     } catch (IOException e) {
-      toReturn = null;
+      System.err.println("Cannot deobfucsate stack trace:");
+      e.printStackTrace();
     }
-
-    symbolMaps.put(strongName, toReturn);
-    return toReturn;
   }

-  /**
-   * Resymbolizes a trace from obfuscated symbols to Java names.
-   */
-  private void resymbolize(Throwable exception) {
-    if (exception == null) {
-      return;
+  private StackTraceDeobfuscator getDeobfuscator() throws IOException {
+    if (deobfuscator == null) {
+ String path = getRequestModuleBasePath() + "/" + JUnitSymbolMapsLinker.SYMBOL_MAP_DIR; + deobfuscator = StackTraceDeobfuscator.fromUrl(getServletContext().getResource(path));
     }
-    StackTraceElement[] stackTrace = exception.getStackTrace();
-    StrongName strongName = new StrongName(getPermutationStrongName());
-    Map<SymbolName, String> map = loadSymbolMap(strongName);
-    if (map == null) {
-      return;
-    }
-    for (int i = 0; i < stackTrace.length; ++i) {
-      StackTraceElement ste = stackTrace[i];
-      String symbolData = map.get(new SymbolName(ste.getMethodName()));
-      if (symbolData != null) {
-        // jsniIdent, className, memberName, sourceUri, sourceLine
-        String[] parts = symbolData.split(",");
-        assert parts.length == 6 : "Expected 6, have " + parts.length;
-
-        JsniRef ref = JsniRef.parse(parts[0].substring(0,
-            parts[0].lastIndexOf(')') + 1));
-        stackTrace[i] = new StackTraceElement(ref.className(),
-            ref.memberName(), ste.getFileName(), ste.getLineNumber());
-      }
-    }
-    exception.setStackTrace(stackTrace);
-    resymbolize(exception.getCause());
+    return deobfuscator;
   }
 }
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
index 9d9dd8a..fac71a8 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java +++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
@@ -28,9 +28,6 @@
 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.SerializationException;
-import com.google.gwt.user.client.rpc.SerializationStreamFactory;
-import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 import com.google.gwt.user.client.rpc.ServiceDefTarget;

 import java.util.HashMap;
@@ -243,8 +240,6 @@
     if (failureMessage != null) {
       RuntimeException ex = new RuntimeException(failureMessage);
       result.setException(ex);
-    } else if (result.exceptionWrapper != null) {
-      ensureSerializable(result.exceptionWrapper);
     }
     TestInfo currentTest = getCurrentTest();
     currentResults.put(currentTest, result);
@@ -258,25 +253,6 @@
       });
     } else {
       syncToServer();
-    }
-  }
-
-  /**
-   * Convert unserializable exceptions into generic serializable ones.
-   */
-  private void ensureSerializable(ExceptionWrapper wrapper) {
-    if (wrapper == null) {
-      return;
-    }
-
-    ensureSerializable(wrapper.causeWrapper);
-    try {
- SerializationStreamFactory fac = (SerializationStreamFactory) junitHost;
-      SerializationStreamWriter dummyWriter = fac.createStreamWriter();
-      dummyWriter.writeObject(wrapper.exception);
-    } catch (SerializationException e) {
-      wrapper.exception = new Exception(wrapper.exception.toString() +
-          " (unserializable exception)");
     }
   }

diff --git a/user/test/com/google/gwt/junit/DefaultExceptionAsserter.java b/user/test/com/google/gwt/junit/DefaultExceptionAsserter.java
new file mode 100644
index 0000000..e4bc861
--- /dev/null
+++ b/user/test/com/google/gwt/junit/DefaultExceptionAsserter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 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.core.shared.SerializableThrowable;
+import com.google.gwt.junit.client.ExceptionAsserter;
+import com.google.gwt.junit.client.ExpectedFailure;
+
+import junit.framework.Assert;
+
+/**
+ * A default {@link ExceptionAsserter} that checks exception type and message.
+ */
+class DefaultExceptionAsserter extends Assert implements ExceptionAsserter {
+
+  @Override
+ public void assertException(Throwable throwable, ExpectedFailure annotation) {
+    assertAssignable(annotation.withType(), getExceptionClass(throwable));
+ assertTrue(getExceptionMessage(throwable).contains(annotation.withMessage()));
+  }
+
+ private static void assertAssignable(Class<?> expected, Class<?> exceptionClass) {
+    if (!expected.isAssignableFrom(exceptionClass)) {
+ fail("expected assignable to: " + expected + " found: " + exceptionClass);
+    }
+  }
+
+  private Class<?> getExceptionClass(Throwable t) {
+    if (t instanceof SerializableThrowable) {
+      try {
+ SerializableThrowable throwableWithClassName = (SerializableThrowable) t;
+        return Class.forName(throwableWithClassName.getDesignatedType());
+      } catch (Exception e) {
+        // Nothing to do here, just fallback to #getClass
+      }
+    }
+    return t.getClass();
+  }
+
+  private String getExceptionMessage(Throwable t) {
+    return t.getMessage() == null ? "" : t.getMessage();
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/junit/GwtTestSuiteWithExpectedFailures.java b/user/test/com/google/gwt/junit/GwtTestSuiteWithExpectedFailures.java
index 2b3ebf4..7c72b82 100644
--- a/user/test/com/google/gwt/junit/GwtTestSuiteWithExpectedFailures.java
+++ b/user/test/com/google/gwt/junit/GwtTestSuiteWithExpectedFailures.java
@@ -20,7 +20,8 @@
 import junit.framework.TestResult;

 /**
- * A {@link GWTTestSuite} that can interpret {@link ExpectedFailure} on test methods. + * A {@link GWTTestSuite} that can interpret {@link com.google.gwt.junit.client.ExpectedFailure} on
+ * test methods.
  */
 class GwtTestSuiteWithExpectedFailures extends GWTTestSuite {

diff --git a/user/test/com/google/gwt/junit/JUnitSuite.java b/user/test/com/google/gwt/junit/JUnitSuite.java
index fb684c0..9d2a7b4 100644
--- a/user/test/com/google/gwt/junit/JUnitSuite.java
+++ b/user/test/com/google/gwt/junit/JUnitSuite.java
@@ -18,6 +18,7 @@
 import com.google.gwt.junit.client.DevModeOnCompiledScriptTest;
 import com.google.gwt.junit.client.GWTTestCaseAsyncTest;
 import com.google.gwt.junit.client.GWTTestCaseSetupTearDownTest;
+import com.google.gwt.junit.client.GWTTestCaseStackTraceTest;
 import com.google.gwt.junit.client.GWTTestCaseTest;
 import com.google.gwt.junit.client.GWTTestCaseUncaughtExceptionHandlerTest;
 import com.google.gwt.junit.client.PropertyDefiningGWTTest;
@@ -33,6 +34,7 @@
TestSuite suite = new GwtTestSuiteWithExpectedFailures("Test suite for com.google.gwt.junit");

     suite.addTestSuite(GWTTestCaseTest.class);
+    suite.addTestSuite(GWTTestCaseStackTraceTest.class);
     suite.addTestSuite(GWTTestCaseUncaughtExceptionHandlerTest.class);
     suite.addTest(new TestSuiteWithOrder(GWTTestCaseAsyncTest.class));
suite.addTest(new TestSuiteWithOrder(GWTTestCaseSetupTearDownTest.class)); diff --git a/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java b/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
index c97846a..8660ec6 100644
--- a/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
+++ b/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.junit;

+import com.google.gwt.junit.client.ExceptionAsserter;
+import com.google.gwt.junit.client.ExpectedFailure;
+
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
 import junit.framework.TestCase;
@@ -42,19 +45,21 @@
   @Override
   public void addFailure(Test test, AssertionFailedError t) {
     failed = true;
-    if (isAnExpectedException(test, t)) {
-      return; // This is a good kind of failure, do not report it.
+    if (isTestExpectedToFail(test)) {
+      processException(test, t);
+    } else {
+      super.addFailure(test, t);
     }
-    super.addFailure(test, t);
   }

   @Override
   public void addError(Test test, Throwable t) {
     failed = true;
-    if (isAnExpectedException(test, t)) {
-      return; // This is a good kind of failure, do not report it.
+    if (isTestExpectedToFail(test)) {
+      processException(test, t);
+    } else {
+      super.addError(test, t);
     }
-    super.addError(test, t);
   }

   @Override
@@ -71,27 +76,24 @@
     return getExpectedFailureAnnotation(test) != null;
   }

-  private boolean isAnExpectedException(Test test, Throwable t) {
+  private void processException(Test test, Throwable t) {
     ExpectedFailure annotation = getExpectedFailureAnnotation(test);
-    if (annotation != null) {
-      t = normalizeGwtTestException(t);
-      return annotation.withType().isAssignableFrom(t.getClass())
-          && getExceptionMessage(t).contains(annotation.withMessage());
+    try {
+      getAsserter(annotation).newInstance().assertException(t, annotation);
+    } catch (AssertionFailedError e) {
+ String msg = e + "\n(Asserted exception is reported below via 'cause by')";
+      AssertionFailedError errorToReport = new AssertionFailedError(msg);
+      errorToReport.initCause(t);
+      errorToReport.setStackTrace(e.getStackTrace());
+      super.addFailure(test, errorToReport);
+    } catch (Exception e) {
+      super.addError(test, e);
     }
-    return false;
   }

-  /**
- * Extracts the real exception from the {@code RuntimeException} thrown by GwtTestCase.
-   */
-  private Throwable normalizeGwtTestException(Throwable t) {
- // GWTTestCase replaces AssertionFailedError with RuntimeException and for all other exceptions
-    // it puts them into 'cause' property.
- return t.getCause() == null ? new AssertionFailedError(t.getMessage()) : t.getCause();
-  }
-
-  private String getExceptionMessage(Throwable t) {
-    return t.getMessage() == null ? "" : t.getMessage();
+ private Class<? extends ExceptionAsserter> getAsserter(ExpectedFailure annotation) { + Class<? extends ExceptionAsserter> asserter = annotation.withAsserter(); + return asserter == ExceptionAsserter.class ? DefaultExceptionAsserter.class : asserter;
   }

   private ExpectedFailure getExpectedFailureAnnotation(Test test) {
diff --git a/user/test/com/google/gwt/junit/TestSuiteWithOrder.java b/user/test/com/google/gwt/junit/TestSuiteWithOrder.java
index 459b477..86c77fc 100644
--- a/user/test/com/google/gwt/junit/TestSuiteWithOrder.java
+++ b/user/test/com/google/gwt/junit/TestSuiteWithOrder.java
@@ -37,6 +37,7 @@
    */

   public TestSuiteWithOrder(Class<? extends TestCase> clazz) {
+    super(clazz.getName());
for (Class<?> c = clazz; Test.class.isAssignableFrom(c); c = c.getSuperclass()) {
       for (Method each : getDeclaredMethods(c)) {
         if (isTestMethod(each)) {
diff --git a/user/test/com/google/gwt/junit/client/ExceptionAsserter.java b/user/test/com/google/gwt/junit/client/ExceptionAsserter.java
new file mode 100644
index 0000000..f09f14d
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ExceptionAsserter.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 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.client;
+
+/**
+ * An abstraction to define assertion of exceptions to be used with {@link ExpectedFailure}.
+ */
+public interface ExceptionAsserter {
+  void assertException(Throwable throwable, ExpectedFailure annotation);
+}
diff --git a/user/test/com/google/gwt/junit/ExpectedFailure.java b/user/test/com/google/gwt/junit/client/ExpectedFailure.java
similarity index 79%
rename from user/test/com/google/gwt/junit/ExpectedFailure.java
rename to user/test/com/google/gwt/junit/client/ExpectedFailure.java
index dc76900..9fb4efc 100644
--- a/user/test/com/google/gwt/junit/ExpectedFailure.java
+++ b/user/test/com/google/gwt/junit/client/ExpectedFailure.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.junit;
+package com.google.gwt.junit.client;

 import junit.framework.AssertionFailedError;

@@ -31,4 +31,8 @@
   String withMessage() default "";

   Class<? extends Throwable> withType() default AssertionFailedError.class;
+
+ // Default is DefaultExceptionAsserter but its source is not GWT compatible due to use of
+  // reflection so we will not directly refer it from here.
+ Class<? extends ExceptionAsserter> withAsserter() default ExceptionAsserter.class;
 }
diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseAsyncTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseAsyncTest.java
index 3984236..9bdf3a5 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseAsyncTest.java
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseAsyncTest.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.junit.client;

-import com.google.gwt.junit.ExpectedFailure;
 import com.google.gwt.user.client.Timer;

 /**
diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseSetupTearDownTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseSetupTearDownTest.java
index d93cef2..07df732 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseSetupTearDownTest.java +++ b/user/test/com/google/gwt/junit/client/GWTTestCaseSetupTearDownTest.java
@@ -19,7 +19,6 @@
import static com.google.gwt.junit.client.GWTTestCaseSetupTearDownTest.SetUpTearDownState.TEARDOWN; import static com.google.gwt.junit.client.GWTTestCaseSetupTearDownTest.SetUpTearDownState.TESTCASE;

-import com.google.gwt.junit.ExpectedFailure;
 import com.google.gwt.user.client.Timer;

 import java.util.ArrayList;
@@ -31,16 +30,12 @@
  *
* Note: This test requires some test methods to be executed in a specific order.
  */
-public class GWTTestCaseSetupTearDownTest extends GWTTestCase {
-
-  public String getModuleName() {
-    return "com.google.gwt.junit.JUnit";
-  }
+public class GWTTestCaseSetupTearDownTest extends GWTTestCaseTestBase {

   /**
    * Tracks setup, teardown and testcase runs.
    */
-  protected enum SetUpTearDownState {
+  enum SetUpTearDownState {
     SETUP, TEARDOWN, TESTCASE
   }

diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseStackTraceTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseStackTraceTest.java
new file mode 100644
index 0000000..6a4d385
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseStackTraceTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2013 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.client;
+
+import com.google.gwt.core.shared.SerializableThrowable;
+import com.google.gwt.junit.client.WithProperties.Property;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * This class tests stack traces generated by GWTTestCase.
+ */
+public class GWTTestCaseStackTraceTest extends GWTTestCaseTestBase {
+
+  private static final int LINE_NUMBER_1 = 38;
+  private static final int LINE_NUMBER_2 = LINE_NUMBER_1 + 2;
+
+  private static final String FILE_NAME = "GWTTestCaseStackTraceTest.java";
+ private static final String CLASS_NAME = GWTTestCaseStackTraceTest.class.getName();
+
+  private static void throwException(boolean withCause) {
+ if (Math.abs(Math.random()) < 0) return; // Dummy code to prevent inlining
+
+ AssertionFailedError exception = new AssertionFailedError("stack_trace_msg");
+    if (withCause) {
+      exception.initCause(new RuntimeException("the_cause"));
+    }
+    throw exception;
+  }
+
+  private static void assertStackTrace(
+      Throwable t, String methodName, int lineNumber, boolean hasCause) {
+    assertSame(AssertionFailedError.class, t.getClass());
+    assertTrue(t.getMessage().startsWith("stack_trace_msg"));
+    StackTraceElement[] trace = t.getStackTrace();
+ assertStackTrace(trace, CLASS_NAME, "throwException", FILE_NAME, LINE_NUMBER_1);
+    assertStackTrace(trace, CLASS_NAME, methodName, FILE_NAME, lineNumber);
+    assertCause(t, hasCause);
+  }
+
+  private static void assertCause(Throwable t, boolean hasCause) {
+    Throwable cause = t.getCause();
+    if (hasCause) {
+      assertNotNull(cause);
+      assertCauseDetails(cause);
+    } else {
+      assertNull(cause);
+    }
+  }
+
+  private static void assertCauseDetails(Throwable t) {
+    assertSame(SerializableThrowable.class, t.getClass());
+    String type = ((SerializableThrowable) t).getDesignatedType();
+    assertEquals(RuntimeException.class.getName(), type);
+    assertTrue(t.getMessage().startsWith("the_cause"));
+    StackTraceElement[] trace = t.getStackTrace();
+ assertStackTrace(trace, CLASS_NAME, "throwException", FILE_NAME, LINE_NUMBER_2);
+  }
+
+ private static void assertStackTrace(StackTraceElement[] stackTrace, String className,
+      String methodName, String fileName, int lineNumber) {
+    for (StackTraceElement stackTraceElement : stackTrace) {
+      if (stackTraceElement.getClassName().equals(className)
+          && stackTraceElement.getMethodName().equals(methodName)) {
+        assertEquals(fileName, stackTraceElement.getFileName());
+        assertEquals(lineNumber, stackTraceElement.getLineNumber());
+        return; // Found!!!
+      }
+    }
+    fail("Stack trace element not found " + className + "#" + methodName);
+  }
+
+  /** Asserts stack trace generated by {@link #testStackTrace} */
+  public static class StackTraceAsserter implements ExceptionAsserter {
+    @Override
+ public void assertException(Throwable throwable, ExpectedFailure annotation) {
+      final int lineNumber = 98;
+      assertStackTrace(throwable, "testStackTrace", lineNumber, false);
+    }
+  }
+
+  @ExpectedFailure(withAsserter = StackTraceAsserter.class)
+  public void testStackTrace() {
+    throwException(false);
+  }
+
+  /** Asserts stack trace generated by {@link #testStackTrace_withCause} */
+ public static class StackTraceAsserterWithCause implements ExceptionAsserter {
+    @Override
+ public void assertException(Throwable throwable, ExpectedFailure annotation) {
+      final int lineNumber = 112;
+ assertStackTrace(throwable, "testStackTrace_withCause", lineNumber, true);
+    }
+  }
+
+  @ExpectedFailure(withAsserter = StackTraceAsserterWithCause.class)
+  public void testStackTrace_withCause() {
+    throwException(true);
+  }
+
+ /** Asserts stack trace generated by {@link #testStackTrace_fromDifferentModule} */ + public static class StackTraceAsserterFromDifferentModule implements ExceptionAsserter {
+    @Override
+ public void assertException(Throwable throwable, ExpectedFailure annotation) {
+      final int lineNumber = 128;
+ assertStackTrace(throwable, "testStackTrace_fromDifferentModule", lineNumber, false);
+    }
+  }
+
+  // @Propery added just to introduce a different module name for the test
+  @WithProperties(@Property(name = "locale", value = "tr"))
+ @ExpectedFailure(withAsserter = StackTraceAsserterFromDifferentModule.class)
+  public void testStackTrace_fromDifferentModule() {
+    throwException(false);
+  }
+}
diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
index 340951b..eef5b43 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
@@ -23,7 +23,6 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptException;
 import com.google.gwt.junit.DoNotRunWith;
-import com.google.gwt.junit.ExpectedFailure;
 import com.google.gwt.junit.Platform;

 import junit.framework.AssertionFailedError;
@@ -78,11 +77,32 @@
     throw new Exception();
   }

-  @ExpectedFailure(withType = Exception.class)
-  public void testThrowsNonSerializableException() {
+  @ExpectedFailure(withType = JavaScriptException.class)
+  public void testThrowsJavaScriptException() {
     throw new JavaScriptException("name", "desc");
   }

+  @ExpectedFailure(withType = NullPointerException.class)
+  public void testThrowsNullPointerException() {
+    throw new NullPointerException();
+  }
+
+  static class SomeNonSerializableException extends RuntimeException {
+    public SomeNonSerializableException(String msg) {
+      super(msg);
+    }
+    // no default constructor
+    // public SomeNonSerializableException() {}
+  }
+
+ // We loose some type information if class meta data is not available, setting expected failure
+  // to RuntimeException will ensure this test case passes for no metadata.
+  @ExpectedFailure(withType = RuntimeException.class,
+      withMessage = "testThrowsNonSerializableException")
+  public void testThrowsNonSerializableException() {
+ throw new SomeNonSerializableException("testThrowsNonSerializableException");
+  }
+
   public void testAssertEqualsDouble() {
     assertEquals(0.0, 0.0, 0.0);
     assertEquals(1.1, 1.1, 0.0);
diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java
index b9a50d8..72a0e05 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java +++ b/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java
@@ -18,7 +18,6 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
 import com.google.gwt.junit.DoNotRunWith;
-import com.google.gwt.junit.ExpectedFailure;
 import com.google.gwt.junit.Platform;

 import junit.framework.AssertionFailedError;

--
To view, visit https://gwt-review.googlesource.com/2290
To unsubscribe, visit https://gwt-review.googlesource.com/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1e1021bc99ac88ea6d9d47c3d23c83e79a896213
Gerrit-PatchSet: 1
Gerrit-Project: gwt
Gerrit-Branch: master
Gerrit-Owner: Goktug Gokdogan <[email protected]>

--
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors
--- You received this message because you are subscribed to the Google Groups "Google Web Toolkit Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to