Refactors c.g.gwt.junit to use common SerializableThrowable & StacktraceDeobfuscator.
- Gets rid of duplicate 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
Review-Link: https://gwt-review.googlesource.com/#/c/2290/
Review by: skybrian@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11581 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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..044d697 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -16,10 +16,10 @@
package com.google.gwt.junit;
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 +27,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
@@ -95,15 +95,6 @@
}
}
- private static final Set<Class<? extends Throwable>> THROWABLES_NOT_RETRIED = createThrowablesNotRetried();
-
- private static Set<Class<? extends Throwable>> createThrowablesNotRetried() {
- Set<Class<? extends Throwable>> throwableSet = new HashSet<Class<? extends Throwable>>();
- throwableSet.add(com.google.gwt.junit.JUnitFatalLaunchException.class);
- throwableSet.add(java.lang.Error.class);
- return throwableSet;
- }
-
/**
* Records results for each client; must lock before accessing.
*/
@@ -463,14 +454,20 @@
if (result == null) {
return true;
}
- Throwable exception = result.getException();
- if (exception != null && !isMember(exception, THROWABLES_NOT_RETRIED)) {
+
+ if (isNonFatalFailure(result)) {
return true;
}
}
return false;
}
+ private boolean isNonFatalFailure(JUnitResult result) {
+ return result.isAnyException()
+ && !result.isExceptionOf(Error.class)
+ && !result.isExceptionOf(JUnitFatalLaunchException.class);
+ }
+
void removeResults(TestInfo testInfo) {
synchronized (clientStatusesLock) {
testResults.remove(testInfo);
@@ -520,14 +517,4 @@
}
return results;
}
-
- private boolean isMember(Throwable exception,
- Set<Class<? extends Throwable>> throwableSet) {
- for (Class<? extends Throwable> throwable : throwableSet) {
- if (throwable.isInstance(exception)) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 3ae26a9..2555991 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,41 +1225,28 @@
+ " != " + 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;
+ if (result.isAnyException()) {
+ if (result.isExceptionOf(AssertionFailedError.class)) {
+ testResult.addFailure(testCase, toAssertionFailedError(result.getException()));
} else {
- exception = new RuntimeException(msg, exception);
+ testResult.addError(testCase, result.getException());
}
}
-
- // 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 void runTestImpl(GWTTestCase testCase, TestResult testResult)
throws UnableToCompleteException {
runTestImpl(testCase, testResult, 0);
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..9d6476e 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,12 @@
*/
package com.google.gwt.junit.client.impl;
+import com.google.gwt.core.shared.GwtIncompatible;
+import com.google.gwt.core.shared.SerializableThrowable;
+import com.google.gwt.junit.client.TimeoutException;
+
+import junit.framework.AssertionFailedError;
+
import java.io.Serializable;
/**
@@ -33,7 +39,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 +51,22 @@
return agent;
}
- public Throwable getException() {
- return (exceptionWrapper == null) ? null : exceptionWrapper.getException();
+ public SerializableThrowable getException() {
+ return thrown;
+ }
+
+ public boolean isAnyException() {
+ return thrown != null;
+ }
+
+ @GwtIncompatible
+ public boolean isExceptionOf(Class<?> expectedException) {
+ try {
+ return thrown == null ? false
+ : expectedException.isAssignableFrom(Class.forName(thrown.getDesignatedType()));
+ } catch (Exception e) {
+ return false;
+ }
}
public String getHost() {
@@ -58,7 +78,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 +91,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/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 4708d35..e7e0f9b 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -15,8 +15,7 @@
*/
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.junit.JUnitFatalLaunchException;
import com.google.gwt.junit.JUnitMessageQueue;
import com.google.gwt.junit.JUnitMessageQueue.ClientInfoExt;
@@ -24,16 +23,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;
@@ -47,18 +43,6 @@
*/
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 +75,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 +97,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 +155,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());
+ Throwable 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(Throwable 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("Unable to deobfuscate a stack trace due to an error:");
+ 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);
@@ -262,25 +257,6 @@
}
/**
- * 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)");
- }
- }
-
- /**
* Executes a test on provided test class instance.
*/
public void executeTestMethod(GWTTestCase testCase, String className, String methodName)
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..4e84625 100644
--- a/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
+++ b/user/test/com/google/gwt/junit/TestResultWithExpectedFailures.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.junit;
+import com.google.gwt.junit.client.ExpectedFailure;
+
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
@@ -42,19 +44,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 +75,20 @@
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 {
+ annotation.withAsserter().newInstance().assertException(annotation, t);
+ } catch (AssertionFailedError e) {
+ String msg = "Assertion failed for thrown exception: " + e.getMessage()
+ + "\n(Actual thrown exception is reported below via 'caused 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 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/DefaultExceptionAsserter.java b/user/test/com/google/gwt/junit/client/DefaultExceptionAsserter.java
new file mode 100644
index 0000000..38b176a
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/DefaultExceptionAsserter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.GwtIncompatible;
+import com.google.gwt.core.shared.SerializableThrowable;
+
+import junit.framework.Assert;
+
+/**
+ * A default {@link ExceptionAsserter} that checks exception type and message.
+ */
+public class DefaultExceptionAsserter extends Assert implements ExceptionAsserter {
+
+ @GwtIncompatible
+ @Override
+ public void assertException(ExpectedFailure annotation, Throwable actual) {
+ assertAssignable(annotation.withType(), getExceptionClass(actual));
+ assertMessageContains(annotation.withMessage(), getExceptionMessage(actual));
+ }
+
+ private static void assertMessageContains(String expected, String actual) {
+ if (!actual.contains(expected)) {
+ fail("expected message: " + expected + " in: " + actual);
+ }
+ }
+
+ @GwtIncompatible
+ private static void assertAssignable(Class<?> expected, Class<?> exceptionClass) {
+ if (!expected.isAssignableFrom(exceptionClass)) {
+ fail("expected subclass of: " + expected + " found: " + exceptionClass);
+ }
+ }
+
+ @GwtIncompatible
+ 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/client/ExceptionAsserter.java b/user/test/com/google/gwt/junit/client/ExceptionAsserter.java
new file mode 100644
index 0000000..1ee2587
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ExceptionAsserter.java
@@ -0,0 +1,27 @@
+/*
+ * 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.GwtIncompatible;
+
+/**
+ * An abstraction to define assertion of exceptions to be used with {@link ExpectedFailure}.
+ * Note: Exception asserters are only executed in JRE so they don't need to be GWT compatible.
+ */
+public interface ExceptionAsserter {
+ @GwtIncompatible
+ void assertException(ExpectedFailure annotation, Throwable actual);
+}
diff --git a/user/test/com/google/gwt/junit/ExpectedFailure.java b/user/test/com/google/gwt/junit/client/ExpectedFailure.java
similarity index 89%
rename from user/test/com/google/gwt/junit/ExpectedFailure.java
rename to user/test/com/google/gwt/junit/client/ExpectedFailure.java
index dc76900..d4c2b8d 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,6 @@
String withMessage() default "";
Class<? extends Throwable> withType() default AssertionFailedError.class;
+
+ Class<? extends ExceptionAsserter> withAsserter() default DefaultExceptionAsserter.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..59822c3
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseStackTraceTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 = 39;
+ 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
+
+ // the next line should be LINE_NUMBER_1
+ 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 {
+ public void assertException(ExpectedFailure annotation, Throwable actual) {
+ final int lineNumber = 98;
+ assertStackTrace(actual, "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 {
+ public void assertException(ExpectedFailure annotation, Throwable actual) {
+ final int lineNumber = 111;
+ assertStackTrace(actual, "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 {
+ public void assertException(ExpectedFailure annotation, Throwable actual) {
+ final int lineNumber = 126;
+ assertStackTrace(actual, "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..54eed8f 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 lose 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 0c7e575..e018477 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseUncaughtExceptionHandlerTest.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
-import com.google.gwt.junit.ExpectedFailure;
import junit.framework.AssertionFailedError;