blob: a7e2317692cea1ebe7915355cd719cc0892172bd [file] [log] [blame]
package org.antlr.mojo.antlr3;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.gunit.GrammarInfo;
import org.antlr.gunit.gUnitExecutor;
import org.antlr.gunit.AbstractTest;
import org.antlr.gunit.Interp;
/**
* Takes gUnit scripts and directly performs testing.
*
* @goal gunit
*
* @phase test
* @requiresDependencyResolution test
* @requiresProject true
*
* @author Steve Ebersole
*/
public class GUnitExecuteMojo extends AbstractMojo {
public static final String ANTLR_GROUP_ID = "org.antlr";
public static final String ANTLR_ARTIFACT_NAME = "antlr";
public static final String ANTLR_RUNTIME_ARTIFACT_NAME = "antlr-runtime";
/**
* INTERNAL : The Maven Project to which we are attached
*
* @parameter expression="${project}"
* @required
*/
private MavenProject project;
/**
* INTERNAL : The artifacts associated to the dependencies defined as part
* of our configuration within the project to which we are being attached.
*
* @parameter expression="${plugin.artifacts}"
* @required
* @readonly
*/
private List<Artifact> pluginArtifacts;
/**
* Specifies the directory containing the gUnit testing files.
*
* @parameter expression="${basedir}/src/test/gunit"
* @required
*/
private File sourceDirectory;
/**
* A set of patterns for matching files from the sourceDirectory that
* should be included as gUnit source files.
*
* @parameter
*/
private Set<String> includes;
/**
* A set of exclude patterns.
*
* @parameter
*/
private Set<String> excludes;
/**
* Specifies directory to which gUnit reports should get written.
*
* @parameter expression="${basedir}/target/gunit-report"
* @required
*/
private File reportDirectory;
/**
* Should gUnit functionality be completely by-passed?
* <p>
* By default we skip gUnit tests if the user requested that all testing be skipped using 'maven.test.skip'</p>
*
* @parameter expression="${maven.test.skip}"
*/
private boolean skip;
public Set<String> getIncludePatterns() {
return includes == null || includes.isEmpty()
? Collections.singleton( "**/*.testsuite" )
: includes;
}
public Set<String> getExcludePatterns() {
return excludes == null
? Collections.<String>emptySet()
: excludes;
}
public final void execute() throws MojoExecutionException, MojoFailureException {
if ( skip ) {
getLog().info( "Skipping gUnit processing" );
return;
}
Artifact pluginAntlrArtifact = determinePluginAntlrArtifact();
validateProjectsAntlrVersion( determineArtifactVersion( pluginAntlrArtifact ) );
performExecution( determineProjectCompileScopeClassLoader( pluginAntlrArtifact ) );
}
private Artifact determinePluginAntlrArtifact() throws MojoExecutionException {
for ( Artifact artifact : pluginArtifacts ) {
boolean match = ANTLR_GROUP_ID.equals( artifact.getGroupId() )
&& ANTLR_ARTIFACT_NAME.equals( artifact.getArtifactId() );
if ( match ) {
return artifact;
}
}
throw new MojoExecutionException(
"Unexpected state : could not locate " + ANTLR_GROUP_ID + ':' + ANTLR_ARTIFACT_NAME +
" in plugin dependencies"
);
}
private ArtifactVersion determineArtifactVersion(Artifact artifact) throws MojoExecutionException {
try {
return artifact.getVersion() != null
? new DefaultArtifactVersion( artifact.getVersion() )
: artifact.getSelectedVersion();
}
catch ( OverConstrainedVersionException e ) {
throw new MojoExecutionException( "artifact [" + artifact.getId() + "] defined an overly constrained version range" );
}
}
private void validateProjectsAntlrVersion(ArtifactVersion pluginAntlrVersion) throws MojoExecutionException {
Artifact antlrArtifact = null;
Artifact antlrRuntimeArtifact = null;
if ( project.getCompileArtifacts() != null ) {
for ( Object o : project.getCompileArtifacts() ) {
final Artifact artifact = ( Artifact ) o;
if ( ANTLR_GROUP_ID.equals( artifact.getGroupId() ) ) {
if ( ANTLR_ARTIFACT_NAME.equals( artifact.getArtifactId() ) ) {
antlrArtifact = artifact;
break;
}
if ( ANTLR_RUNTIME_ARTIFACT_NAME.equals( artifact.getArtifactId() ) ) {
antlrRuntimeArtifact = artifact;
}
}
}
}
validateBuildTimeArtifact( antlrArtifact, pluginAntlrVersion );
validateRunTimeArtifact( antlrRuntimeArtifact, pluginAntlrVersion );
}
@SuppressWarnings(value = "unchecked")
protected void validateBuildTimeArtifact(Artifact antlrArtifact, ArtifactVersion pluginAntlrVersion)
throws MojoExecutionException {
if ( antlrArtifact == null ) {
validateMissingBuildtimeArtifact();
return;
}
// otherwise, lets make sure they match...
ArtifactVersion projectAntlrVersion = determineArtifactVersion( antlrArtifact );
if ( pluginAntlrVersion.compareTo( projectAntlrVersion ) != 0 ) {
getLog().warn(
"Encountered " + ANTLR_GROUP_ID + ':' + ANTLR_ARTIFACT_NAME + ':' + projectAntlrVersion.toString() +
" which did not match Antlr version used by plugin [" + pluginAntlrVersion.toString() + "]"
);
}
}
protected void validateMissingBuildtimeArtifact() {
// generally speaking, its ok for the project to not define a dep on the build-time artifact...
}
@SuppressWarnings(value = "unchecked")
protected void validateRunTimeArtifact(Artifact antlrRuntimeArtifact, ArtifactVersion pluginAntlrVersion)
throws MojoExecutionException {
if ( antlrRuntimeArtifact == null ) {
// its possible, if the project instead depends on the build-time (or full) artifact.
return;
}
ArtifactVersion projectAntlrVersion = determineArtifactVersion( antlrRuntimeArtifact );
if ( pluginAntlrVersion.compareTo( projectAntlrVersion ) != 0 ) {
getLog().warn(
"Encountered " + ANTLR_GROUP_ID + ':' + ANTLR_RUNTIME_ARTIFACT_NAME + ':' + projectAntlrVersion.toString() +
" which did not match Antlr version used by plugin [" + pluginAntlrVersion.toString() + "]"
);
}
}
/**
* Builds the classloader to pass to gUnit.
*
* @param antlrArtifact The plugin's (our) Antlr dependency artifact.
*
* @return The classloader for gUnit to use
*
* @throws MojoExecutionException Problem resolving artifacts to {@link java.net.URL urls}.
*/
private ClassLoader determineProjectCompileScopeClassLoader(Artifact antlrArtifact)
throws MojoExecutionException {
ArrayList<URL> classPathUrls = new ArrayList<URL>();
getLog().info( "Adding Antlr artifact : " + antlrArtifact.getId() );
classPathUrls.add( resolveLocalURL( antlrArtifact ) );
for ( String path : classpathElements() ) {
try {
getLog().info( "Adding project compile classpath element : " + path );
classPathUrls.add( new File( path ).toURI().toURL() );
}
catch ( MalformedURLException e ) {
throw new MojoExecutionException( "Unable to build path URL [" + path + "]" );
}
}
return new URLClassLoader( classPathUrls.toArray( new URL[classPathUrls.size()] ), getClass().getClassLoader() );
}
protected static URL resolveLocalURL(Artifact artifact) throws MojoExecutionException {
try {
return artifact.getFile().toURI().toURL();
}
catch ( MalformedURLException e ) {
throw new MojoExecutionException( "Unable to resolve artifact url : " + artifact.getId(), e );
}
}
@SuppressWarnings( "unchecked" )
private List<String> classpathElements() throws MojoExecutionException {
try {
// todo : should we combine both compile and test scoped elements?
return ( List<String> ) project.getTestClasspathElements();
}
catch ( DependencyResolutionRequiredException e ) {
throw new MojoExecutionException( "Call to Project#getCompileClasspathElements required dependency resolution" );
}
}
private void performExecution(ClassLoader projectCompileScopeClassLoader) throws MojoExecutionException {
getLog().info( "gUnit report directory : " + reportDirectory.getAbsolutePath() );
if ( !reportDirectory.exists() ) {
boolean directoryCreated = reportDirectory.mkdirs();
if ( !directoryCreated ) {
getLog().warn( "mkdirs() reported problem creating report directory" );
}
}
Result runningResults = new Result();
ArrayList<String> failureNames = new ArrayList<String>();
System.out.println();
System.out.println( "-----------------------------------------------------------" );
System.out.println( " G U N I T R E S U L T S" );
System.out.println( "-----------------------------------------------------------" );
for ( File script : collectIncludedSourceGrammars() ) {
final String scriptPath = script.getAbsolutePath();
System.out.println( "Executing script " + scriptPath );
try {
String scriptBaseName = StringUtils.chompLast( FileUtils.basename( script.getName() ), "." );
ANTLRFileStream antlrStream = new ANTLRFileStream( scriptPath );
GrammarInfo grammarInfo = Interp.parse( antlrStream );
gUnitExecutor executor = new gUnitExecutor(
grammarInfo,
projectCompileScopeClassLoader,
script.getParentFile().getAbsolutePath()
);
String report = executor.execTest();
writeReportFile( new File( reportDirectory, scriptBaseName + ".txt" ), report );
Result testResult = new Result();
testResult.tests = executor.numOfTest;
testResult.failures = executor.numOfFailure;
testResult.invalids = executor.numOfInvalidInput;
System.out.println( testResult.render() );
runningResults.add( testResult );
for ( AbstractTest test : executor.failures ) {
failureNames.add( scriptBaseName + "#" + test.getHeader() );
}
}
catch ( IOException e ) {
throw new MojoExecutionException( "Could not open specified script file", e );
}
catch ( RecognitionException e ) {
throw new MojoExecutionException( "Could not parse gUnit script", e );
}
}
System.out.println();
System.out.println( "Summary :" );
if ( ! failureNames.isEmpty() ) {
System.out.println( " Found " + failureNames.size() + " failures" );
for ( String name : failureNames ) {
System.out.println( " - " + name );
}
}
System.out.println( runningResults.render() );
System.out.println();
if ( runningResults.failures > 0 ) {
throw new MojoExecutionException( "Found gUnit test failures" );
}
if ( runningResults.invalids > 0 ) {
throw new MojoExecutionException( "Found invalid gUnit tests" );
}
}
private Set<File> collectIncludedSourceGrammars() throws MojoExecutionException {
SourceMapping mapping = new SuffixMapping( "g", Collections.<String>emptySet() );
SourceInclusionScanner scan = new SimpleSourceInclusionScanner( getIncludePatterns(), getExcludePatterns() );
scan.addSourceMapping( mapping );
try {
return scan.getIncludedSources( sourceDirectory, null );
}
catch ( InclusionScanException e ) {
throw new MojoExecutionException( "Error determining gUnit sources", e );
}
}
private void writeReportFile(File reportFile, String results) {
try {
Writer writer = new FileWriter( reportFile );
writer = new BufferedWriter( writer );
try {
writer.write( results );
writer.flush();
}
finally {
try {
writer.close();
}
catch ( IOException ignore ) {
}
}
}
catch ( IOException e ) {
getLog().warn( "Error writing gUnit report file", e );
}
}
private static class Result {
private int tests = 0;
private int failures = 0;
private int invalids = 0;
public String render() {
return String.format( "Tests run: %d, Failures: %d, Invalid: %d", tests, failures, invalids );
}
public void add(Result result) {
this.tests += result.tests;
this.failures += result.failures;
this.invalids += result.invalids;
}
}
}