| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.junit.client; |
| |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; |
| import com.google.gwt.core.client.impl.Impl; |
| import com.google.gwt.junit.client.impl.GWTRunner; |
| import com.google.gwt.junit.client.impl.JUnitResult; |
| import com.google.gwt.user.client.Timer; |
| |
| import junit.framework.TestCase; |
| |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * The translatable implementation of {@link GWTTestCase}. |
| */ |
| @SuppressWarnings("unused") |
| public abstract class GWTTestCase extends TestCase { |
| |
| /** |
| * A watchdog class for use with asynchronous mode. On construction, |
| * immediately schedules itself for the specified timeout. If the timeout |
| * expires before this timer is canceled, causes the enclosing test case to |
| * fail with {@link TimeoutException}. |
| */ |
| private final class KillTimer extends Timer { |
| |
| /** |
| * Stashed so the timeout can be reported via {@link TimeoutException}. |
| */ |
| private final int timeoutMillis; |
| |
| public KillTimer(int timeoutMillis) { |
| this.timeoutMillis = timeoutMillis; |
| schedule(timeoutMillis); |
| } |
| |
| @Override |
| public void run() { |
| if (timer == this) { |
| // The test has failed due to timeout |
| reportResultsAndRunNextMethod(new TimeoutException(timeoutMillis)); |
| } else { |
| // Something happened so that we are no longer the active timer. |
| // Just do nothing. |
| } |
| } |
| } |
| |
| /** |
| * UncaughtExceptionHandler used to catch exceptions reported via |
| * {@link GWT#reportUncaughtException}. |
| */ |
| private final class TestCaseUncaughtExceptionHandler implements UncaughtExceptionHandler { |
| @Override |
| public void onUncaughtException(Throwable e) { |
| reportUncaughtException(e); |
| } |
| } |
| |
| private final Logger logger = Logger.getLogger("GWTTestCase"); |
| |
| /** |
| * Tracks whether the main test body has run (for asynchronous mode). |
| */ |
| private boolean mainTestHasRun = false; |
| |
| /** |
| * Tracks whether this test is completely done. |
| */ |
| private boolean testIsFinished = false; |
| |
| /** |
| * If non-null, a timer to kill the current test case (for asynchronous mode). |
| */ |
| private KillTimer timer; |
| |
| // Holds the first exception that's thrown "synchronously", meaning "before |
| // the test method returns". |
| private Throwable synchronousException = null; |
| |
| /** |
| * Name of the test class. |
| */ |
| private String testClass; |
| |
| public void init(String testClass, String testName) { |
| this.testClass = testClass; |
| setName(testName); |
| } |
| |
| // CHECKSTYLE_OFF |
| /** |
| * Actually run the user's test. Called from {@link GWTRunner}. |
| */ |
| public void __doRunTest() { |
| if (shouldCatchExceptions()) { |
| try { |
| runBare(); |
| } catch (Throwable e) { |
| // If an exception was explicitly reported, it must have happened |
| // before the exception was thrown from the test method. |
| if (synchronousException == null) { |
| synchronousException = e; |
| } |
| } |
| } else { |
| runBareTestCaseAvoidingExceptionDecl(); |
| } |
| |
| // Mark that the main test body has now run. From this point, if |
| // timer != null we are in true asynchronous mode. |
| mainTestHasRun = true; |
| |
| if (synchronousException != null || timer == null) { |
| reportResultsAndRunNextMethod(synchronousException); |
| } // else Test is still running; wait for asynchronous completion. |
| } |
| // CHECKSTYLE_ON |
| |
| public boolean catchExceptions() { |
| return true; |
| } |
| |
| public abstract String getModuleName(); |
| |
| public boolean isPureJava() { |
| return false; |
| } |
| |
| @Override |
| public void runBare() throws Throwable { |
| logger.log(Level.FINE, this + " -> Running"); |
| setUp(); |
| runTest(); |
| // No tearDown call here; we do it from reportResults. |
| } |
| |
| @Override |
| protected void doRunTest(String name) throws Throwable { |
| GWTRunner.get().executeTestMethod(this, testClass, name); |
| } |
| |
| public void setForcePureJava(boolean forcePureJava) { |
| // Ignore completely. The test is being run in GWT mode, |
| // hence assumed not to be pure Java. |
| } |
| |
| protected final void delayTestFinish(int timeoutMillis) { |
| logger.log(Level.FINE, this + " -> Delay test finish: " + timeoutMillis); |
| if (timer != null) { |
| // Cancel the pending timer |
| timer.cancel(); |
| } |
| |
| // Set a new timer for the specified new timeout |
| timer = new KillTimer(timeoutMillis); |
| } |
| |
| protected final void finishTest() { |
| if (testIsFinished) { |
| // This test is totally done already, just ignore the call. |
| return; |
| } |
| |
| if (timer == null) { |
| throw new IllegalStateException( |
| "This test is not in asynchronous mode; call delayTestFinish() first"); |
| } |
| |
| if (mainTestHasRun) { |
| // This is a correct, successful async finish. |
| reportResultsAndRunNextMethod(null); |
| } else { |
| // The user tried to finish the test before the main body returned! |
| // Just let the test continue running normally. |
| resetAsyncState(); |
| } |
| } |
| |
| protected void gwtSetUp() throws Exception { |
| } |
| |
| protected void gwtTearDown() throws Exception { |
| } |
| |
| @Override |
| protected final void setUp() throws Exception { |
| // Make sure all exceptions escape to the browser if shouldCatchExceptions returns false |
| setAllUncaughtExceptionHandlers( |
| shouldCatchExceptions() ? new TestCaseUncaughtExceptionHandler() : null); |
| gwtSetUp(); |
| } |
| |
| @Override |
| protected final void tearDown() throws Exception { |
| try { |
| gwtTearDown(); |
| } finally { |
| testIsFinished = true; |
| setAllUncaughtExceptionHandlers(null); |
| resetAsyncState(); |
| } |
| } |
| |
| protected void reportUncaughtException(Throwable ex) { |
| assertTestState(); |
| |
| if (mainTestHasRun && timer != null) { |
| // Asynchronous mode; uncaught exceptions cause an immediate failure. |
| reportResultsAndRunNextMethod(ex); |
| } else { |
| // Synchronous mode: hang on to it for after the test method returns. |
| // We can't call reportResultsAndRunNextMethod() yet, as it will cause |
| // a race condition that often causes the same test to be run again. |
| if (synchronousException == null) { |
| synchronousException = ex; |
| } |
| } |
| } |
| |
| private void assertTestState() { |
| // TODO(goktug): Add assertTestState to other calls (e.g. delayTestFinish) |
| // TODO(goktug): Report any problems via GWTRunner. |
| assert (!testIsFinished); |
| } |
| |
| /** |
| * Cleans up any outstanding state, reports ex to the remote runner, and kicks off the next test. |
| * |
| * @param ex The results of this test. |
| */ |
| private void reportResultsAndRunNextMethod(Throwable ex) { |
| try { |
| tearDown(); |
| } catch (Throwable e) { |
| // ignore any exceptions thrown from tearDown |
| } |
| |
| JUnitResult result = new JUnitResult(); |
| if (ex != null) { |
| result.setException(ex); |
| } |
| |
| String resultMsg = result.isAnyException() ? "FAILURE" : "SUCCESS"; |
| logger.log(Level.FINE, this + " -> Result: " + resultMsg, result.getException()); |
| |
| GWTRunner.get().reportResultsAndGetNextMethod(result); |
| } |
| |
| /** |
| * Cleans up any asynchronous mode state. |
| */ |
| private void resetAsyncState() { |
| // clear our timer if there is one |
| if (timer != null) { |
| timer.cancel(); |
| timer = null; |
| } |
| } |
| |
| /** |
| * In the mode where we need to let uncaught exceptions escape to the browser, |
| * this method serves as a hack to avoid "throws" clause problems. |
| */ |
| private native void runBareTestCaseAvoidingExceptionDecl() /*-{ |
| this.@junit.framework.TestCase::runBare()(); |
| }-*/; |
| |
| /** |
| * A helper method to determine if we should catch exceptions. Wraps the call |
| * into user code with a try/catch; if the user's code throws an exception, we |
| * just ignore the exception and use the default behavior. |
| * |
| * @return <code>true</code> if exceptions should be handled normally, |
| * <code>false</code> if they should be allowed to escape. |
| */ |
| private boolean shouldCatchExceptions() { |
| try { |
| return catchExceptions(); |
| } catch (Throwable e) { |
| return true; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getName() + "(" + testClass + ")"; |
| } |
| |
| private static void setAllUncaughtExceptionHandlers(UncaughtExceptionHandler handler) { |
| Impl.setUncaughtExceptionHandlerForTest(handler); |
| // TODO(goktug) There is still code out there using GWT#getUncaughtExceptionHandler to report |
| // exceptions, so we need to keep setting the production exception handler for compatibility: |
| GWT.setUncaughtExceptionHandler(handler); |
| } |
| } |