View Javadoc
1   package org.codehaus.mojo.gwt.shell;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.plugin.MojoExecutionException;
24  import org.apache.maven.plugin.MojoFailureException;
25  import org.apache.maven.plugins.annotations.LifecyclePhase;
26  import org.apache.maven.plugins.annotations.Mojo;
27  import org.apache.maven.plugins.annotations.Parameter;
28  import org.apache.maven.plugins.annotations.ResolutionScope;
29  import org.apache.maven.surefire.report.ReporterManager;
30  import org.codehaus.mojo.gwt.test.MavenTestRunner;
31  import org.codehaus.mojo.gwt.test.TestTemplate;
32  import org.codehaus.plexus.util.StringUtils;
33  
34  import java.io.File;
35  import java.net.URL;
36  import java.util.Collection;
37  
38  /**
39   * Mimic surefire to run GWTTestCases during integration-test phase, until SUREFIRE-508 is fixed
40   *
41   * @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a>
42   * @version $Id: TestMojo.java 9466 2009-04-16 12:03:15Z ndeloof $
43   */
44  @Mojo(name = "test", defaultPhase = LifecyclePhase.INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
45  public class TestMojo
46      extends AbstractGwtShellMojo
47  {
48  
49      /**
50       * Set this to 'true' to skip running tests, but still compile them. Its use is NOT RECOMMENDED,
51       * but quite convenient on occasion.
52       */
53      @Parameter(property = "skipTests")
54      private boolean skipTests;
55  
56      /**
57       * DEPRECATED This old parameter is just like skipTests, but bound to the old property
58       * maven.test.skip.exec. Use -DskipTests instead; it's shorter.
59       */
60      @Deprecated
61      @Parameter(property = "maven.test.skip.exec")
62      private boolean skipExec;
63  
64      /**
65       * Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if
66       * you enable it using the "maven.test.skip" property, because maven.test.skip disables both
67       * running the tests and compiling the tests. Consider using the skipTests parameter instead.
68       */
69      @Parameter(property = "maven.test.skip")
70      private boolean skip;
71  
72      /**
73       * Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite
74       * convenient on occasion.
75       */
76      @Parameter(property = "maven.test.failure.ignore")
77      private boolean testFailureIgnore;
78  
79      /**
80       * output directory for code generated by GWT for tests
81       */
82      @Parameter(defaultValue = "${project.build.directory}/www-test")
83      private String out;
84  
85      /**
86       * run tests using web mode rather than developer (a.k.a. hosted) mode
87       * 
88       * @deprecated Use productionMode instead.
89       */
90      @Deprecated
91      @Parameter(defaultValue = "false", property = "gwt.test.web")
92      private boolean webMode;
93  
94      /**
95       * run tests using production mode rather than development (a.k.a. hosted) mode.
96       * 
97       * @see http://code.google.com/intl/fr-FR/webtoolkit/doc/latest/DevGuideCompilingAndDebugging.html#DevGuideProdMode
98       */
99      @Parameter(defaultValue = "false", property = "gwt.test.prod")
100     private boolean productionMode;
101 
102     /**
103      * Configure test mode. Can be set to "manual", "htmlunit" or "selenium". If set
104      * to any other value, that value will be passed as the <code>-runStyle</code> argument,
105      * allowing you to use an arbitrary RunStyle when running tests.
106      */
107     @Parameter(defaultValue = "htmlunit", property = "gwt.test.mode")
108     private String mode;
109 
110     /**
111      * Configure options to run tests with HTMLUnit. The value must describe the browser emulation
112      * to be used, FF31, FF38, IE8, IE11, Chrome or Edge (possible multiple values separated by comas).
113      * 
114      * @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingHtmlUnit.html
115      */
116     @Parameter(defaultValue = "FF38", property = "gwt.test.htmlunit")
117     private String htmlunit;
118 
119     /**
120      * Configure options to run tests with Selenium. The value must describe the Selenium Remote
121      * Control target
122      * 
123      * @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingRemoteTesting.html#Selenium
124      */
125     @Parameter(property = "gwt.test.selenium")
126     private String selenium;
127 
128     /**
129      * Time out (in seconds) for test execution in dedicated JVM
130      */
131     @Parameter(defaultValue = "60")
132     @SuppressWarnings("unused")
133     private int testTimeOut;
134 
135     /**
136      * Comma separated list of ant-style inclusion patterns for GWT integration tests. For example,
137      * can be set to <code>**\/*GwtTest.java</code> to match all test class following this naming
138      * convention. Surefire plugin may then ne configured to exclude such tests.
139      * <p>
140      * It is recommended to use a TestSuite to run GwtTests, as they require some huge setup and are
141      * very slow. Running inside a suite allow to execute the setup only once. The default value is
142      * defined with this best practice in mind.
143      */
144     @Parameter(defaultValue = "**/GwtTest*.java,**/Gwt*Suite.java")
145     protected String includes;
146 
147     /**
148      * Comma separated list of ant-style exclusion patterns for GWT integration tests
149      */
150     @Parameter
151     protected String excludes;
152 
153     /**
154      * Directory for test reports, defaults to surefire one to match the surefire-report plugin
155      */
156     @Parameter(defaultValue = "${project.build.directory}/surefire-reports")
157     private File reportsDirectory;
158     
159     /**
160      * Specify the user agents to reduce the number of permutations in '-prod' mode;
161      * e.g. ie8,safari,gecko1_8
162      * 
163      * @since 2.5.0-rc1
164      */
165     @Parameter(property = "gwt.test.userAgents")
166     private String userAgents;
167     
168     /**
169      * Configure batch execution of tests.
170      * <p>
171      * Value must be one of 'none', 'class' or 'module'.
172      * </p>
173      * 
174      * @since 2.5.0-rc1
175      */
176     @Parameter(property = "gwt.test.batch")
177     private String batch;
178 
179     /**
180      * Causes the log window and browser windows to be displayed; useful for debugging.
181      * 
182      * @since 2.6.0-rc1
183      */
184     @Parameter(defaultValue = "false", property = "gwt.test.showUi")
185     private boolean showUi;
186 
187     /**
188      * The compiler's working directory for internal use (must be writeable; defaults to a system temp dir)
189      *
190      * @since 2.6.0-rc1
191      */
192     @Parameter
193     private File workDir;
194 
195     /**
196      * Logs to a file in the given directory
197      * 
198      * @since 2.6.0-rc1
199      */
200     @Parameter
201     private File logDir;
202 
203     /**
204      * Specifies Java source level.
205      * <p>
206      * The default value depends on the JVM used to launch Maven.
207      *
208      * @since 2.6.0-rc1
209      */
210     @Parameter(defaultValue = "auto", property = "maven.compiler.source")
211     private String sourceLevel;
212 
213     /**
214      * Whether or not to enable assertions in generated scripts (-checkAssertions).
215      *
216      * @since 2.6.0-rc1
217      */
218     @Parameter(alias = "enableAssertions", defaultValue = "false")
219     private boolean checkAssertions;
220 
221     /**
222      * EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName()).
223      * <p>
224      * Can be set from command line using '-Dgwt.disableClassMetadata=true'.
225      * </p>
226      *
227      * @since 2.6.0-rc1
228      */
229     @Parameter(defaultValue = "false", property = "gwt.disableClassMetadata")
230     private boolean disableClassMetadata;
231 
232     /**
233      * EXPERIMENTAL: Disables run-time checking of cast operations.
234      * <p>
235      * Can be set from command line using '-Dgwt.disableCastChecking=true'.
236      * </p>
237      *
238      * @since 2.6.0-rc1
239      */
240     @Parameter(defaultValue = "false", property = "gwt.disableCastChecking")
241     private boolean disableCastChecking;
242 
243     /**
244      * EXPERIMENTAL: Disables code-splitting.
245      * <p>
246      * Can be set from command line using '-Dgwt.disableRunAsync=true'.
247      * </p>
248      *
249      * @since 2.6.0-rc1
250      */
251     @Parameter(defaultValue = "false", property = "gwt.disableRunAsync")
252     private boolean disableRunAsync;
253 
254     /**
255      * Enable faster, but less-optimized, compilations.
256      * <p>
257      * Can be set from command line using '-Dgwt.draftCompile=true'.
258      * </p>
259      *
260      * @since 2.6.0-rc1
261      */
262     @Parameter(defaultValue = "false", property = "gwt.draftCompiler")
263     private boolean draftCompile;
264 
265     /**
266      * EXPERIMENTAL: Cluster similar functions in the output to improve compression.
267      *
268      * @since 2.6.0-rc1
269      */
270     @Parameter(defaultValue = "true", property = "gwt.compiler.clusterFunctions")
271     private boolean clusterFunctions;
272 
273     /**
274      * EXPERIMENTAL: Inline literal parameters to shrink function declarations and
275      * provide more deadcode elimination possibilities.
276      *
277      * @since 2.6.0-rc1
278      */
279     @Parameter(defaultValue = "true", property = "gwt.compiler.inlineLiteralParameters")
280     private boolean inlineLiteralParameters;
281 
282     /**
283      * EXPERIMENTAL: Analyze and optimize dataflow.
284      *
285      * since 2.6.0-rc1
286      */
287     @Parameter(defaultValue = "true", property = "gwt.compiler.optimizeDataflow")
288     private boolean optimizeDataflow;
289 
290     /**
291      * EXPERIMENTAL: Ordinalize enums to reduce some large strings.
292      *
293      * @since 2.6.0-rc1
294      */
295     @Parameter(defaultValue = "true", property = "gwt.compiler.ordinalizeEnums")
296     private boolean ordinalizeEnums;
297 
298     /**
299      * EXPERIMENTAL: Removing duplicate functions.
300      * <p>
301      * Will interfere with stacktrace deobfuscation and so is only honored when compiler.stackMode is set to strip.
302      *
303      * @since 2.6.0-rc1
304      */
305     @Parameter(defaultValue = "true", property = "gwt.compiler.removeDuplicateFunctions")
306     private boolean removeDuplicateFunctions;
307 
308     /**
309      * Sets the optimization level used by the compiler.  0=none 9=maximum.
310      * <p>
311      * -1 uses the default level of the compiler.
312      * </p>
313      * <p>
314      * Can be set from command line using '-Dgwt.compiler.optimizationLevel=n'.
315      * </p>
316      *
317      * @since 2.6.0-rc1
318      */
319     @Parameter(defaultValue = "-1", property = "gwt.compiler.optimizationLevel")
320     private int optimizationLevel;
321 
322     /**
323      * Set the test method timeout, in minutes
324      * 
325      * @since 2.6.0-rc1
326      */
327     @Parameter(defaultValue = "5", property = "gwt.testMethodTimeout")
328     private int testMethodTimeout;
329 
330     /**
331      * Set the test begin timeout (time for clients to contact server), in minutes
332      * 
333      * @since 2.6.0-rc1
334      */
335     @Parameter(defaultValue = "1", property = "gwt.testBeginTimeout")
336     private int testBeginTimeout;
337 
338     /**
339      * Precompile modules as tests are running (speeds up remote tests but requires more memory)
340      * <p>
341      * The value is one of <tt>simple</tt>, <tt>all</tt>, or <tt>parallel</tt>.
342      * 
343      * @since 2.6.0-rc1
344      */
345     @Parameter(defaultValue = "simple", property = "gwt.test.precompile")
346     private String precompile;
347 
348     /**
349      * EXPERIMENTAL: Sets the maximum number of attempts for running each test method
350      * 
351      * @since 2.6.0-rc1
352      */
353     @Parameter(defaultValue = "1", property = "gwt.test.tries")
354     private int tries;
355 
356     /**
357      * Puts most JavaScript globals into namespaces.
358      * <p>
359      * Value is one of PACKAGE or NONE.
360      * <p>
361      * Default: PACKAGE for -draftCompile, otherwise NONE
362      * 
363      * @since 2.7.0-rc1
364      */
365     @Parameter
366     private String namespace;
367 
368     /**
369      * Compiles faster by reusing data from the previous compile.
370      * 
371      * @since 2.7.0-rc1
372      */
373     @Parameter(alias = "compilePerFile", defaultValue = "false", property = "gwt.compiler.incremental")
374     private boolean incremental;
375 
376     /**
377      * Generate exports for JsInterop purposes.
378      *
379      * @since 2.8.0-rc1
380      */
381     @Parameter(alias = "generateJsInteropExports", defaultValue = "false", property = "gwt.compiler.generateJsInteropExports")
382     private boolean generateJsInteropExports;
383 
384     /** failures counter */
385     private int failures;
386 
387     @Override
388     public void doExecute()
389         throws MojoExecutionException, MojoFailureException
390     {
391         if ( skip || skipTests || skipExec )
392         {
393             return;
394         }
395         new TestTemplate( getProject(), includes, excludes, new TestTemplate.CallBack()
396         {
397             public void doWithTest( File sourceDir, String test )
398                 throws MojoExecutionException
399             {
400                 forkToRunTest( test );
401             }
402         } );
403 
404         if ( failures > 0 )
405         {
406             if ( testFailureIgnore )
407             {
408                 getLog().error( "There are test failures.\n\nPlease refer to " + reportsDirectory
409                                     + " for the individual test results." );
410             }
411             else
412             {
413                 throw new MojoExecutionException( "There were test failures." );
414             }
415         }
416     }
417 
418     /**
419      * @param classpath the test execution classpath
420      * @param jvm the JVM process command
421      * @param test the test to run
422      * @throws MojoExecutionException some error occured
423      */
424     private void forkToRunTest( String test )
425         throws MojoExecutionException
426     {
427         test = test.substring( 0, test.length() - 5 );
428         test = StringUtils.replace( test, File.separator, "." );
429         try
430         {
431             File outFile = new File(out);
432             if (outFile.isAbsolute())
433             {
434                 outFile.mkdirs();
435             }
436             else
437             {
438                 new File( getProject().getBasedir(), out ).mkdirs();
439             }
440             try
441             {
442                 JavaCommand cmd = createJavaCommand()
443                     .setMainClass( MavenTestRunner.class.getName() );
444                 if ( gwtSdkFirstInClasspath )
445                 {
446                     cmd.addToClasspath( getGwtUserJar() )
447                        .addToClasspath( getGwtDevJar() );
448                 }
449                 cmd.addToClasspath( getClasspath( Artifact.SCOPE_TEST ) );
450                 if ( !gwtSdkFirstInClasspath )
451                 {
452                     cmd.addToClasspath( getGwtUserJar() )
453                        .addToClasspath( getGwtDevJar() );
454                 }
455 
456                 addCompileSourceArtifacts( cmd );
457 
458                 cmd.arg( test );
459                 cmd.systemProperty( "surefire.reports", reportsDirectory.getAbsolutePath() );
460                 cmd.systemProperty( "gwt.args", StringUtils.escape(getGwtArgs()) );
461 
462                 cmd.execute();
463             }
464             catch ( JavaCommandException e )
465             {
466                 getLog().warn("GWT test failure", e);
467                 failures++;
468             }
469         }
470         catch ( Exception e )
471         {
472             throw new MojoExecutionException( "Failed to run GWT tests", e );
473         }
474     }
475 
476     protected String getGwtArgs()
477     {
478         StringBuilder sb = new StringBuilder();
479         sb.append( "-war " ).append( quote( out ) );
480         sb.append( " -logLevel " ).append( quote( getLogLevel() ) );
481         sb.append( ( webMode || productionMode ) ? " -nodevMode" : " -devMode" );
482         sb.append( checkAssertions ? " -checkAssertions" : " -nocheckAssertions" );
483         sb.append( clusterFunctions ? " -XclusterFunctions" : " -XnoclusterFunctions" );
484         sb.append( disableCastChecking ? " -XnocheckCasts" : " -XcheckCasts" );
485         sb.append( disableClassMetadata ? " -XnoclassMetadata" : " -XclassMetadata" );
486         sb.append( disableRunAsync ? " -XnocodeSplitting" : " -XcodeSplitting" );
487         sb.append( draftCompile ? " -draftCompile" : " -nodraftCompile" );
488         sb.append( inlineLiteralParameters ? " -XinlineLiteralParameters" : " -XnoinlineLiteralParameters" );
489         sb.append( optimizeDataflow ? " -XoptimizeDataflow" : " -XnooptimizeDataflow" );
490         sb.append( ordinalizeEnums ? " -XordinalizeEnums" : " -XnoordinalizeEnums" );
491         sb.append( removeDuplicateFunctions ? " -XremoveDuplicateFunctions" : " -XnoremoveDuplicateFunctions" );
492         sb.append( showUi ? " -showUi" : " -noshowUi" );
493         sb.append( " -sourceLevel " ).append( quote( sourceLevel ) );
494         sb.append( " -testBeginTimeout " ).append( testBeginTimeout );
495         sb.append( " -testMethodTimeout ").append( testMethodTimeout );
496         sb.append( " -Xtries " ).append( tries );
497         sb.append( incremental ? " -incremental" : " -noincremental" );
498 
499         if ( optimizationLevel >= 0 )
500         {
501             sb.append( " -optimize " ).append( optimizationLevel );
502         }
503         if ( precompile != null && !precompile.trim().isEmpty() )
504         {
505             sb.append( " -precompile " ).append( quote( precompile ) );
506         }
507         if ( logDir != null )
508         {
509             sb.append( " -logdir " ).append( quote( logDir.getAbsolutePath() ) );
510         }
511         if ( workDir != null )
512         {
513             sb.append( " -workDir " ).append( quote( workDir.getAbsolutePath() ) );
514         }
515         if ( namespace != null && !namespace.trim().isEmpty() )
516         {
517             sb.append( " -Xnamespace " ).append( quote( namespace ) );
518         }
519         if ( generateJsInteropExports )
520         {
521             sb.append( " -generateJsInteropExports" );
522         }
523         if ( mode.equalsIgnoreCase( "manual" ) )
524         {
525             sb.append( " -runStyle Manual:1" );
526         }
527         else if ( mode.equalsIgnoreCase( "htmlunit" ) )
528         {
529             sb.append( " -runStyle ").append( quote( "HtmlUnit:" + htmlunit ) );
530         }
531         else if ( mode.equalsIgnoreCase( "selenium" ) )
532         {
533             sb.append( " -runStyle ").append( quote( "Selenium:" + selenium ) );
534         }
535         else if ( !mode.trim().isEmpty() )
536         {
537             sb.append( " -runStyle ").append( quote( mode ) );
538         }
539         if ( userAgents != null && !userAgents.trim().isEmpty() )
540         {
541             sb.append( " -userAgents " ).append( quote( userAgents ) );
542         }
543         if ( batch != null && !batch.trim().isEmpty() )
544         {
545             sb.append( " -batch " ).append( quote( batch ) );
546         }
547         // TODO Is addArgumentDeploy(cmd) also needed to get readable test stacktraces with an alternative deploy dir?
548 
549         return sb.toString();
550     }
551 
552     private String quote(String arg) {
553         return StringUtils.quoteAndEscape( arg, '"', new char[] { '"', ' ', '\t', '\r', '\n' } );
554     }
555 
556     @Override
557     protected void postProcessClassPath( Collection<File> classpath )
558     {
559         classpath.add( getClassPathElementFor( TestMojo.class ) );
560         classpath.add( getClassPathElementFor( ReporterManager.class ) );
561     }
562 
563     /**
564      * @param clazz class to check for classpath resolution
565      * @return The classpath element this class was loaded from
566      */
567     private File getClassPathElementFor( Class<?> clazz )
568     {
569         String classFile = clazz.getName().replace( '.', '/' ) + ".class";
570         ClassLoader cl = Thread.currentThread().getContextClassLoader();
571         if ( cl == null )
572         {
573             cl = getClass().getClassLoader();
574         }
575         URL url = cl.getResource( classFile );
576         getLog().debug( "getClassPathElementFor " + clazz.getName() + " file " + url.toString() );
577         String path = url.toString();
578     
579         if ( path.startsWith( "jar:" ) )
580         {
581             path = path.substring( 4, path.indexOf( "!" ) );
582         }
583         else
584         {
585             path = path.substring( 0, path.length() - classFile.length() );
586         }
587         if ( path.startsWith( "file:" ) )
588         {
589             path = path.substring( 5 );
590             // windauze hack with maven 3 we get those !
591             path = path.replace( "%20", " " );
592         }
593         File file = new File( path );
594         getLog().debug( "getClassPathElementFor " + clazz.getName() + " file " + file.getPath() );
595         return file;
596     }
597 
598     /**
599      * @param testTimeOut the testTimeOut to set
600      */
601     public void setTestTimeOut( int testTimeOut )
602     {
603         setTimeOut( testTimeOut );
604     }
605 
606 }