package org.codehaus.mojo.gwt.shell; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.surefire.report.ReporterManager; import org.codehaus.mojo.gwt.test.MavenTestRunner; import org.codehaus.mojo.gwt.test.TestTemplate; import org.codehaus.plexus.util.StringUtils; import java.io.File; import java.net.URL; import java.util.Collection; /** * Mimic surefire to run GWTTestCases during integration-test phase, until SUREFIRE-508 is fixed * * @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a> * @version $Id: TestMojo.java 9466 2009-04-16 12:03:15Z ndeloof $ */ @Mojo(name = "test", defaultPhase = LifecyclePhase.INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) public class TestMojo extends AbstractGwtShellMojo { /** * Set this to 'true' to skip running tests, but still compile them. Its use is NOT RECOMMENDED, * but quite convenient on occasion. */ @Parameter(property = "skipTests") private boolean skipTests; /** * DEPRECATED This old parameter is just like skipTests, but bound to the old property * maven.test.skip.exec. Use -DskipTests instead; it's shorter. */ @Deprecated @Parameter(property = "maven.test.skip.exec") private boolean skipExec; /** * Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if * you enable it using the "maven.test.skip" property, because maven.test.skip disables both * running the tests and compiling the tests. Consider using the skipTests parameter instead. */ @Parameter(property = "maven.test.skip") private boolean skip; /** * Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite * convenient on occasion. */ @Parameter(property = "maven.test.failure.ignore") private boolean testFailureIgnore; /** * output directory for code generated by GWT for tests */ @Parameter(defaultValue = "${project.build.directory}/www-test") private String out; /** * run tests using web mode rather than developer (a.k.a. hosted) mode * * @deprecated Use productionMode instead. */ @Deprecated @Parameter(defaultValue = "false", property = "gwt.test.web") private boolean webMode; /** * run tests using production mode rather than development (a.k.a. hosted) mode. * * @see http://code.google.com/intl/fr-FR/webtoolkit/doc/latest/DevGuideCompilingAndDebugging.html#DevGuideProdMode */ @Parameter(defaultValue = "false", property = "gwt.test.prod") private boolean productionMode; /** * Configure test mode. Can be set to "manual", "htmlunit" or "selenium". If set * to any other value, that value will be passed as the <code>-runStyle</code> argument, * allowing you to use an arbitrary RunStyle when running tests. */ @Parameter(defaultValue = "htmlunit", property = "gwt.test.mode") private String mode; /** * Configure options to run tests with HTMLUnit. The value must describe the browser emulation * to be used, FF31, FF38, IE8, IE11, Chrome or Edge (possible multiple values separated by comas). * * @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingHtmlUnit.html */ @Parameter(defaultValue = "FF38", property = "gwt.test.htmlunit") private String htmlunit; /** * Configure options to run tests with Selenium. The value must describe the Selenium Remote * Control target * * @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingRemoteTesting.html#Selenium */ @Parameter(property = "gwt.test.selenium") private String selenium; /** * Time out (in seconds) for test execution in dedicated JVM */ @Parameter(defaultValue = "60") @SuppressWarnings("unused") private int testTimeOut; /** * Comma separated list of ant-style inclusion patterns for GWT integration tests. For example, * can be set to <code>**\/*GwtTest.java</code> to match all test class following this naming * convention. Surefire plugin may then ne configured to exclude such tests. * <p> * It is recommended to use a TestSuite to run GwtTests, as they require some huge setup and are * very slow. Running inside a suite allow to execute the setup only once. The default value is * defined with this best practice in mind. */ @Parameter(defaultValue = "**/GwtTest*.java,**/Gwt*Suite.java") protected String includes; /** * Comma separated list of ant-style exclusion patterns for GWT integration tests */ @Parameter protected String excludes; /** * Directory for test reports, defaults to surefire one to match the surefire-report plugin */ @Parameter(defaultValue = "${project.build.directory}/surefire-reports") private File reportsDirectory; /** * Specify the user agents to reduce the number of permutations in '-prod' mode; * e.g. ie8,safari,gecko1_8 * * @since 2.5.0-rc1 */ @Parameter(property = "gwt.test.userAgents") private String userAgents; /** * Configure batch execution of tests. * <p> * Value must be one of 'none', 'class' or 'module'. * </p> * * @since 2.5.0-rc1 */ @Parameter(property = "gwt.test.batch") private String batch; /** * Causes the log window and browser windows to be displayed; useful for debugging. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.test.showUi") private boolean showUi; /** * The compiler's working directory for internal use (must be writeable; defaults to a system temp dir) * * @since 2.6.0-rc1 */ @Parameter private File workDir; /** * Logs to a file in the given directory * * @since 2.6.0-rc1 */ @Parameter private File logDir; /** * Specifies Java source level. * <p> * The default value depends on the JVM used to launch Maven. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "auto", property = "maven.compiler.source") private String sourceLevel; /** * Whether or not to enable assertions in generated scripts (-checkAssertions). * * @since 2.6.0-rc1 */ @Parameter(alias = "enableAssertions", defaultValue = "false") private boolean checkAssertions; /** * EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName()). * <p> * Can be set from command line using '-Dgwt.disableClassMetadata=true'. * </p> * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.disableClassMetadata") private boolean disableClassMetadata; /** * EXPERIMENTAL: Disables run-time checking of cast operations. * <p> * Can be set from command line using '-Dgwt.disableCastChecking=true'. * </p> * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.disableCastChecking") private boolean disableCastChecking; /** * EXPERIMENTAL: Disables code-splitting. * <p> * Can be set from command line using '-Dgwt.disableRunAsync=true'. * </p> * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.disableRunAsync") private boolean disableRunAsync; /** * Enable faster, but less-optimized, compilations. * <p> * Can be set from command line using '-Dgwt.draftCompile=true'. * </p> * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "false", property = "gwt.draftCompiler") private boolean draftCompile; /** * EXPERIMENTAL: Cluster similar functions in the output to improve compression. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.clusterFunctions") private boolean clusterFunctions; /** * EXPERIMENTAL: Inline literal parameters to shrink function declarations and * provide more deadcode elimination possibilities. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.inlineLiteralParameters") private boolean inlineLiteralParameters; /** * EXPERIMENTAL: Analyze and optimize dataflow. * * since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.optimizeDataflow") private boolean optimizeDataflow; /** * EXPERIMENTAL: Ordinalize enums to reduce some large strings. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.ordinalizeEnums") private boolean ordinalizeEnums; /** * EXPERIMENTAL: Removing duplicate functions. * <p> * Will interfere with stacktrace deobfuscation and so is only honored when compiler.stackMode is set to strip. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "true", property = "gwt.compiler.removeDuplicateFunctions") private boolean removeDuplicateFunctions; /** * Sets the optimization level used by the compiler. 0=none 9=maximum. * <p> * -1 uses the default level of the compiler. * </p> * <p> * Can be set from command line using '-Dgwt.compiler.optimizationLevel=n'. * </p> * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "-1", property = "gwt.compiler.optimizationLevel") private int optimizationLevel; /** * Set the test method timeout, in minutes * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "5", property = "gwt.testMethodTimeout") private int testMethodTimeout; /** * Set the test begin timeout (time for clients to contact server), in minutes * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "1", property = "gwt.testBeginTimeout") private int testBeginTimeout; /** * Precompile modules as tests are running (speeds up remote tests but requires more memory) * <p> * The value is one of <tt>simple</tt>, <tt>all</tt>, or <tt>parallel</tt>. * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "simple", property = "gwt.test.precompile") private String precompile; /** * EXPERIMENTAL: Sets the maximum number of attempts for running each test method * * @since 2.6.0-rc1 */ @Parameter(defaultValue = "1", property = "gwt.test.tries") private int tries; /** * Puts most JavaScript globals into namespaces. * <p> * Value is one of PACKAGE or NONE. * <p> * Default: PACKAGE for -draftCompile, otherwise NONE * * @since 2.7.0-rc1 */ @Parameter private String namespace; /** * Compiles faster by reusing data from the previous compile. * * @since 2.7.0-rc1 */ @Parameter(alias = "compilePerFile", defaultValue = "false", property = "gwt.compiler.incremental") private boolean incremental; /** * Generate exports for JsInterop purposes. * * @since 2.8.0-rc1 */ @Parameter(alias = "generateJsInteropExports", defaultValue = "false", property = "gwt.compiler.generateJsInteropExports") private boolean generateJsInteropExports; /** failures counter */ private int failures; @Override public void doExecute() throws MojoExecutionException, MojoFailureException { if ( skip || skipTests || skipExec ) { return; } new TestTemplate( getProject(), includes, excludes, new TestTemplate.CallBack() { public void doWithTest( File sourceDir, String test ) throws MojoExecutionException { forkToRunTest( test ); } } ); if ( failures > 0 ) { if ( testFailureIgnore ) { getLog().error( "There are test failures.\n\nPlease refer to " + reportsDirectory + " for the individual test results." ); } else { throw new MojoExecutionException( "There were test failures." ); } } } /** * @param classpath the test execution classpath * @param jvm the JVM process command * @param test the test to run * @throws MojoExecutionException some error occured */ private void forkToRunTest( String test ) throws MojoExecutionException { test = test.substring( 0, test.length() - 5 ); test = StringUtils.replace( test, File.separator, "." ); try { File outFile = new File(out); if (outFile.isAbsolute()) { outFile.mkdirs(); } else { new File( getProject().getBasedir(), out ).mkdirs(); } try { JavaCommand cmd = createJavaCommand() .setMainClass( MavenTestRunner.class.getName() ); if ( gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } cmd.addToClasspath( getClasspath( Artifact.SCOPE_TEST ) ); if ( !gwtSdkFirstInClasspath ) { cmd.addToClasspath( getGwtUserJar() ) .addToClasspath( getGwtDevJar() ); } addCompileSourceArtifacts( cmd ); cmd.arg( test ); cmd.systemProperty( "surefire.reports", reportsDirectory.getAbsolutePath() ); cmd.systemProperty( "gwt.args", StringUtils.escape(getGwtArgs()) ); cmd.execute(); } catch ( JavaCommandException e ) { getLog().warn("GWT test failure", e); failures++; } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to run GWT tests", e ); } } protected String getGwtArgs() { StringBuilder sb = new StringBuilder(); sb.append( "-war " ).append( quote( out ) ); sb.append( " -logLevel " ).append( quote( getLogLevel() ) ); sb.append( ( webMode || productionMode ) ? " -nodevMode" : " -devMode" ); sb.append( checkAssertions ? " -checkAssertions" : " -nocheckAssertions" ); sb.append( clusterFunctions ? " -XclusterFunctions" : " -XnoclusterFunctions" ); sb.append( disableCastChecking ? " -XnocheckCasts" : " -XcheckCasts" ); sb.append( disableClassMetadata ? " -XnoclassMetadata" : " -XclassMetadata" ); sb.append( disableRunAsync ? " -XnocodeSplitting" : " -XcodeSplitting" ); sb.append( draftCompile ? " -draftCompile" : " -nodraftCompile" ); sb.append( inlineLiteralParameters ? " -XinlineLiteralParameters" : " -XnoinlineLiteralParameters" ); sb.append( optimizeDataflow ? " -XoptimizeDataflow" : " -XnooptimizeDataflow" ); sb.append( ordinalizeEnums ? " -XordinalizeEnums" : " -XnoordinalizeEnums" ); sb.append( removeDuplicateFunctions ? " -XremoveDuplicateFunctions" : " -XnoremoveDuplicateFunctions" ); sb.append( showUi ? " -showUi" : " -noshowUi" ); sb.append( " -sourceLevel " ).append( quote( sourceLevel ) ); sb.append( " -testBeginTimeout " ).append( testBeginTimeout ); sb.append( " -testMethodTimeout ").append( testMethodTimeout ); sb.append( " -Xtries " ).append( tries ); sb.append( incremental ? " -incremental" : " -noincremental" ); if ( optimizationLevel >= 0 ) { sb.append( " -optimize " ).append( optimizationLevel ); } if ( precompile != null && !precompile.trim().isEmpty() ) { sb.append( " -precompile " ).append( quote( precompile ) ); } if ( logDir != null ) { sb.append( " -logdir " ).append( quote( logDir.getAbsolutePath() ) ); } if ( workDir != null ) { sb.append( " -workDir " ).append( quote( workDir.getAbsolutePath() ) ); } if ( namespace != null && !namespace.trim().isEmpty() ) { sb.append( " -Xnamespace " ).append( quote( namespace ) ); } if ( generateJsInteropExports ) { sb.append( " -generateJsInteropExports" ); } if ( mode.equalsIgnoreCase( "manual" ) ) { sb.append( " -runStyle Manual:1" ); } else if ( mode.equalsIgnoreCase( "htmlunit" ) ) { sb.append( " -runStyle ").append( quote( "HtmlUnit:" + htmlunit ) ); } else if ( mode.equalsIgnoreCase( "selenium" ) ) { sb.append( " -runStyle ").append( quote( "Selenium:" + selenium ) ); } else if ( !mode.trim().isEmpty() ) { sb.append( " -runStyle ").append( quote( mode ) ); } if ( userAgents != null && !userAgents.trim().isEmpty() ) { sb.append( " -userAgents " ).append( quote( userAgents ) ); } if ( batch != null && !batch.trim().isEmpty() ) { sb.append( " -batch " ).append( quote( batch ) ); } // TODO Is addArgumentDeploy(cmd) also needed to get readable test stacktraces with an alternative deploy dir? return sb.toString(); } private String quote(String arg) { return StringUtils.quoteAndEscape( arg, '"', new char[] { '"', ' ', '\t', '\r', '\n' } ); } @Override protected void postProcessClassPath( Collection<File> classpath ) { classpath.add( getClassPathElementFor( TestMojo.class ) ); classpath.add( getClassPathElementFor( ReporterManager.class ) ); } /** * @param clazz class to check for classpath resolution * @return The classpath element this class was loaded from */ private File getClassPathElementFor( Class<?> clazz ) { String classFile = clazz.getName().replace( '.', '/' ) + ".class"; ClassLoader cl = Thread.currentThread().getContextClassLoader(); if ( cl == null ) { cl = getClass().getClassLoader(); } URL url = cl.getResource( classFile ); getLog().debug( "getClassPathElementFor " + clazz.getName() + " file " + url.toString() ); String path = url.toString(); if ( path.startsWith( "jar:" ) ) { path = path.substring( 4, path.indexOf( "!" ) ); } else { path = path.substring( 0, path.length() - classFile.length() ); } if ( path.startsWith( "file:" ) ) { path = path.substring( 5 ); // windauze hack with maven 3 we get those ! path = path.replace( "%20", " " ); } File file = new File( path ); getLog().debug( "getClassPathElementFor " + clazz.getName() + " file " + file.getPath() ); return file; } /** * @param testTimeOut the testTimeOut to set */ public void setTestTimeOut( int testTimeOut ) { setTimeOut( testTimeOut ); } }