KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > junit > client > impl > GWTTestCaseImpl


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.junit.client.impl;
17
18 import com.google.gwt.core.client.GWT;
19 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
20 import com.google.gwt.junit.client.GWTTestCase;
21 import com.google.gwt.junit.client.TestResults;
22 import com.google.gwt.junit.client.TimeoutException;
23 import com.google.gwt.junit.client.Trial;
24 import com.google.gwt.user.client.Timer;
25 import com.google.gwt.user.client.rpc.AsyncCallback;
26 import com.google.gwt.user.client.rpc.ServiceDefTarget;
27
28 import java.util.ArrayList JavaDoc;
29 import java.util.List JavaDoc;
30
31 /**
32  * The implementation class for the translatable {@link GWTTestCase}.
33  *
34  * This is the main test running logic. Each time a test completes, the results
35  * are reported back through {@link #junitHost}, and the next method to run is
36  * returned. This process repeats until the next method to run is null.
37  *
38  * This class is split out of {@link GWTTestCase} to make debugging work. Trying
39  * to debug the translatable {@link GWTTestCase} confuses the debugger, which
40  * tends to use the non-translatable version.
41  */

42 public class GWTTestCaseImpl implements UncaughtExceptionHandler {
43
44   /**
45    * The RPC callback object for {@link GWTTestCaseImpl#junitHost}. When
46    * {@link #onSuccess(Object)} is called, it's time to run the next test case.
47    */

48   private final class JUnitHostListener implements AsyncCallback {
49
50     /**
51      * A call to junitHost failed.
52      */

53     public void onFailure(Throwable JavaDoc caught) {
54       // just stop the test?
55
}
56
57     /**
58      * A call to junitHost succeeded; run the next test case.
59      */

60     public void onSuccess(Object JavaDoc result) {
61       if (result != null) {
62         // Clone the current test case object
63
GWTTestCase testCase = outer.getNewTestCase();
64         // Tell it what method name to run
65
testCase.setName((String JavaDoc) result);
66         // Launch it
67
testCase.impl.runTest();
68       }
69     }
70   }
71
72   /**
73    * A watchdog class for use with asynchronous mode. On construction,
74    * immediately schedules itself for the specified timeout. If the timeout
75    * expires before this timer is cancelled, causes the enclosing test case to
76    * fail with {@link TimeoutException}.
77    */

78   private final class KillTimer extends Timer {
79
80     /**
81      * Stashed so the timeout can be reported via {@link TimeoutException}.
82      */

83     private final int timeoutMillis;
84
85     public KillTimer(int timeoutMillis) {
86       this.timeoutMillis = timeoutMillis;
87       schedule(timeoutMillis);
88     }
89
90     public void run() {
91       if (timer == this) {
92         // The test has failed due to timeout
93
reportResultsAndRunNextMethod(new TimeoutException(timeoutMillis));
94       } else {
95         // Something happened so that we are no longer the active timer.
96
// Just do nothing.
97
}
98     }
99   }
100
101   /**
102    * The remote service to communicate with.
103    */

104   private static final JUnitHostAsync junitHost = (JUnitHostAsync) GWT.create(JUnitHost.class);
105
106   private static String JavaDoc SERVERLESS_QUERY_PARAM = "gwt.junit.testfuncname";
107
108   static {
109     // Bind junitHost to the appropriate url.
110
ServiceDefTarget endpoint = (ServiceDefTarget) junitHost;
111     String JavaDoc url = GWT.getModuleBaseURL() + "junithost";
112     endpoint.setServiceEntryPoint(url);
113
114     // Null out the default uncaught exception handler since control it.
115
GWT.setUncaughtExceptionHandler(null);
116   }
117
118   private static String JavaDoc checkForQueryParamTestToRun() {
119     String JavaDoc query = getQuery();
120     int pos = query.indexOf("?" + SERVERLESS_QUERY_PARAM + "=");
121     if (pos < 0) {
122       pos = query.indexOf("&" + SERVERLESS_QUERY_PARAM + "=");
123     }
124     if (pos < 0) {
125       return null;
126     }
127     // advance past param name to to param value; +2 for the '&' and '='
128
pos += SERVERLESS_QUERY_PARAM.length() + 2;
129     query = query.substring(pos);
130     // trim any query params that follow
131
pos = query.indexOf('&');
132     if (pos >= 0) {
133       query = query.substring(0, pos);
134     }
135     return query;
136   }
137
138   private static native String JavaDoc getDocumentLocation() /*-{
139     return $doc.location.toString();
140   }-*/
;
141
142   private static native String JavaDoc getQuery() /*-{
143     return $wnd.location.search || '';
144   }-*/
;
145
146   /**
147    * The collected checkpoint messages.
148    */

149   private List JavaDoc checkPoints;
150
151   /**
152    * Handles all RPC responses.
153    */

154   private final JUnitHostListener junitHostListener = new JUnitHostListener();
155
156   /**
157    * Tracks whether the main test body has run (for asynchronous mode).
158    */

159   private boolean mainTestHasRun = false;
160
161   /**
162    * My paired (enclosing) {@link GWTTestCase}.
163    */

164   private final GWTTestCase outer;
165
166   /**
167    * Collective test results.
168    *
169    */

170   private TestResults results = new TestResults();
171
172   /**
173    * If true, run a single test case with no RPC.
174    */

175   private boolean serverless = false;
176
177   /**
178    * The time the test began execution.
179    */

180   private long testBeginMillis;
181
182   /**
183    * Tracks whether this test is completely done.
184    */

185   private boolean testIsFinished = false;
186
187   /**
188    * If non-null, a timer to kill the current test case (for asynchronous mode).
189    */

190   private KillTimer timer;
191
192   /**
193    * Constructs a new GWTTestCaseImpl that is paired one-to-one with a
194    * {@link GWTTestCase}.
195    *
196    * @param outer The paired (enclosing) GWTTestCase.
197    */

198   public GWTTestCaseImpl(GWTTestCase outer) {
199     this.outer = outer;
200   }
201
202   /**
203    * Implementation of {@link GWTTestCase#addCheckpoint(String)}.
204    */

205   public void addCheckpoint(String JavaDoc msg) {
206     if (checkPoints == null) {
207       checkPoints = new ArrayList JavaDoc();
208     }
209     checkPoints.add(msg);
210   }
211
212   public void clearCheckpoints() {
213     checkPoints = null;
214   }
215
216   /**
217    * Implementation of {@link GWTTestCase#delayTestFinish(int)}.
218    */

219   public void delayTestFinish(int timeoutMillis) {
220     if (timer != null) {
221       // Cancel the pending timer
222
timer.cancel();
223     }
224
225     // Set a new timer for the specified new timeout
226
timer = new KillTimer(timeoutMillis);
227   }
228
229   /**
230    * Implementation of {@link GWTTestCase#finishTest()}.
231    */

232   public void finishTest() {
233     if (testIsFinished) {
234       // This test is totally done already, just ignore the call.
235
return;
236     }
237
238     if (timer == null) {
239       throw new IllegalStateException JavaDoc(
240           "This test is not in asynchronous mode; call delayTestFinish() first");
241     }
242
243     if (mainTestHasRun) {
244       // This is a correct, successful async finish.
245
reportResultsAndRunNextMethod(null);
246     } else {
247       // The user tried to finish the test before the main body returned!
248
// Just let the test continue running normally.
249
resetAsyncState();
250     }
251   }
252
253   public String JavaDoc[] getCheckpoints() {
254     if (checkPoints == null) {
255       return new String JavaDoc[0];
256     } else {
257       int len = checkPoints.size();
258       String JavaDoc[] result = new String JavaDoc[len];
259       for (int i = 0; i < len; ++i) {
260         result[i] = (String JavaDoc) checkPoints.get(i);
261       }
262       return result;
263     }
264   }
265
266   public TestResults getTestResults() {
267     return results;
268   }
269
270   /**
271    * Implementation of {@link GWTTestCase#onModuleLoad()}.
272    */

273   public void onModuleLoad() {
274     String JavaDoc queryParamTestToRun = checkForQueryParamTestToRun();
275     if (queryParamTestToRun != null) {
276       /*
277        * Just run a single test with no server-side interaction.
278        */

279       outer.setName(queryParamTestToRun);
280       serverless = true;
281       runTest();
282     } else {
283       /*
284        * Normal operation: Kick off the test running process by getting the
285        * first method to run from the server.
286        */

287       junitHost.getFirstMethod(outer.getTestName(), junitHostListener);
288     }
289   }
290
291   /**
292    * An uncaught exception escaped to the browser; what we should do depends on
293    * what state we're in.
294    */

295   public void onUncaughtException(Throwable JavaDoc ex) {
296     if (mainTestHasRun && timer != null) {
297       // Asynchronous mode; uncaught exceptions cause an immediate failure.
298
assert (!testIsFinished);
299       reportResultsAndRunNextMethod(ex);
300     } else {
301       // just ignore it
302
}
303   }
304
305   /**
306    * Cleans up any outstanding state, reports ex to the remote runner, and kicks
307    * off the next test.
308    *
309    * @param ex The results of this test.
310    */

311   private void reportResultsAndRunNextMethod(Throwable JavaDoc ex) {
312     List JavaDoc trials = results.getTrials();
313
314     if (serverless) {
315       // That's it, we're done
316
return;
317     }
318     
319     // TODO(tobyr) - Consider making this logic polymorphic which will remove
320
// instanceof test
321
//
322
// If this is not a benchmark, we have to create a fake trial run
323
if ( ! (outer instanceof com.google.gwt.junit.client.Benchmark) ) {
324       Trial trial = new Trial();
325       long testDurationMillis = System.currentTimeMillis() - testBeginMillis;
326       trial.setRunTimeMillis( testDurationMillis );
327
328       if (ex != null) {
329         ExceptionWrapper ew = new ExceptionWrapper(ex);
330         if (checkPoints != null) {
331           for (int i = 0, c = checkPoints.size(); i < c; ++i) {
332             ew.message += "\n" + checkPoints.get(i);
333           }
334         }
335         trial.setExceptionWrapper( ew );
336       }
337
338       trials.add( trial );
339     } else {
340       // If this was a benchmark, we need to handle exceptions specially
341
// If an exception occurred, it happened without the trial being recorded
342
// We, unfortunately, don't know the trial parameters at this point.
343
// We should consider putting the exception handling code directly into
344
// the generated Benchmark subclasses.
345
if (ex != null) {
346         ExceptionWrapper ew = new ExceptionWrapper(ex);
347         if (checkPoints != null) {
348           for (int i = 0, c = checkPoints.size(); i < c; ++i) {
349             ew.message += "\n" + checkPoints.get(i);
350           }
351         }
352         Trial trial = new Trial();
353         trial.setExceptionWrapper( ew );
354         trials.add( trial );
355       }
356     }
357
358     results.setSourceRef( getDocumentLocation() );
359     testIsFinished = true;
360     resetAsyncState();
361     String JavaDoc testName = outer.getTestName();
362     junitHost.reportResultsAndGetNextMethod(testName, results, junitHostListener);
363   }
364
365   /**
366    * Cleans up any asynchronous mode state.
367    */

368   private void resetAsyncState() {
369     // clear our timer if there is one
370
if (timer != null) {
371       timer.cancel();
372       timer = null;
373     }
374   }
375
376   /**
377    * In the mode where we need to let uncaught exceptions escape to the browser,
378    * this method serves as a hack to avoid "throws" clause problems.
379    */

380   private native void runBareTestCaseAvoidingExceptionDecl() /*-{
381     this.@com.google.gwt.junit.client.impl.GWTTestCaseImpl::outer.@junit.framework.TestCase::runBare()();
382   }-*/
;
383
384   /**
385    * Actually run the user's test.
386    */

387   private void runTest() {
388     Throwable JavaDoc caught = null;
389
390     testBeginMillis = System.currentTimeMillis();
391     results = new TestResults();
392
393     if (shouldCatchExceptions()) {
394       // Make sure no exceptions escape
395
GWT.setUncaughtExceptionHandler(this);
396       try {
397         outer.runBare();
398       } catch (Throwable JavaDoc e) {
399         caught = e;
400       }
401     } else {
402       // Special; make sure all exceptions escape to the browser (for debugging)
403
GWT.setUncaughtExceptionHandler(null);
404       runBareTestCaseAvoidingExceptionDecl();
405     }
406
407     // Mark that the main test body has now run. From this point, if
408
// timer != null we are in true asynchronous mode.
409
mainTestHasRun = true;
410
411     if (caught != null) {
412       // Test failed; finish test no matter what state we're in.
413
reportResultsAndRunNextMethod(caught);
414     } else if (timer != null) {
415       // Test is still running; wait for asynchronous completion.
416
} else {
417       // Test is really done; report success.
418
reportResultsAndRunNextMethod(null);
419     }
420   }
421
422   /**
423    * A helper method to determine if we should catch exceptions. Wraps the call
424    * into user code with a try/catch; if the user's code throws an exception, we
425    * just ignore the exception and use the default behavior.
426    *
427    * @return <code>true</code> if exceptions should be handled normally,
428    * <code>false</code> if they should be allowed to escape.
429    */

430   private boolean shouldCatchExceptions() {
431     try {
432       return outer.catchExceptions();
433     } catch (Throwable JavaDoc e) {
434       return true;
435     }
436   }
437 }
438
Popular Tags