001    /*
002     * Copyright 2006 Google Inc.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package com.google.gwt.junit.client;
017    
018    import com.google.gwt.core.ext.Generator;
019    import com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator;
020    
021    import junit.framework.TestCase;
022    import junit.framework.TestResult;
023    
024    import java.lang.reflect.InvocationTargetException;
025    import java.lang.reflect.Method;
026    import java.util.ArrayList;
027    
028    /**
029     * Acts as a bridge between the JUnit environment and the GWT environment. We
030     * hook the run method and stash the TestResult object for later communication
031     * between the test runner and the unit test shell that drives the test case
032     * inside a hosted browser.
033     * 
034     * <p>
035     * There are two versions of this class. One is used in hosted mode and the
036     * other is used in hybrid mode. Implementations are very different between the
037     * two modes, making it simpler to have a separate implementation for each.
038     * Please see the <code>translatable</code> subpackage for the other
039     * implementation.
040     * </p>
041     */
042    public abstract class GWTTestCase extends TestCase {
043    
044      private static Method runTestMethod;
045      private static Object unitTestShell;
046    
047      /**
048       * Accumulates messages that are printed along with failures (useful in hybrid
049       * mode as a substitute for stack traces).
050       */
051      public void addCheckpoint(String msg) {
052        checkPointMessages.add(msg);
053      }
054    
055      /**
056       * Determines whether or not exceptions will be caught by the test fixture.
057       */
058      public boolean catchExceptions() {
059        return true;
060      }
061    
062      /**
063       * Resets the current checkpoint messages.
064       */
065      public void clearCheckpoints() {
066        checkPointMessages.clear();
067      }
068    
069      /**
070       * Gets the current checkpoint messages.
071       */
072      public String[] getCheckpoints() {
073        return (String[]) checkPointMessages.toArray(new String[checkPointMessages
074          .size()]);
075      }
076    
077      /**
078       * Tests can only be run in the context of a module. Therefore, the concrete
079       * TestCase must provide an implementation for this method.
080       * 
081       * @return the fully qualified name of the module to use for this test.
082       */
083      public abstract String getModuleName();
084    
085      /**
086       * Stashes the <code>TestResult</code> object so that it can be accessed
087       * from the unit test shell.
088       */
089      public void run(TestResult result) {
090        testResult = result;
091        super.run(result);
092      }
093    
094      /**
095       * Runs the test by delegating to the unit test shell.
096       */
097      protected void runTest() throws Throwable {
098        Throwable caught;
099        try {
100          if (runTestMethod == null) {
101            // Use reflection to avoid build-time dependencies and to provide an
102            // opportunity for a useful failure message.
103            //
104            Class unitTestShellClass;
105            try {
106              unitTestShellClass = Class
107                .forName("com.google.gwt.dev.GWTUnitTestShell");
108              Method getUnitTestShellMethod = unitTestShellClass.getDeclaredMethod(
109                "getUnitTestShell", new Class[]{Generator.class});
110              runTestMethod = unitTestShellClass.getDeclaredMethod("runTest",
111                new Class[]{String.class, TestCase.class, TestResult.class});
112              Generator generator = new JUnitTestCaseStubGenerator(getClass()
113                .getName());
114              unitTestShell = getUnitTestShellMethod.invoke(null,
115                new Object[]{generator});
116    
117              if (unitTestShell == null) {
118                throw new RuntimeException(
119                  "Unable to start required development shell support; see the console for details");
120              }
121            } catch (ClassNotFoundException e) {
122              throw new RuntimeException(
123                "Unable to find JUnit integration support; are you running with the proper dev jar?");
124            }
125          }
126    
127          runTestMethod.invoke(unitTestShell, new Object[]{
128            getModuleName(), this, testResult});
129    
130          clearCheckpoints();
131    
132          return;
133    
134        } catch (SecurityException e) {
135          caught = e;
136        } catch (NoSuchMethodException e) {
137          caught = e;
138        } catch (IllegalArgumentException e) {
139          caught = e;
140        } catch (IllegalAccessException e) {
141          caught = e;
142        } catch (InvocationTargetException e) {
143          caught = e;
144          Throwable nested = caught.getCause();
145          if (nested != null) {
146            caught = nested;
147          }
148        }
149        throw new RuntimeException(
150          "Unable to run unit test due to an unexpected exception", caught);
151      }
152    
153      /**
154       * Not supported in the current implemenation.
155       */
156      protected final void setUp() throws Exception {
157      }
158    
159      /**
160       * Not supported in the current implementation.
161       */
162      protected void tearDown() throws Exception {
163      }
164    
165      /*
166       * A list of messages that serve as debugging aids to track down hybrid mode
167       * failures.
168       */
169      private ArrayList checkPointMessages = new ArrayList();
170    
171      /*
172       * Object that collects the results of this test case execution.
173       */
174      private TestResult testResult = null;
175    }