KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > junit > JUnitShell


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;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.UnableToCompleteException;
20 import com.google.gwt.dev.BootStrapPlatform;
21 import com.google.gwt.dev.GWTShell;
22 import com.google.gwt.dev.cfg.ModuleDef;
23 import com.google.gwt.dev.cfg.ModuleDefLoader;
24 import com.google.gwt.dev.shell.BrowserWidgetHost;
25 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
26 import com.google.gwt.junit.benchmarks.BenchmarkReport;
27 import com.google.gwt.junit.client.Benchmark;
28 import com.google.gwt.junit.client.TestResults;
29 import com.google.gwt.junit.client.TimeoutException;
30 import com.google.gwt.junit.client.Trial;
31 import com.google.gwt.junit.remote.BrowserManager;
32 import com.google.gwt.util.tools.ArgHandlerFlag;
33 import com.google.gwt.util.tools.ArgHandlerString;
34
35 import junit.framework.AssertionFailedError;
36 import junit.framework.TestCase;
37 import junit.framework.TestResult;
38
39 import java.io.File JavaDoc;
40 import java.rmi.Naming JavaDoc;
41 import java.util.ArrayList JavaDoc;
42 import java.util.Date JavaDoc;
43 import java.util.List JavaDoc;
44 import java.util.regex.Matcher JavaDoc;
45 import java.util.regex.Pattern JavaDoc;
46
47 /**
48  * This class is responsible for hosting JUnit test case execution. There are
49  * three main pieces to the JUnit system.
50  *
51  * <ul>
52  * <li>Test environment</li>
53  * <li>Client classes</li>
54  * <li>Server classes</li>
55  * </ul>
56  *
57  * <p>
58  * The test environment consists of this class and the non-translatable version
59  * of {@link com.google.gwt.junit.client.GWTTestCase}. These two classes
60  * integrate directly into the real JUnit test process.
61  * </p>
62  *
63  * <p>
64  * The client classes consist of the translatable version of {@link
65  * com.google.gwt.junit.client.GWTTestCase}, translatable JUnit classes, and the
66  * user's own {@link com.google.gwt.junit.client.GWTTestCase}-derived class.
67  * The client communicates to the server via RPC.
68  * </p>
69  *
70  * <p>
71  * The server consists of {@link com.google.gwt.junit.server.JUnitHostImpl}, an
72  * RPC servlet which communicates back to the test environment through a
73  * {@link JUnitMessageQueue}, thus closing the loop.
74  * </p>
75  */

