| /* |
| * 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.junit.JUnitShell; |
| import com.google.gwt.junit.JUnitShell.Strategy; |
| import com.google.gwt.junit.PropertyDefiningStrategy; |
| import com.google.gwt.junit.client.impl.JUnitHost.TestInfo; |
| |
| import junit.framework.TestCase; |
| import junit.framework.TestResult; |
| |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Acts as a bridge between the JUnit environment and the GWT environment. We |
| * hook the run method and stash the TestResult object for later communication |
| * between the test runner and the unit test shell that drives the test case |
| * inside a hosted browser. |
| * |
| * <p> |
| * There are two versions of this class. This version is the binary version that |
| * derives from JUnit's {@link TestCase} and handles all the work of starting up |
| * the GWT environment. The other version is a translatable class that is used |
| * within the browser. See the <code>translatable</code> subpackage for the |
| * translatable implementation. |
| * </p> |
| */ |
| @SuppressWarnings("unused") |
| public abstract class GWTTestCase extends TestCase { |
| |
| /** |
| * Information about a synthetic module used for testing. |
| */ |
| public static final class TestModuleInfo { |
| private String moduleName; |
| private String syntheticModuleName; |
| private Strategy strategy; |
| |
| /** |
| * The ordered tests in this synthetic module. |
| */ |
| private Set<TestInfo> tests = new LinkedHashSet<TestInfo>(); |
| |
| /** |
| * Construct a new {@link TestModuleInfo}. |
| * |
| * @param moduleName the module name |
| * @param syntheticModuleName the synthetic module name |
| * @param strategy the test {@link Strategy} |
| */ |
| public TestModuleInfo(String moduleName, String syntheticModuleName, |
| Strategy strategy) { |
| this.moduleName = moduleName; |
| this.syntheticModuleName = syntheticModuleName; |
| this.strategy = strategy; |
| } |
| |
| public String getModuleName() { |
| return moduleName; |
| } |
| |
| public Strategy getStrategy() { |
| return strategy; |
| } |
| |
| public String getSyntheticModuleName() { |
| return syntheticModuleName; |
| } |
| |
| /** |
| * Returns the tests that are part of this module. |
| */ |
| public Set<TestInfo> getTests() { |
| return tests; |
| } |
| } |
| |
| /** |
| * Records all live GWTTestCases by synthetic module name so we can optimize |
| * run they are compiled and run. Ordered so that we can precompile the |
| * modules in the order that they will run. |
| */ |
| public static final Map<String, TestModuleInfo> ALL_GWT_TESTS = new LinkedHashMap<String, TestModuleInfo>(); |
| |
| /** |
| * The lock for ALL_GWT_TESTS. |
| */ |
| private static final Object ALL_GWT_TESTS_LOCK = new Object(); |
| |
| /** |
| * Get the names of all test modules. |
| * |
| * @return all test module names |
| */ |
| public static String[] getAllTestModuleNames() { |
| synchronized (ALL_GWT_TESTS_LOCK) { |
| return ALL_GWT_TESTS.keySet().toArray(new String[ALL_GWT_TESTS.size()]); |
| } |
| } |
| |
| /** |
| * Get the number of modules. |
| * |
| * @return the module count. |
| */ |
| public static int getModuleCount() { |
| synchronized (ALL_GWT_TESTS_LOCK) { |
| return ALL_GWT_TESTS.size(); |
| } |
| } |
| |
| /** |
| * Get the set of all {@link TestInfo} for the specified module. |
| * |
| * @param syntheticModuleName the synthetic module name |
| * @return all tests for the module |
| */ |
| public static TestModuleInfo getTestsForModule(String syntheticModuleName) { |
| synchronized (ALL_GWT_TESTS_LOCK) { |
| return ALL_GWT_TESTS.get(syntheticModuleName); |
| } |
| } |
| |
| /** |
| * Object that collects the results of this test case execution. |
| */ |
| protected TestResult testResult = null; |
| |
| /** |
| * Whether this test case should be always run in pure Java mode (non-GWT). |
| * Setting this to <code>true</code> has the same effect as returning |
| * <code>null</code> in {@link #getModuleName}. |
| * |
| * @see #isPureJava |
| */ |
| private boolean forcePureJava; |
| |
| /** |
| * The {@link Strategy} used by this test. |
| */ |
| private Strategy strategy; |
| |
| /** |
| * A new instance of your subclass is constructed for each test method that is |
| * to be run. You should avoid running code in your subclass constructor, |
| * initializer blocks, and field initializations, because if those code blocks |
| * must be runnable outside of the GWT environment. As an example of what |
| * could go wrong if you run code there, trying to run a JSNI method could |
| * generate an {@link UnsatisfiedLinkError}, and trying to call |
| * {@link com.google.gwt.core.client.GWT#create(Class)} could throw an |
| * {@link UnsupportedOperationException}. Instead, override |
| * {@link #gwtSetUp()} and perform any initialization code there. |
| */ |
| public GWTTestCase() { |
| } |
| |
| /** |
| * Determines whether or not exceptions will be caught by the test fixture. |
| * Override this method and return <code>false</code> to let exceptions escape |
| * to the browser. This will break the normal JUnit reporting functionality, |
| * but can be useful in Production Mode with a JavaScript debugger to pin down |
| * where exceptions are originating. |
| * |
| * @return <code>true</code> for normal JUnit behavior, or <code>false</code> |
| * to disable normal JUnit exception reporting |
| */ |
| public boolean catchExceptions() { |
| return true; |
| } |
| |
| /** |
| * Specifies a module to use when running this test case. Subclasses must |
| * return the name of a module that will cause the source for that subclass to |
| * be included. |
| * |
| * @return the fully qualified name of a module, or <code>null</code> to run |
| * as a pure Java (non-GWT) test case (same effect as passing |
| * <code>true</code> to {@link #setForcePureJava}) |
| * |
| * @see #isPureJava |
| */ |
| public abstract String getModuleName(); |
| |
| /** |
| * Get the {@link Strategy} to use when compiling and running this test. |
| * |
| * @return the test {@link Strategy} |
| */ |
| public Strategy getStrategy() { |
| if (strategy == null) { |
| strategy = new PropertyDefiningStrategy(this); |
| } |
| return strategy; |
| } |
| |
| /** |
| * Get the synthetic module name, which includes the synthetic extension |
| * defined by the {@link Strategy}. |
| * |
| * @return the synthetic module name, or <code>null</code> if this test case |
| * is run in pure Java mode (non-GWT) |
| * |
| * @see #isPureJava |
| */ |
| public final String getSyntheticModuleName() { |
| if (isPureJava()) { |
| return null; |
| } else { |
| return getModuleName() + "." + getStrategy().getSyntheticModuleExtension(); |
| } |
| } |
| |
| /** |
| * Returns whether this test case should be run in pure Java mode (non-GWT). |
| * Returns <code>true</code> if and only if {@link #getModuleName} returns |
| * <code>null</code>, or {@link #setForcePureJava} was last invoked with |
| * <code>true</code>. |
| */ |
| public boolean isPureJava() { |
| return forcePureJava || (getModuleName() == null); |
| } |
| |
| /** |
| * Stashes <code>result</code> so that it can be accessed during |
| * {@link #runTest()}. |
| */ |
| @Override |
| public final void run(TestResult result) { |
| testResult = result; |
| super.run(result); |
| } |
| |
| /** |
| * Specifies whether this test case should be always run in pure Java mode |
| * (non-GWT). Passing <code>true</code> has the same effect as returning |
| * <code>null</code> in {@link #getModuleName}. The setting is |
| * <code>false</code> by default. |
| * |
| * @param forcePureJava <code>true</code> to always run this test case in pure |
| * Java mode (non-GWT); <code>false</code> to run this test case in GWT |
| * mode if {@link #getModuleName} does not return <code>null</code> |
| * |
| * @see #isPureJava |
| */ |
| public void setForcePureJava(boolean forcePureJava) { |
| this.forcePureJava = forcePureJava; |
| } |
| |
| @Override |
| public void setName(String name) { |
| super.setName(name); |
| |
| synchronized (ALL_GWT_TESTS_LOCK) { |
| // Once the name is set, we can add ourselves to the global set. |
| String syntheticModuleName = getSyntheticModuleName(); |
| TestModuleInfo moduleInfo = ALL_GWT_TESTS.get(syntheticModuleName); |
| if (moduleInfo == null) { |
| // It should be safe to assume that tests with the same synthetic module |
| // name have the same test strategy. If they didn't, they would compile |
| // over each other. |
| moduleInfo = new TestModuleInfo(getModuleName(), syntheticModuleName, |
| getStrategy()); |
| ALL_GWT_TESTS.put(syntheticModuleName, moduleInfo); |
| } |
| moduleInfo.getTests().add( |
| new TestInfo(syntheticModuleName, getClass().getName(), getName())); |
| } |
| } |
| |
| /** |
| * Put the current test in asynchronous mode. If the test method completes |
| * normally, this test will not immediately succeed. Instead, a <i>delay |
| * period</i> begins. During the delay period, the test system will wait for |
| * one of three things to happen: |
| * |
| * <ol> |
| * <li> If {@link #finishTest()} is called before the delay period expires, |
| * the test will succeed.</li> |
| * <li> If any exception escapes from an event handler during the delay |
| * period, the test will error with the thrown exception.</li> |
| * <li> If the delay period expires and neither of the above has happened, the |
| * test will error with a {@link TimeoutException}. </li> |
| * </ol> |
| * |
| * <p> |
| * This method is typically used to test event driven functionality. |
| * </p> |
| * |
| * <p> |
| * <b>Example:</b> |
| * {@example com.google.gwt.examples.AsyncJUnitExample#testTimer()} |
| * </p> |
| * |
| * @param timeoutMillis how long to wait before the current test will time out |
| * @tip Subsequent calls to this method reset the timeout. |
| * @see #finishTest() |
| * |
| * @throws UnsupportedOperationException if {@link #supportsAsync()} is false |
| */ |
| protected final void delayTestFinish(int timeoutMillis) { |
| // implemented in the translatable version of this class |
| } |
| |
| /** |
| * Cause this test to succeed during asynchronous mode. After calling |
| * {@link #delayTestFinish(int)}, call this method during the delay period to |
| * cause this test to succeed. This method is typically called from an event |
| * handler some time after the test method returns control to the caller. |
| * |
| * <p> |
| * Calling this method before the test method completes, will undo the effect |
| * of having called <code>delayTestFinish()</code>. The test will revert to |
| * normal, non-asynchronous mode. |
| * </p> |
| * |
| * <p> |
| * <b>Example:</b> |
| * {@example com.google.gwt.examples.AsyncJUnitExample#testTimer()} |
| * </p> |
| * |
| * @throws IllegalStateException if this test is not in asynchronous mode |
| * @throws UnsupportedOperationException if {@link #supportsAsync()} is false |
| * |
| * @see #delayTestFinish(int) |
| */ |
| protected final void finishTest() { |
| // implemented in the translatable version of this class |
| } |
| |
| /** |
| * Reports an exception that might have escaped to the browser. |
| * <p> |
| * This method is called by the test framework to report uncaught exceptions. The default |
| * implementation causes the test case to be reported as 'failed'. However in some rare situations |
| * where an uncaught exception is expected, a test case may choose to alter the behavior by |
| * overriding it: |
| * <pre> |
| * class SomeTestCase extends GWTTestCase { |
| * |
| * class ExpectedTestException extends Exception {} |
| * |
| * {@literal @}Override |
| * protected void reportUncaughtException(Throwable t) { |
| * // Ignore exceptions that are expected to be thrown by our test cases: |
| * if (t instanceof ExpectedTestException) { |
| * return; |
| * } |
| * super.reportUncaughtException(t); |
| * } |
| * ... |
| * } |
| * </pre> |
| * |
| * Note that this method will not cause the test case to fail immediately if the main test body is |
| * still executing. In that case, test will fail after the main body returns with the reported |
| * exception. If the test has already finished (fail or success) before this method is called, |
| * the reported exception is currently ignored, but this may result in an error in a future |
| * version of GWT. |
| * |
| * @see com.google.gwt.core.client.GWT#reportUncaughtException |
| */ |
| protected void reportUncaughtException(Throwable ex) { |
| // implemented in the translatable version of this class |
| } |
| |
| /** |
| * A replacement for JUnit's {@link #setUp()} method. This method runs once |
| * per test method in your subclass, just before your each test method runs |
| * and can be used to perform initialization. Override this method instead of |
| * {@link #setUp()}. This method is run even in pure Java mode (non-GWT). |
| * |
| * @see #setForcePureJava |
| */ |
| protected void gwtSetUp() throws Exception { |
| } |
| |
| /** |
| * A replacement for JUnit's {@link #tearDown()} method. This method runs once |
| * per test method in your subclass, just after your each test method runs and |
| * can be used to perform cleanup. Override this method instead of |
| * {@link #tearDown()}. This method is run even in pure Java mode (non-GWT). |
| * |
| * @see #setForcePureJava |
| */ |
| protected void gwtTearDown() throws Exception { |
| } |
| |
| /** |
| * Runs the test via the {@link JUnitShell} environment. Do not override or |
| * call this method. |
| */ |
| @Override |
| protected void runTest() throws Throwable { |
| if (this.getName() == null) { |
| throw new IllegalArgumentException( |
| "GWTTestCases require a name; \"" |
| + this.toString() |
| + "\" has none. Perhaps you used TestSuite.addTest() instead of addTestClass()?"); |
| } |
| |
| if (isPureJava()) { |
| super.runTest(); |
| } else { |
| JUnitShell.runTest(this, testResult); |
| } |
| } |
| |
| /** |
| * This method has been made final to prevent you from accidentally running |
| * client code outside of the GWT environment. Please override |
| * {@link #gwtSetUp()} instead. |
| */ |
| @Override |
| protected final void setUp() throws Exception { |
| if (isPureJava()) { |
| gwtSetUp(); |
| } |
| } |
| |
| /** |
| * This method has been made final to prevent you from accidentally running |
| * client code outside of the GWT environment. Please override |
| * {@link #gwtTearDown()} instead. |
| */ |
| @Override |
| protected final void tearDown() throws Exception { |
| if (isPureJava()) { |
| gwtTearDown(); |
| } |
| } |
| } |