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 java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
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;
/**
* Mimic surefire to run GWTTestCases during integration-test phase, until SUREFIRE-508 is fixed
*
* @goal test
* @phase integration-test
* @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a>
* @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTesting.html
* @requiresDependencyResolution test
* @version $Id: TestMojo.java 9466 2009-04-16 12:03:15Z ndeloof $
*/
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 expression="${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 expression="${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 expression="${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 expression="${maven.test.failure.ignore}"
*/
private boolean testFailureIgnore;
/**
* output directory for code generated by GWT for tests
*
* @parameter default-value="target/www-test"
*/
private String out;
/**
* run tests using web mode rather than developer (a.k.a. hosted) mode
*
* @parameter default-value=false expression="${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 default-value=false expression="${gwt.test.prod}"
*/
private boolean productionMode;
/**
* Configure test mode. Can be set to "manual", "htmlunit", "selenium" or "remoteweb".
*
* @parameter expression="${gwt.test.mode}" default-value="manual"
*/
private String mode;
/**
* Configure options to run tests with HTMLUnit. The value must descrivbe the browser emulation
* to be used, FF2, FF3, IE6, IE7, or IE8 (possible multiple values separated by comas).
*
* @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingHtmlUnit.html
* @parameter expression="${gwt.test.htmlunit}" default-value="FF3"
*/
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 expression="${gwt.test.selenium}"
*/
private String selenium;
/**
* Configure options to run tests RemoteWebBrowser. The value must describe remote web URL, like
* "rmi://myhost/ie8"
* <p>
* You must start BrowserManagerServer before running tests with this option (gwt:browser).
*
* @see http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideTestingRemoteTesting.html#Remote_Web
* @parameter expression="${gwt.test.remoteweb}"
*/
private String remoteweb;
/**
* Time out (in seconds) for test execution in dedicated JVM
*
* @parameter default-value="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 default-value="**\/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 default-value="${project.build.directory}/surefire-reports"
*/
private File reportsDirectory;
/** 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 was 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
{
new File( getProject().getBasedir(), out ).mkdirs();
try
{
new JavaCommand( MavenTestRunner.class.getName() ).withinScope( Artifact.SCOPE_TEST ).arg( test )
.systemProperty( "surefire.reports", reportsDirectory.getAbsolutePath() )
.systemProperty( "gwt.args", getGwtArgs() ).execute();
}
catch ( ForkedProcessExecutionException e )
{
failures++;
}
}
catch ( Exception e )
{
throw new MojoExecutionException( "Failed to run GWT tests", e );
}
}
protected String getGwtArgs()
{
StringBuilder sb = new StringBuilder();
sb.append( "-out " ).append( out ).append( " " );
if ( webMode )
{
sb.append( "-web " );
}
if ( productionMode )
{
sb.append( "-prod " );
}
if ( mode.equalsIgnoreCase( "manual" ) )
{
sb.append( "-runStyle Manual:1 " );
}
else if ( mode.equalsIgnoreCase( "htmlunit" ) )
{
sb.append( "-runStyle HtmlUnit:" + htmlunit );
}
else if ( mode.equalsIgnoreCase( "selenium" ) )
{
sb.append( "-runStyle Selenium:" + selenium );
}
else if ( mode.equalsIgnoreCase( "remoteweb" ) )
{
sb.append( "-runStyle RemoteWeb:" + remoteweb );
}
return sb.toString();
}
@Override
protected void postProcessClassPath( Collection<File> classpath )
throws MojoExecutionException
{
classpath.add( getClassPathElementFor( TestMojo.class ) );
classpath.add( getClassPathElementFor( ReporterManager.class ) );
try
{
// MGWT-232 olamy : add automatically gwt-dev jar in the classpath
classpath.add( getGwtDevJar() );
}
catch ( IOException ioe )
{
throw new MojoExecutionException( "Unable to write launch configuration", ioe );
}
}
/**
* @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;
}
/**
* @return the project classloader
* @throws DependencyResolutionRequiredException failed to resolve project dependencies
* @throws MalformedURLException configuration issue ?
*/
protected ClassLoader getProjectClassLoader()
throws DependencyResolutionRequiredException, MalformedURLException
{
getLog().debug( "TestMojo#getProjectClassLoader()" );
List<?> compile = getProject().getCompileClasspathElements();
URL[] urls = new URL[compile.size()];
int i = 0;
for ( Object object : compile )
{
if ( object instanceof Artifact )
{
urls[i] = ( (Artifact) object ).getFile().toURI().toURL();
}
else
{
urls[i] = new File( (String) object ).toURI().toURL();
}
i++;
}
return new URLClassLoader( urls, ClassLoader.getSystemClassLoader() );
}
/**
* @param path file to add to the project compile directories
*/
protected void addCompileSourceRoot( File path )
{
getProject().addCompileSourceRoot( path.getAbsolutePath() );
}
/**
* Add project classpath element to a classpath URL set
*
* @param originalUrls the initial URL set
* @return full classpath URL set
* @throws MojoExecutionException some error occured
*/
protected URL[] addProjectClasspathElements( URL[] originalUrls )
throws MojoExecutionException
{
Collection<?> sources = getProject().getCompileSourceRoots();
Collection<?> resources = getProject().getResources();
Collection<?> dependencies = getProject().getArtifacts();
URL[] urls = new URL[originalUrls.length + sources.size() + resources.size() + dependencies.size() + 2];
int i = originalUrls.length;
getLog().debug( "add compile source roots to GWTCompiler classpath " + sources.size() );
i = addClasspathElements( sources, urls, i );
getLog().debug( "add resources to GWTCompiler classpath " + resources.size() );
i = addClasspathElements( resources, urls, i );
getLog().debug( "add project dependencies to GWTCompiler classpath " + dependencies.size() );
i = addClasspathElements( dependencies, urls, i );
try
{
urls[i++] = getGenerateDirectory().toURI().toURL();
urls[i] = new File( getProject().getBuild().getOutputDirectory() ).toURI().toURL();
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "Failed to convert project.build.outputDirectory to URL", e );
}
return urls;
}
/**
* Need this to run both pre- and post- PLX-220 fix.
*
* @return a ClassLoader including plugin dependencies and project source foler
* @throws MojoExecutionException failed to configure ClassLoader
*/
protected ClassLoader getClassLoader()
throws MojoExecutionException
{
try
{
Collection<File> classpath = getClasspath( Artifact.SCOPE_COMPILE );
URL[] urls = new URL[classpath.size()];
int i = 0;
for ( File file : classpath )
{
urls[i++] = file.toURI().toURL();
}
ClassLoader parent = getClass().getClassLoader();
return new URLClassLoader( urls, parent.getParent() );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "Unexpecetd internal error" );
}
}
/**
* @param testTimeOut the testTimeOut to set
*/
public void setTestTimeOut( int testTimeOut )
{
setTimeOut( testTimeOut );
}
}