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;