76 public class JUnitShell extends GWTShell {
77
78   /**
79    * Executes shutdown logic for JUnitShell
80    *
81    * Sadly, there's no simple way to know when all unit tests have finished
82    * executing. So this class is registered as a VM shutdown hook so that work
83    * can be done at the end of testing - for example, writing out the reports.
84    */

85   private class Shutdown implements Runnable JavaDoc {
86
87     public void run() {
88       try {
89         String JavaDoc reportPath = System.getProperty(Benchmark.REPORT_PATH);
90         if (reportPath == null || reportPath.trim().equals("")) {
91           reportPath = System.getProperty("user.dir");
92         }
93         report.generate(reportPath + File.separator + "report-"
94             + new Date JavaDoc().getTime() + ".xml");
95       } catch (Exception JavaDoc e) {
96         // It really doesn't matter how we got here.
97
// Regardless of the failure, the VM is shutting down.
98
e.printStackTrace();
99       }
100     }
101   }
102
103   /**
104    * This is a system property that, when set, emulates command line arguments.
105    */

106   private static final String JavaDoc PROP_GWT_ARGS = "gwt.args";
107
108   /**
109    * This legacy system property, when set, causes us to run in web mode.
110    * (Superceded by passing "-web" into gwt.args).
111    */

112   private static final String JavaDoc PROP_JUNIT_HYBRID_MODE = "gwt.hybrid";
113
114   /**
115    * The amount of time to wait for all clients to have contacted the server and
116    * begun running the test.
117    */

118   private static final int TEST_BEGIN_TIMEOUT_MILLIS = 60000;
119
120   /**
121    * Singleton object for hosting unit tests. All test case instances executed
122    * by the TestRunner will use the single unitTestShell.
123    */

124   private static JUnitShell unitTestShell;
125
126   static {
127     ModuleDefLoader.forceInherit("com.google.gwt.junit.JUnit");
128     ModuleDefLoader.setEnableCachingModules(true);
129   }
130
131   /**
132    * Called by {@link com.google.gwt.junit.server.JUnitHostImpl} to get an
133    * interface into the test process.
134    *
135    * @return The {@link JUnitMessageQueue} interface that belongs to the
136    * singleton {@link JUnitShell}, or <code>null</code> if no such
137    * singleton exists.
138    */

139   public static JUnitMessageQueue getMessageQueue() {
140     if (unitTestShell == null) {
141       return null;
142     }
143     return unitTestShell.messageQueue;
144   }
145
146   /**
147    * Called by {@link com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator} to
148    * add test meta data to the test report.
149    *
150    * @return The {@link BenchmarkReport} that belongs to the singleton {@link
151    * JUnitShell}, or <code>null</code> if no such singleton exists.
152    */

153   public static BenchmarkReport getReport() {
154     if (unitTestShell == null) {
155       return null;
156     }
157     return unitTestShell.report;
158   }
159
160   /**
161    * Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
162    * creates the singleton {@link JUnitShell} and invokes its {@link
163    * #runTestImpl(String, TestCase, TestResult)}.
164    */

165   public static void runTest(String JavaDoc moduleName, TestCase testCase,
166       TestResult testResult) throws UnableToCompleteException {
167     getUnitTestShell().runTestImpl(moduleName, testCase, testResult);
168   }
169
170   /**
171    * Retrieves the JUnitShell. This should only be invoked during TestRunner
172    * execution of JUnit tests.
173    */

174   private static JUnitShell getUnitTestShell() {
175     if (unitTestShell == null) {
176       BootStrapPlatform.go();
177       JUnitShell shell = new JUnitShell();
178       String JavaDoc[] args = shell.synthesizeArgs();
179       if (!shell.processArgs(args)) {
180         throw new RuntimeException JavaDoc("Invalid shell arguments");
181       }
182
183       shell.messageQueue = new JUnitMessageQueue(shell.numClients);
184
185       if (!shell.startUp()) {
186         throw new RuntimeException JavaDoc("Shell failed to start");
187       }
188
189       shell.report = new BenchmarkReport(shell.getTopLogger());
190       unitTestShell = shell;
191
192       Runtime.getRuntime().addShutdownHook(new Thread JavaDoc(shell.new Shutdown JavaDoc()));
193     }
194
195     return unitTestShell;
196   }
197
198   /**
199    * When headless, all logging goes to the console.
200    */

201   private PrintWriterTreeLogger consoleLogger;
202
203   /**
204    * Name of the module containing the current/last module to run.
205    */

206   private String JavaDoc currentModuleName;
207
208   /**
209    * If true, the last attempt to launch failed.
210    */

211   private boolean lastLaunchFailed;
212
213   /**
214    * Portal to interact with the servlet.
215    */

216   private JUnitMessageQueue messageQueue;
217
218   /**
219    * The number of test clients executing in parallel. With -remoteweb, users
220    * can specify a number of parallel test clients, but by default we only have
221    * 1.
222    */

223   private int numClients = 1;
224
225   /**
226    * The result of benchmark runs.
227    */

228   private BenchmarkReport report;
229
230   /**
231    * What type of test we're running; Local hosted, local web, or remote web.
232    */

233   private RunStyle runStyle = new RunStyleLocalHosted(this);
234
235   /**
236    * The time at which the current test will fail if the client has not yet
237    * started the test.
238    */

239   private long testBeginTimeout;
240
241   /**
242    * Class name of the current/last test case to run.
243    */

244   private String JavaDoc testCaseClassName;
245
246   /**
247    * Enforce the singleton pattern. The call to {@link GWTShell}'s ctor forces
248    * server mode and disables processing extra arguments as URLs to be shown.
249    */

250   private JUnitShell() {
251     super(true, true);
252
253     registerHandler(new ArgHandlerFlag() {
254
255       public String JavaDoc getPurpose() {
256         return "Causes your test to run in web (compiled) mode (defaults to hosted mode)";
257       }
258
259       public String JavaDoc getTag() {
260         return "-web";
261       }
262
263       public boolean setFlag() {
264         runStyle = new RunStyleLocalWeb(JUnitShell.this);
265         return true;
266       }
267
268     });
269
270     registerHandler(new ArgHandlerString() {
271
272       public String JavaDoc getPurpose() {
273         return "Runs web mode via RMI to a BrowserManagerServer; e.g. rmi://localhost/ie6";
274       }
275
276       public String JavaDoc getTag() {
277         return "-remoteweb";
278       }
279
280       public String JavaDoc[] getTagArgs() {
281         return new String JavaDoc[] {"rmiUrl"};
282       }
283
284       public boolean isUndocumented() {
285         return true;
286       }
287
288       public boolean setString(String JavaDoc str) {
289         try {
290           String JavaDoc[] urls = str.split(",");
291           numClients = urls.length;
292           BrowserManager[] browserManagers = new BrowserManager[numClients];
293           for (int i = 0; i < numClients; ++i) {
294             browserManagers[i] = (BrowserManager) Naming.lookup(urls[i]);
295           }
296           runStyle = new RunStyleRemoteWeb(JUnitShell.this, browserManagers);
297         } catch (Exception JavaDoc e) {
298           System.err.println("Error connecting to browser manager at " + str);
299           e.printStackTrace();
300           System.exit(1);
301           return false;
302         }
303         return true;
304       }
305     });
306
307     registerHandler(new ArgHandlerFlag() {
308
309       public String JavaDoc getPurpose() {
310         return "Causes the log window and browser windows to be displayed; useful for debugging";
311       }
312
313       public String JavaDoc getTag() {
314         return "-notHeadless";
315       }
316
317       public boolean setFlag() {
318         setHeadless(false);
319         return true;
320       }
321     });
322
323     setRunTomcat(true);
324     setHeadless(true);
325
326     // Legacy: -Dgwt.hybrid runs web mode
327
if (System.getProperty(PROP_JUNIT_HYBRID_MODE) != null) {
328       runStyle = new RunStyleLocalWeb(this);
329     }
330   }
331
332   public TreeLogger getTopLogger() {
333     if (consoleLogger != null) {
334       return consoleLogger;
335     } else {
336       return super.getTopLogger();
337     }
338   }
339
340   protected String JavaDoc doGetDefaultLogLevel() {
341     return "WARN";
342   }
343
344   /**
345    * Overrides the default module loading behavior. Clears any existing entry
346    * points and adds an entry point for the class being tested. This test class
347    * is then rebound to a generated subclass.
348    */

349   protected ModuleDef doLoadModule(TreeLogger logger, String JavaDoc moduleName)
350       throws UnableToCompleteException {
351
352     ModuleDef module = super.doLoadModule(logger, moduleName);
353
354     // Tweak the module for JUnit support
355
//
356
if (moduleName.equals(currentModuleName)) {
357       module.clearEntryPoints();
358       module.addEntryPointTypeName(testCaseClassName);
359     }
360     return module;
361   }
362
363   /**
364    * Never check for updates in JUnit mode.
365    */

366   protected boolean doShouldCheckForUpdates() {
367     return false;
368   }
369
370   protected ArgHandlerPort getArgHandlerPort() {
371     return new ArgHandlerPort() {
372       public String JavaDoc[] getDefaultArgs() {
373         return new String JavaDoc[] {"-port", "auto"};
374       }
375     };
376   }
377
378   protected void initializeLogger() {
379     if (isHeadless()) {
380       consoleLogger = new PrintWriterTreeLogger();
381       consoleLogger.setMaxDetail(getLogLevel());
382     } else {
383       super.initializeLogger();
384     }
385   }
386
387   /**
388    * Overrides {@link GWTShell#notDone()} to wait for the currently-running test
389    * to complete.
390    */

391   protected boolean notDone() {
392     if (!messageQueue.haveAllClientsRetrievedCurrentTest()
393         && testBeginTimeout < System.currentTimeMillis()) {
394       throw new TimeoutException(
395           "The browser did not contact the server within "
396               + TEST_BEGIN_TIMEOUT_MILLIS + "ms.");
397     }
398
399     if (messageQueue.hasResult(testCaseClassName)) {
400       return false;
401     }
402
403     return !runStyle.wasInterrupted();
404   }
405
406   void compileForWebMode(String JavaDoc moduleName) throws UnableToCompleteException {
407     BrowserWidgetHost browserHost = getBrowserHost();
408     assert (browserHost != null);
409     browserHost.compile(new String JavaDoc[] {moduleName});
410   }
411
412   /**
413    * Runs a particular test case.
414    */

415   private void runTestImpl(String JavaDoc moduleName, TestCase testCase,
416       TestResult testResult) throws UnableToCompleteException {
417
418     String JavaDoc newTestCaseClassName = testCase.getClass().getName();
419     boolean sameTest = newTestCaseClassName.equals(testCaseClassName);
420     if (sameTest && lastLaunchFailed) {
421       throw new UnableToCompleteException();
422     }
423
424     messageQueue.setNextTestName(newTestCaseClassName, testCase.getName());
425
426     try {
427       lastLaunchFailed = false;
428       testCaseClassName = newTestCaseClassName;
429       currentModuleName = moduleName;
430       runStyle.maybeLaunchModule(moduleName, !sameTest);
431     } catch (UnableToCompleteException e) {
432       lastLaunchFailed = true;
433       testResult.addError(testCase, e);
434       return;
435     }
436
437     // Wait for test to complete
438
try {
439       // Set a timeout period to automatically fail if the servlet hasn't been
440
// contacted; something probably went wrong (the module failed to load?)
441
testBeginTimeout = System.currentTimeMillis() + TEST_BEGIN_TIMEOUT_MILLIS;
442       pumpEventLoop();
443     } catch (TimeoutException e) {
444       lastLaunchFailed = true;
445       testResult.addError(testCase, e);
446       return;
447     }
448
449     List JavaDoc/* JUnitMessageQueue.TestResult */results
450         = messageQueue.getResults(testCaseClassName);
451
452     if (results == null) {
453       return;
454     }
455
456     boolean parallelTesting = numClients > 1;
457
458     for (int i = 0; i < results.size(); ++i) {
459       TestResults result = (TestResults) results.get(i);
460       Trial firstTrial = (Trial) result.getTrials().get(0);
461       Throwable JavaDoc exception = firstTrial.getException();
462
463       // In the case that we're running multiple clients at once, we need to
464
// let the user know the browser in which the failure happened
465
if (parallelTesting && exception != null) {
466         String JavaDoc msg = "Remote test failed at " + result.getHost() + " on "
467             + result.getAgent();
468         if (exception instanceof AssertionFailedError) {
469           AssertionFailedError newException = new AssertionFailedError(msg
470               + "\n" + exception.getMessage());
471           newException.setStackTrace(exception.getStackTrace());
472           exception = newException;
473         } else {
474           exception = new RuntimeException JavaDoc(msg, exception);
475         }
476       }
477
478       // A "successful" failure
479
if (exception instanceof AssertionFailedError) {
480         testResult.addFailure(testCase, (AssertionFailedError) exception);
481       } else if (exception != null) {
482         // A real failure
483
testResult.addError(testCase, exception);
484       }
485
486       if (testCase instanceof Benchmark) {
487         report.addBenchmarkResults(testCase, result);
488       }
489     }
490   }
491
492   /**
493    * Synthesize command line arguments from a system property.
494    */

495   private String JavaDoc[] synthesizeArgs() {
496     ArrayList JavaDoc argList = new ArrayList JavaDoc();
497
498     String JavaDoc args = System.getProperty(PROP_GWT_ARGS);
499     if (args != null) {
500       // Match either a non-whitespace, non start of quoted string, or a
501
// quoted string that can have embedded, escaped quoting characters
502
//
503
Pattern JavaDoc pattern = Pattern.compile("[^\\s\"]+|\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"");
504       Matcher JavaDoc matcher = pattern.matcher(args);
505       while (matcher.find()) {
506         argList.add(matcher.group());
507       }
508     }
509
510     return (String JavaDoc[]) argList.toArray(new String JavaDoc[argList.size()]);
511   }
512 }
513
Popular Tags