blob: 9cc70a51776a1fd6684d54f287fc1a68bedbf0c5 [file] [log] [blame]
package org.apache.maven.it;
/*
* 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.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import junit.framework.Assert;
import org.apache.maven.shared.utils.StringUtils;
import org.apache.maven.shared.utils.cli.CommandLineException;
import org.apache.maven.shared.utils.cli.CommandLineUtils;
import org.apache.maven.shared.utils.cli.Commandline;
import org.apache.maven.shared.utils.cli.StreamConsumer;
import org.apache.maven.shared.utils.cli.WriterStreamConsumer;
import org.apache.maven.shared.utils.io.FileUtils;
import org.apache.maven.shared.utils.io.IOUtil;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* @author Jason van Zyl
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @version $Id$
* @noinspection UseOfSystemOutOrSystemErr, RefusedBequest, UnusedDeclaration
*/
public class Verifier
{
private static final String LOG_FILENAME = "log.txt";
public String localRepo;
private final String basedir;
private final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
private final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
private PrintStream originalOut;
private PrintStream originalErr;
private List<String> cliOptions = new ArrayList<String>();
private Properties systemProperties = new Properties();
private Map<String, String> environmentVariables = new HashMap<String, String>();
private Properties verifierProperties = new Properties();
private boolean autoclean = true;
private String localRepoLayout = "default";
private boolean debug;
private Boolean forkJvm;
private String logFileName = LOG_FILENAME;
private String defaultMavenHome;
private String defaultClassworldConf;
private String defaultClasspath;
// will launch mvn with --debug
private boolean mavenDebug = false;
private String forkMode;
private boolean debugJvm = false;
private static MavenLauncher embeddedLauncher;
public Verifier( String basedir )
throws VerificationException
{
this( basedir, null );
}
public Verifier( String basedir, boolean debug )
throws VerificationException
{
this( basedir, null, debug );
}
public Verifier( String basedir, String settingsFile )
throws VerificationException
{
this( basedir, settingsFile, false );
}
public Verifier( String basedir, String settingsFile, boolean debug )
throws VerificationException
{
this( basedir, settingsFile, debug, null );
}
public Verifier( String basedir, String settingsFile, boolean debug, boolean forkJvm )
throws VerificationException
{
this( basedir, settingsFile, debug, Boolean.valueOf( forkJvm ) );
}
private Verifier( String basedir, String settingsFile, boolean debug, Boolean forkJvm )
throws VerificationException
{
this.basedir = basedir;
this.forkJvm = forkJvm;
this.forkMode = System.getProperty( "verifier.forkMode" );
if ( !debug )
{
originalOut = System.out;
originalErr = System.err;
}
setDebug( debug );
findLocalRepo( settingsFile );
findDefaultMavenHome();
if ( StringUtils.isEmpty( defaultMavenHome ) && StringUtils.isEmpty( forkMode ) )
{
forkMode = "auto";
}
}
private void findDefaultMavenHome()
throws VerificationException
{
defaultClasspath = System.getProperty( "maven.bootclasspath" );
defaultClassworldConf = System.getProperty( "classworlds.conf" );
defaultMavenHome = System.getProperty( "maven.home" );
if ( defaultMavenHome == null )
{
Properties envVars = CommandLineUtils.getSystemEnvVars();
defaultMavenHome = envVars.getProperty( "M2_HOME" );
}
if ( defaultMavenHome == null )
{
File f = new File( System.getProperty( "user.home" ), "m2" );
if ( new File( f, "bin/mvn" ).isFile() )
{
defaultMavenHome = f.getAbsolutePath();
}
}
}
public void setLocalRepo( String localRepo )
{
this.localRepo = localRepo;
}
public void resetStreams()
{
if ( !debug )
{
System.setOut( originalOut );
System.setErr( originalErr );
}
}
public void displayStreamBuffers()
{
String out = outStream.toString();
if ( out != null && out.trim().length() > 0 )
{
System.out.println( "----- Standard Out -----" );
System.out.println( out );
}
String err = errStream.toString();
if ( err != null && err.trim().length() > 0 )
{
System.err.println( "----- Standard Error -----" );
System.err.println( err );
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
public void verify( boolean chokeOnErrorOutput )
throws VerificationException
{
List<String> lines = loadFile( getBasedir(), "expected-results.txt", false );
for ( String line : lines )
{
verifyExpectedResult( line );
}
if ( chokeOnErrorOutput )
{
verifyErrorFreeLog();
}
}
public void verifyErrorFreeLog()
throws VerificationException
{
List<String> lines = loadFile( getBasedir(), getLogFileName(), false );
for ( String line : lines )
{
// A hack to keep stupid velocity resource loader errors from triggering failure
if ( line.contains( "[ERROR]" ) && !isVelocityError( line ) )
{
throw new VerificationException( "Error in execution: " + line );
}
}
}
/**
* Checks whether the specified line is just an error message from Velocity. Especially old versions of Doxia employ
* a very noisy Velocity instance.
*
* @param line The log line to check, must not be <code>null</code>.
* @return <code>true</code> if the line appears to be a Velocity error, <code>false</code> otherwise.
*/
private static boolean isVelocityError( String line )
{
return line.contains( "VM_global_library.vm" ) || line.contains( "VM #" ) && line.contains( "macro" );
}
/**
* Throws an exception if the text is not present in the log.
*
* @param text the text to assert present
* @throws VerificationException
*/
public void verifyTextInLog( String text )
throws VerificationException
{
List<String> lines = loadFile( getBasedir(), getLogFileName(), false );
boolean result = false;
for ( String line : lines )
{
if ( line.contains( text ) )
{
result = true;
break;
}
}
if ( !result )
{
throw new VerificationException( "Text not found in log: " + text );
}
}
public Properties loadProperties( String filename )
throws VerificationException
{
Properties properties = new Properties();
try
{
File propertiesFile = new File( getBasedir(), filename );
if ( propertiesFile.exists() )
{
FileInputStream fis = new FileInputStream( propertiesFile );
try
{
properties.load( fis );
}
finally
{
fis.close();
}
}
}
catch ( FileNotFoundException e )
{
throw new VerificationException( "Error reading properties file", e );
}
catch ( IOException e )
{
throw new VerificationException( "Error reading properties file", e );
}
return properties;
}
/**
* Loads the (non-empty) lines of the specified text file.
*
* @param filename The path to the text file to load, relative to the base directory, must not be <code>null</code>.
* @param encoding The character encoding of the file, may be <code>null</code> or empty to use the platform default
* encoding.
* @return The list of (non-empty) lines from the text file, can be empty but never <code>null</code>.
* @throws IOException If the file could not be loaded.
* @since 1.2
*/
public List<String> loadLines( String filename, String encoding )
throws IOException
{
List<String> lines = new ArrayList<String>();
File file = new File( getBasedir(), filename );
BufferedReader reader = null;
try
{
if ( StringUtils.isNotEmpty( encoding ) )
{
reader = new BufferedReader( new InputStreamReader( new FileInputStream( file ), encoding ) );
}
else
{
reader = new BufferedReader( new FileReader( file ) );
}
String line;
while ( ( line = reader.readLine() ) != null )
{
if ( line.length() > 0 )
{
lines.add( line );
}
}
}
finally
{
IOUtil.close( reader );
}
return lines;
}
public List<String> loadFile( String basedir, String filename, boolean hasCommand )
throws VerificationException
{
return loadFile( new File( basedir, filename ), hasCommand );
}
public List<String> loadFile( File file, boolean hasCommand )
throws VerificationException
{
List<String> lines = new ArrayList<String>();
BufferedReader reader = null;
if ( file.exists() )
{
try
{
reader = new BufferedReader( new FileReader( file ) );
String line = reader.readLine();
while ( line != null )
{
line = line.trim();
if ( !line.startsWith( "#" ) && line.length() != 0 )
{
lines.addAll( replaceArtifacts( line, hasCommand ) );
}
line = reader.readLine();
}
reader.close();
}
catch ( FileNotFoundException e )
{
throw new VerificationException( e );
}
catch ( IOException e )
{
throw new VerificationException( e );
}
finally
{
IOUtil.close( reader );
}
}
return lines;
}
private final static String MARKER = "${artifact:";
private List<String> replaceArtifacts( String line, boolean hasCommand )
{
int index = line.indexOf( MARKER );
if ( index >= 0 )
{
String newLine = line.substring( 0, index );
index = line.indexOf( "}", index );
if ( index < 0 )
{
throw new IllegalArgumentException( "line does not contain ending artifact marker: '" + line + "'" );
}
String artifact = line.substring( newLine.length() + MARKER.length(), index );
newLine += getArtifactPath( artifact );
newLine += line.substring( index + 1 );
List<String> l = new ArrayList<String>();
l.add( newLine );
int endIndex = newLine.lastIndexOf( '/' );
String command = null;
String filespec;
if ( hasCommand )
{
int startIndex = newLine.indexOf( ' ' );
command = newLine.substring( 0, startIndex );
filespec = newLine.substring( startIndex + 1, endIndex );
}
else
{
filespec = newLine;
}
File dir = new File( filespec );
addMetadataToList( dir, hasCommand, l, command );
addMetadataToList( dir.getParentFile(), hasCommand, l, command );
return l;
}
else
{
return Collections.singletonList( line );
}
}
private static void addMetadataToList( File dir, boolean hasCommand, List<String> l, String command )
{
if ( dir.exists() && dir.isDirectory() )
{
String[] files = dir.list( new FilenameFilter()
{
public boolean accept( File dir, String name )
{
return name.startsWith( "maven-metadata" ) && name.endsWith( ".xml" );
}
} );
for ( String file : files )
{
if ( hasCommand )
{
l.add( command + " " + new File( dir, file ).getPath() );
}
else
{
l.add( new File( dir, file ).getPath() );
}
}
}
}
private String getArtifactPath( String artifact )
{
StringTokenizer tok = new StringTokenizer( artifact, ":" );
if ( tok.countTokens() != 4 )
{
throw new IllegalArgumentException( "Artifact must have 4 tokens: '" + artifact + "'" );
}
String[] a = new String[4];
for ( int i = 0; i < 4; i++ )
{
a[i] = tok.nextToken();
}
String org = a[0];
String name = a[1];
String version = a[2];
String ext = a[3];
return getArtifactPath( org, name, version, ext );
}
public String getArtifactPath( String org, String name, String version, String ext )
{
return getArtifactPath( org, name, version, ext, null );
}
/**
* Returns the absolute path to the artifact denoted by groupId, artifactId, version, extension and classifier.
*
* @param gid The groupId, must not be null.
* @param aid The artifactId, must not be null.
* @param version The version, must not be null.
* @param ext The extension, must not be null.
* @param classifier The classifier, may be null to be omitted.
* @return the absolute path to the artifact denoted by groupId, artifactId, version, extension and classifier,
* never null.
*/
public String getArtifactPath( String gid, String aid, String version, String ext, String classifier )
{
if ( classifier != null && classifier.length() == 0 )
{
classifier = null;
}
if ( "maven-plugin".equals( ext ) )
{
ext = "jar";
}
if ( "coreit-artifact".equals( ext ) )
{
ext = "jar";
classifier = "it";
}
if ( "test-jar".equals( ext ) )
{
ext = "jar";
classifier = "tests";
}
String repositoryPath;
if ( "legacy".equals( localRepoLayout ) )
{
repositoryPath = gid + "/" + ext + "s/" + aid + "-" + version + "." + ext;
}
else if ( "default".equals( localRepoLayout ) )
{
repositoryPath = gid.replace( '.', '/' );
repositoryPath = repositoryPath + "/" + aid + "/" + version;
repositoryPath = repositoryPath + "/" + aid + "-" + version;
if ( classifier != null )
{
repositoryPath = repositoryPath + "-" + classifier;
}
repositoryPath = repositoryPath + "." + ext;
}
else
{
throw new IllegalStateException( "Unknown layout: " + localRepoLayout );
}
return localRepo + "/" + repositoryPath;
}
public List<String> getArtifactFileNameList( String org, String name, String version, String ext )
{
List<String> files = new ArrayList<String>();
String artifactPath = getArtifactPath( org, name, version, ext );
File dir = new File( artifactPath );
files.add( artifactPath );
addMetadataToList( dir, false, files, null );
addMetadataToList( dir.getParentFile(), false, files, null );
return files;
}
/**
* Gets the path to the local artifact metadata. Note that the method does not check whether the returned path
* actually points to existing metadata.
*
* @param gid The group id, must not be <code>null</code>.
* @param aid The artifact id, must not be <code>null</code>.
* @param version The artifact version, may be <code>null</code>.
* @return The (absolute) path to the local artifact metadata, never <code>null</code>.
*/
public String getArtifactMetadataPath( String gid, String aid, String version )
{
return getArtifactMetadataPath( gid, aid, version, "maven-metadata-local.xml" );
}
/**
* Gets the path to a file in the local artifact directory. Note that the method does not check whether the returned
* path actually points to an existing file.
*
* @param gid The group id, must not be <code>null</code>.
* @param aid The artifact id, may be <code>null</code>.
* @param version The artifact version, may be <code>null</code>.
* @param filename The filename to use, must not be <code>null</code>.
* @return The (absolute) path to the local artifact metadata, never <code>null</code>.
*/
public String getArtifactMetadataPath( String gid, String aid, String version, String filename )
{
StringBuilder buffer = new StringBuilder( 256 );
buffer.append( localRepo );
buffer.append( '/' );
if ( "default".equals( localRepoLayout ) )
{
buffer.append( gid.replace( '.', '/' ) );
buffer.append( '/' );
if ( aid != null )
{
buffer.append( aid );
buffer.append( '/' );
if ( version != null )
{
buffer.append( version );
buffer.append( '/' );
}
}
buffer.append( filename );
}
else
{
throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
}
return buffer.toString();
}
/**
* Gets the path to the local artifact metadata. Note that the method does not check whether the returned path
* actually points to existing metadata.
*
* @param gid The group id, must not be <code>null</code>.
* @param aid The artifact id, must not be <code>null</code>.
* @return The (absolute) path to the local artifact metadata, never <code>null</code>.
*/
public String getArtifactMetadataPath( String gid, String aid )
{
return getArtifactMetadataPath( gid, aid, null );
}
public void executeHook( String filename )
throws VerificationException
{
try
{
File f = new File( getBasedir(), filename );
if ( !f.exists() )
{
return;
}
List<String> lines = loadFile( f, true );
for ( String line1 : lines )
{
String line = resolveCommandLineArg( line1 );
executeCommand( line );
}
}
catch ( VerificationException e )
{
throw e;
}
catch ( Exception e )
{
throw new VerificationException( e );
}
}
private void executeCommand( String line )
throws VerificationException
{
int index = line.indexOf( " " );
String cmd;
String args = null;
if ( index >= 0 )
{
cmd = line.substring( 0, index );
args = line.substring( index + 1 );
}
else
{
cmd = line;
}
if ( "rm".equals( cmd ) )
{
System.out.println( "Removing file: " + args );
File f = new File( args );
if ( f.exists() && !f.delete() )
{
throw new VerificationException( "Error removing file - delete failed" );
}
}
else if ( "rmdir".equals( cmd ) )
{
System.out.println( "Removing directory: " + args );
try
{
File f = new File( args );
FileUtils.deleteDirectory( f );
}
catch ( IOException e )
{
throw new VerificationException( "Error removing directory - delete failed" );
}
}
else if ( "svn".equals( cmd ) )
{
launchSubversion( line, getBasedir() );
}
else
{
throw new VerificationException( "unknown command: " + cmd );
}
}
public static void launchSubversion( String line, String basedir )
throws VerificationException
{
try
{
Commandline cli = new Commandline( line );
cli.setWorkingDirectory( basedir );
Writer logWriter = new FileWriter( new File( basedir, LOG_FILENAME ) );
StreamConsumer out = new WriterStreamConsumer( logWriter );
StreamConsumer err = new WriterStreamConsumer( logWriter );
System.out.println( "Command: " + CommandLineUtils.toString( cli.getCommandline() ) );
int ret = CommandLineUtils.executeCommandLine( cli, out, err );
logWriter.close();
if ( ret > 0 )
{
System.err.println( "Exit code: " + ret );
throw new VerificationException();
}
}
catch ( CommandLineException e )
{
throw new VerificationException( e );
}
catch ( IOException e )
{
throw new VerificationException( e );
}
}
private static String retrieveLocalRepo( String settingsXmlPath )
throws VerificationException
{
UserModelReader userModelReader = new UserModelReader();
String userHome = System.getProperty( "user.home" );
File userXml;
String repo = null;
if ( settingsXmlPath != null )
{
System.out.println( "Using settings from " + settingsXmlPath );
userXml = new File( settingsXmlPath );
}
else
{
userXml = new File( userHome, ".m2/settings.xml" );
}
if ( userXml.exists() )
{
userModelReader.parse( userXml );
String localRepository = userModelReader.getLocalRepository();
if ( localRepository != null )
{
repo = new File( localRepository ).getAbsolutePath();
}
}
return repo;
}
public void deleteArtifact( String org, String name, String version, String ext )
throws IOException
{
List<String> files = getArtifactFileNameList( org, name, version, ext );
for ( String fileName : files )
{
FileUtils.forceDelete( new File( fileName ) );
}
}
/**
* Deletes all artifacts in the specified group id from the local repository.
*
* @param gid The group id whose artifacts should be deleted, must not be <code>null</code>.
* @throws IOException If the artifacts could not be deleted.
* @since 1.2
*/
public void deleteArtifacts( String gid )
throws IOException
{
String path;
if ( "default".equals( localRepoLayout ) )
{
path = gid.replace( '.', '/' );
}
else if ( "legacy".equals( localRepoLayout ) )
{
path = gid;
}
else
{
throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
}
FileUtils.deleteDirectory( new File( localRepo, path ) );
}
/**
* Deletes all artifacts in the specified g:a:v from the local repository.
*
* @param gid The group id whose artifacts should be deleted, must not be <code>null</code>.
* @param aid The artifact id whose artifacts should be deleted, must not be <code>null</code>.
* @param version The (base) version whose artifacts should be deleted, must not be <code>null</code>.
* @throws IOException If the artifacts could not be deleted.
* @since 1.3
*/
public void deleteArtifacts( String gid, String aid, String version )
throws IOException
{
String path;
if ( "default".equals( localRepoLayout ) )
{
path = gid.replace( '.', '/' ) + '/' + aid + '/' + version;
}
else
{
throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
}
FileUtils.deleteDirectory( new File( localRepo, path ) );
}
/**
* Deletes the specified directory.
*
* @param path The path to the directory to delete, relative to the base directory, must not be <code>null</code>.
* @throws IOException If the directory could not be deleted.
* @since 1.2
*/
public void deleteDirectory( String path )
throws IOException
{
FileUtils.deleteDirectory( new File( getBasedir(), path ) );
}
/**
* Writes a text file with the specified contents. The contents will be encoded using UTF-8.
*
* @param path The path to the file, relative to the base directory, must not be <code>null</code>.
* @param contents The contents to write, must not be <code>null</code>.
* @throws IOException If the file could not be written.
* @since 1.2
*/
public void writeFile( String path, String contents )
throws IOException
{
FileUtils.fileWrite( new File( getBasedir(), path ).getAbsolutePath(), "UTF-8", contents );
}
/**
* Filters a text file by replacing some user-defined tokens.
*
* @param srcPath The path to the input file, relative to the base directory, must not be <code>null</code>.
* @param dstPath The path to the output file, relative to the base directory and possibly equal to the input file,
* must not be <code>null</code>.
* @param fileEncoding The file encoding to use, may be <code>null</code> or empty to use the platform's default
* encoding.
* @param filterProperties The mapping from tokens to replacement values, must not be <code>null</code>.
* @return The path to the filtered output file, never <code>null</code>.
* @throws IOException If the file could not be filtered.
* @since 1.2
*/
public File filterFile( String srcPath, String dstPath, String fileEncoding, Map<String, String> filterProperties )
throws IOException
{
File srcFile = new File( getBasedir(), srcPath );
String data = FileUtils.fileRead( srcFile, fileEncoding );
for ( String token : filterProperties.keySet() )
{
String value = String.valueOf( filterProperties.get( token ) );
data = StringUtils.replace( data, token, value );
}
File dstFile = new File( getBasedir(), dstPath );
//noinspection ResultOfMethodCallIgnored
dstFile.getParentFile().mkdirs();
FileUtils.fileWrite( dstFile.getPath(), fileEncoding, data );
return dstFile;
}
/**
* There are 226 references to this method in Maven core ITs. In most (all?) cases it is used together with
* {@link #newDefaultFilterProperties()}. Need to remove both methods and update all clients eventually/
*
* @deprecated use {@link #filterFile(String, String, String, Map)}
*/
@SuppressWarnings( { "rawtypes", "unchecked" } )
public File filterFile( String srcPath, String dstPath, String fileEncoding, Properties filterProperties )
throws IOException
{
return filterFile( srcPath, dstPath, fileEncoding, (Map) filterProperties );
}
/**
* Gets a new copy of the default filter properties. These default filter properties map the tokens "@basedir@" and
* "@baseurl@" to the test's base directory and its base <code>file:</code> URL, respectively.
*
* @return The (modifiable) map with the default filter properties, never <code>null</code>.
* @since 1.2
*/
public Properties newDefaultFilterProperties()
{
Properties filterProperties = new Properties();
String basedir = new File( getBasedir() ).getAbsolutePath();
filterProperties.put( "@basedir@", basedir );
/*
* NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here
* and just do it the simple way.
*/
String baseurl = basedir;
if ( !baseurl.startsWith( "/" ) )
{
baseurl = '/' + baseurl;
}
baseurl = "file://" + baseurl.replace( '\\', '/' );
filterProperties.put( "@baseurl@", baseurl );
return filterProperties;
}
public void assertFilePresent( String file )
{
try
{
verifyExpectedResult( file, true );
}
catch ( VerificationException e )
{
Assert.fail( e.getMessage() );
}
}
/**
* Check that given file's content matches an regular expression. Note this method also checks that the file exists
* and is readable.
*
* @param file the file to check.
* @param regex a regular expression.
* @see Pattern
*/
public void assertFileMatches( String file, String regex )
{
assertFilePresent( file );
try
{
String content = FileUtils.fileRead( file );
if ( !Pattern.matches( regex, content ) )
{
Assert.fail( "Content of " + file + " does not match " + regex );
}
}
catch ( IOException e )
{
Assert.fail( e.getMessage() );
}
}
public void assertFileNotPresent( String file )
{
try
{
verifyExpectedResult( file, false );
}
catch ( VerificationException e )
{
Assert.fail( e.getMessage() );
}
}
private void verifyArtifactPresence( boolean wanted, String org, String name, String version, String ext )
{
List<String> files = getArtifactFileNameList( org, name, version, ext );
for ( String fileName : files )
{
try
{
verifyExpectedResult( fileName, wanted );
}
catch ( VerificationException e )
{
Assert.fail( e.getMessage() );
}
}
}
public void assertArtifactPresent( String org, String name, String version, String ext )
{
verifyArtifactPresence( true, org, name, version, ext );
}
public void assertArtifactNotPresent( String org, String name, String version, String ext )
{
verifyArtifactPresence( false, org, name, version, ext );
}
private void verifyExpectedResult( String line )
throws VerificationException
{
boolean wanted = true;
if ( line.startsWith( "!" ) )
{
line = line.substring( 1 );
wanted = false;
}
verifyExpectedResult( line, wanted );
}
private void verifyExpectedResult( String line, boolean wanted )
throws VerificationException
{
if ( line.indexOf( "!/" ) > 0 )
{
String urlString = "jar:file:" + getBasedir() + "/" + line;
InputStream is = null;
try
{
URL url = new URL( urlString );
is = url.openStream();
if ( is == null )
{
if ( wanted )
{
throw new VerificationException( "Expected JAR resource was not found: " + line );
}
}
else
{
if ( !wanted )
{
throw new VerificationException( "Unwanted JAR resource was found: " + line );
}
}
}
catch ( MalformedURLException e )
{
throw new VerificationException( "Error looking for JAR resource", e );
}
catch ( IOException e )
{
if ( wanted )
{
throw new VerificationException( "Error looking for JAR resource: " + line );
}
}
finally
{
if ( is != null )
{
try
{
is.close();
}
catch ( IOException e )
{
System.err.println( "WARN: error closing stream: " + e );
}
}
}
}
else
{
File expectedFile = new File( line );
// NOTE: On Windows, a path with a leading (back-)slash is relative to the current drive
if ( !expectedFile.isAbsolute() && !expectedFile.getPath().startsWith( File.separator ) )
{
expectedFile = new File( getBasedir(), line );
}
if ( line.indexOf( '*' ) > -1 )
{
File parent = expectedFile.getParentFile();
if ( !parent.exists() )
{
if ( wanted )
{
throw new VerificationException(
"Expected file pattern was not found: " + expectedFile.getPath() );
}
}
else
{
String shortNamePattern = expectedFile.getName().replaceAll( "\\*", ".*" );
String[] candidates = parent.list();
boolean found = false;
if ( candidates != null )
{
for ( String candidate : candidates )
{
if ( candidate.matches( shortNamePattern ) )
{
found = true;
break;
}
}
}
if ( !found && wanted )
{
throw new VerificationException(
"Expected file pattern was not found: " + expectedFile.getPath() );
}
else if ( found && !wanted )
{
throw new VerificationException( "Unwanted file pattern was found: " + expectedFile.getPath() );
}
}
}
else
{
if ( !expectedFile.exists() )
{
if ( wanted )
{
throw new VerificationException( "Expected file was not found: " + expectedFile.getPath() );
}
}
else
{
if ( !wanted )
{
throw new VerificationException( "Unwanted file was found: " + expectedFile.getPath() );
}
}
}
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
public void executeGoal( String goal )
throws VerificationException
{
executeGoal( goal, environmentVariables );
}
public void executeGoal( String goal, Map<String, String> envVars )
throws VerificationException
{
executeGoals( Arrays.asList( goal ), envVars );
}
public void executeGoals( List<String> goals )
throws VerificationException
{
executeGoals( goals, environmentVariables );
}
public String getExecutable()
{
// Use a strategy for finding the maven executable, John has a simple method like this
// but a little strategy + chain of command would be nicer.
String mavenHome = defaultMavenHome;
if ( mavenHome != null )
{
return mavenHome + "/bin/mvn";
}
else
{
File f = new File( System.getProperty( "user.home" ), "m2/bin/mvn" );
if ( f.exists() )
{
return f.getAbsolutePath();
}
else
{
return "mvn";
}
}
}
public void executeGoals( List<String> goals, Map<String, String> envVars )
throws VerificationException
{
List<String> allGoals = new ArrayList<String>();
if ( autoclean )
{
/*
* NOTE: Neither test lifecycle binding nor prefix resolution here but call the goal directly.
*/
allGoals.add( "org.apache.maven.plugins:maven-clean-plugin:clean" );
}
allGoals.addAll( goals );
List<String> args = new ArrayList<String>();
int ret;
File logFile = new File( getBasedir(), getLogFileName() );
for ( Object cliOption : cliOptions )
{
String key = String.valueOf( cliOption );
String resolvedArg = resolveCommandLineArg( key );
try
{
args.addAll( Arrays.asList( CommandLineUtils.translateCommandline( resolvedArg ) ) );
}
catch ( Exception e )
{
e.printStackTrace();
}
}
args.add( "-e" );
args.add( "--batch-mode" );
if ( this.mavenDebug )
{
args.add( "--debug" );
}
for ( Object o : systemProperties.keySet() )
{
String key = (String) o;
String value = systemProperties.getProperty( key );
args.add( "-D" + key + "=" + value );
}
/*
* NOTE: Unless explicitly requested by the caller, the forked builds should use the current local
* repository. Otherwise, the forked builds would in principle leave the sandbox environment which has been
* setup for the current build. In particular, using "maven.repo.local" will make sure the forked builds use
* the same local repo as the parent build even if a custom user settings is provided.
*/
boolean useMavenRepoLocal = Boolean.valueOf( verifierProperties.getProperty( "use.mavenRepoLocal", "true" ) );
if ( useMavenRepoLocal )
{
args.add( "-Dmaven.repo.local=" + localRepo );
}
args.addAll( allGoals );
try
{
String[] cliArgs = args.toArray( new String[args.size()] );
MavenLauncher launcher = getMavenLauncher( envVars );
ret = launcher.run( cliArgs, getBasedir(), logFile );
}
catch ( LauncherException e )
{
throw new VerificationException( "Failed to execute Maven: " + e.getMessage(), e );
}
catch ( IOException e )
{
throw new VerificationException( e );
}
if ( ret > 0 )
{
System.err.println( "Exit code: " + ret );
throw new VerificationException(
"Exit code was non-zero: " + ret + "; command line and log = \n" + new File( defaultMavenHome,
"bin/mvn" ) + " "
+ StringUtils.join( args.iterator(), " " ) + "\n" + getLogContents( logFile ) );
}
}
private MavenLauncher getMavenLauncher( Map<String, String> envVars )
throws LauncherException
{
boolean fork;
if ( forkJvm != null )
{
fork = forkJvm;
}
else if ( ( envVars.isEmpty() && "auto".equalsIgnoreCase( forkMode ) )
|| "embedded".equalsIgnoreCase( forkMode ) )
{
fork = false;
try
{
initEmbeddedLauncher();
}
catch ( Exception e )
{
fork = true;
}
}
else
{
fork = true;
}
if ( !fork )
{
if ( !envVars.isEmpty() )
{
throw new LauncherException( "Environment variables are not supported in embedded runtime" );
}
initEmbeddedLauncher();
return embeddedLauncher;
}
else
{
return new ForkedLauncher( defaultMavenHome, envVars, debugJvm );
}
}
private void initEmbeddedLauncher()
throws LauncherException
{
if ( embeddedLauncher == null )
{
if ( StringUtils.isEmpty( defaultMavenHome ) )
{
embeddedLauncher = Embedded3xLauncher.createFromClasspath();
}
else
{
embeddedLauncher =
Embedded3xLauncher.createFromMavenHome( defaultMavenHome, defaultClassworldConf, getClasspath() );
}
}
}
private List<URL> getClasspath()
throws LauncherException
{
if ( defaultClasspath == null )
{
return null;
}
ArrayList<URL> classpath = new ArrayList<URL>();
StringTokenizer st = new StringTokenizer( defaultClasspath, File.pathSeparator );
while ( st.hasMoreTokens() )
{
try
{
classpath.add( new File( st.nextToken() ).toURI().toURL() );
}
catch ( MalformedURLException e )
{
throw new LauncherException( "Invalid launcher classpath " + defaultClasspath, e );
}
}
return classpath;
}
public String getMavenVersion()
throws VerificationException
{
try
{
return getMavenLauncher( Collections.<String, String> emptyMap() ).getMavenVersion();
}
catch ( LauncherException e )
{
throw new VerificationException( e );
}
catch ( IOException e )
{
throw new VerificationException( e );
}
}
private static String getLogContents( File logFile )
{
try
{
return FileUtils.fileRead( logFile );
}
catch ( IOException e )
{
// ignore
return "(Error reading log contents: " + e.getMessage() + ")";
}
}
private String resolveCommandLineArg( String key )
{
String result = key.replaceAll( "\\$\\{basedir\\}", getBasedir() );
if ( result.contains( "\\\\" ) )
{
result = result.replaceAll( "\\\\", "\\" );
}
result = result.replaceAll( "\\/\\/", "\\/" );
return result;
}
private static List<String> discoverIntegrationTests( String directory )
throws VerificationException
{
try
{
ArrayList<String> tests = new ArrayList<String>();
List<File> subTests = FileUtils.getFiles( new File( directory ), "**/goals.txt", null );
for ( File testCase : subTests )
{
tests.add( testCase.getParent() );
}
return tests;
}
catch ( IOException e )
{
throw new VerificationException( directory + " is not a valid test case container", e );
}
}
private void displayLogFile()
{
System.out.println( "Log file contents:" );
BufferedReader reader = null;
try
{
reader = new BufferedReader( new FileReader( new File( getBasedir(), getLogFileName() ) ) );
String line = reader.readLine();
while ( line != null )
{
System.out.println( line );
line = reader.readLine();
}
reader.close();
}
catch ( FileNotFoundException e )
{
System.err.println( "Error: " + e );
}
catch ( IOException e )
{
System.err.println( "Error: " + e );
}
finally
{
IOUtil.close( reader );
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
public static void main( String args[] )
throws VerificationException
{
String basedir = System.getProperty( "user.dir" );
List<String> tests = null;
List<String> argsList = new ArrayList<String>();
String settingsFile = null;
// skip options
for ( int i = 0; i < args.length; i++ )
{
if ( args[i].startsWith( "-D" ) )
{
int index = args[i].indexOf( "=" );
if ( index >= 0 )
{
System.setProperty( args[i].substring( 2, index ), args[i].substring( index + 1 ) );
}
else
{
System.setProperty( args[i].substring( 2 ), "true" );
}
}
else if ( "-s".equals( args[i] ) || "--settings".equals( args[i] ) )
{
if ( i == args.length - 1 )
{
// should have been detected before
throw new IllegalStateException( "missing argument to -s" );
}
i += 1;
settingsFile = args[i];
}
else if ( args[i].startsWith( "-" ) )
{
System.out.println( "skipping unrecognised argument: " + args[i] );
}
else
{
argsList.add( args[i] );
}
}
if ( argsList.size() == 0 )
{
if ( FileUtils.fileExists( basedir + File.separator + "integration-tests.txt" ) )
{
try
{
tests = FileUtils.loadFile( new File( basedir, "integration-tests.txt" ) );
}
catch ( IOException e )
{
System.err.println( "Unable to load integration tests file" );
System.err.println( e.getMessage() );
System.exit( 2 );
}
}
else
{
tests = discoverIntegrationTests( "." );
}
}
else
{
tests = new ArrayList<String>( argsList.size() );
NumberFormat fmt = new DecimalFormat( "0000" );
for ( String test : argsList )
{
if ( test.endsWith( "," ) )
{
test = test.substring( 0, test.length() - 1 );
}
if ( StringUtils.isNumeric( test ) )
{
test = "it" + fmt.format( Integer.valueOf( test ) );
tests.add( test.trim() );
}
else if ( "it".startsWith( test ) )
{
test = test.trim();
if ( test.length() > 0 )
{
tests.add( test );
}
}
else if ( FileUtils.fileExists( test ) && new File( test ).isDirectory() )
{
tests.addAll( discoverIntegrationTests( test ) );
}
else
{
System.err.println(
"[WARNING] rejecting " + test + " as an invalid test or test source directory" );
}
}
}
if ( tests.size() == 0 )
{
System.out.println( "No tests to run" );
}
int exitCode = 0;
List<String> failed = new ArrayList<String>();
for ( String test : tests )
{
System.out.print( test + "... " );
String dir = basedir + "/" + test;
if ( !new File( dir, "goals.txt" ).exists() )
{
System.err.println( "Test " + test + " in " + dir + " does not exist" );
System.exit( 2 );
}
Verifier verifier = new Verifier( dir );
verifier.findLocalRepo( settingsFile );
System.out.println( "Using default local repository: " + verifier.localRepo );
try
{
runIntegrationTest( verifier );
}
catch ( Throwable e )
{
verifier.resetStreams();
System.out.println( "FAILED" );
verifier.displayStreamBuffers();
System.out.println( ">>>>>> Error Stacktrace:" );
e.printStackTrace( System.out );
System.out.println( "<<<<<< Error Stacktrace" );
verifier.displayLogFile();
exitCode = 1;
failed.add( test );
}
}
System.out.println( tests.size() - failed.size() + "/" + tests.size() + " passed" );
if ( !failed.isEmpty() )
{
System.out.println( "Failed tests: " + failed );
}
System.exit( exitCode );
}
private void findLocalRepo( String settingsFile )
throws VerificationException
{
if ( localRepo == null )
{
localRepo = System.getProperty( "maven.repo.local" );
}
if ( localRepo == null )
{
localRepo = retrieveLocalRepo( settingsFile );
}
if ( localRepo == null )
{
localRepo = System.getProperty( "user.home" ) + "/.m2/repository";
}
File repoDir = new File( localRepo );
if ( !repoDir.exists() )
{
//noinspection ResultOfMethodCallIgnored
repoDir.mkdirs();
}
// normalize path
localRepo = repoDir.getAbsolutePath();
localRepoLayout = System.getProperty( "maven.repo.local.layout", "default" );
}
private static void runIntegrationTest( Verifier verifier )
throws VerificationException
{
verifier.executeHook( "prebuild-hook.txt" );
Properties properties = verifier.loadProperties( "system.properties" );
Properties controlProperties = verifier.loadProperties( "verifier.properties" );
boolean chokeOnErrorOutput = Boolean.valueOf( controlProperties.getProperty( "failOnErrorOutput", "true" ) );
List<String> goals = verifier.loadFile( verifier.getBasedir(), "goals.txt", false );
List<String> cliOptions = verifier.loadFile( verifier.getBasedir(), "cli-options.txt", false );
verifier.setCliOptions( cliOptions );
verifier.setSystemProperties( properties );
verifier.setVerifierProperties( controlProperties );
verifier.executeGoals( goals );
verifier.executeHook( "postbuild-hook.txt" );
System.out.println( "*** Verifying: fail when [ERROR] detected? " + chokeOnErrorOutput + " ***" );
verifier.verify( chokeOnErrorOutput );
verifier.resetStreams();
System.out.println( "OK" );
}
public void assertArtifactContents( String org, String artifact, String version, String type, String contents )
throws IOException
{
String fileName = getArtifactPath( org, artifact, version, type );
Assert.assertEquals( contents, FileUtils.fileRead( fileName ) );
}
static class UserModelReader
extends DefaultHandler
{
private String localRepository;
private StringBuffer currentBody = new StringBuffer();
public void parse( File file )
throws VerificationException
{
try
{
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
SAXParser parser = saxFactory.newSAXParser();
InputSource is = new InputSource( new FileInputStream( file ) );
parser.parse( is, this );
}
catch ( FileNotFoundException e )
{
throw new VerificationException( "file not found path : " + file.getAbsolutePath(), e );
}
catch ( IOException e )
{
throw new VerificationException( " IOException path : " + file.getAbsolutePath(), e );
}
catch ( ParserConfigurationException e )
{
throw new VerificationException( e );
}
catch ( SAXException e )
{
throw new VerificationException( "Parsing exception for file " + file.getAbsolutePath(), e );
}
}
public void warning( SAXParseException spe )
{
printParseError( "Warning", spe );
}
public void error( SAXParseException spe )
{
printParseError( "Error", spe );
}
public void fatalError( SAXParseException spe )
{
printParseError( "Fatal Error", spe );
}
private void printParseError( String type, SAXParseException spe )
{
System.err.println(
type + " [line " + spe.getLineNumber() + ", row " + spe.getColumnNumber() + "]: " + spe.getMessage() );
}
public String getLocalRepository()
{
return localRepository;
}
public void characters( char[] ch, int start, int length )
throws SAXException
{
currentBody.append( ch, start, length );
}
public void endElement( String uri, String localName, String rawName )
throws SAXException
{
if ( "localRepository".equals( rawName ) )
{
if ( notEmpty( currentBody.toString() ) )
{
localRepository = currentBody.toString().trim();
}
else
{
throw new SAXException(
"Invalid mavenProfile entry. Missing one or more " + "fields: {localRepository}." );
}
}
currentBody = new StringBuffer();
}
private boolean notEmpty( String test )
{
return test != null && test.trim().length() > 0;
}
public void reset()
{
currentBody = null;
localRepository = null;
}
}
public List<String> getCliOptions()
{
return cliOptions;
}
public void setCliOptions( List<String> cliOptions )
{
this.cliOptions = cliOptions;
}
public void addCliOption( String option )
{
cliOptions.add( option );
}
public Properties getSystemProperties()
{
return systemProperties;
}
public void setSystemProperties( Properties systemProperties )
{
this.systemProperties = systemProperties;
}
public void setSystemProperty( String key, String value )
{
if ( value != null )
{
systemProperties.setProperty( key, value );
}
else
{
systemProperties.remove( key );
}
}
public Map<String, String> getEnvironmentVariables()
{
return environmentVariables;
}
public void setEnvironmentVariables( Map<String, String> environmentVariables )
{
this.environmentVariables = environmentVariables;
}
public void setEnvironmentVariable( String key, String value )
{
if ( value != null )
{
environmentVariables.put( key, value );
}
else
{
environmentVariables.remove( key );
}
}
public Properties getVerifierProperties()
{
return verifierProperties;
}
public void setVerifierProperties( Properties verifierProperties )
{
this.verifierProperties = verifierProperties;
}
public boolean isAutoclean()
{
return autoclean;
}
public void setAutoclean( boolean autoclean )
{
this.autoclean = autoclean;
}
public String getBasedir()
{
return basedir;
}
/**
* Gets the name of the file used to log build output.
*
* @return The name of the log file, relative to the base directory, never <code>null</code>.
* @since 1.2
*/
public String getLogFileName()
{
return this.logFileName;
}
/**
* Sets the name of the file used to log build output.
*
* @param logFileName The name of the log file, relative to the base directory, must not be empty or
* <code>null</code>.
* @since 1.2
*/
public void setLogFileName( String logFileName )
{
if ( StringUtils.isEmpty( logFileName ) )
{
throw new IllegalArgumentException( "log file name unspecified" );
}
this.logFileName = logFileName;
}
public void setDebug( boolean debug )
{
this.debug = debug;
if ( !debug )
{
System.setOut( new PrintStream( outStream ) );
System.setErr( new PrintStream( errStream ) );
}
}
public boolean isMavenDebug()
{
return mavenDebug;
}
public void setMavenDebug( boolean mavenDebug )
{
this.mavenDebug = mavenDebug;
}
public void setForkJvm( boolean forkJvm )
{
this.forkJvm = forkJvm;
}
public boolean isDebugJvm()
{
return debugJvm;
}
public void setDebugJvm( boolean debugJvm )
{
this.debugJvm = debugJvm;
}
public String getLocalRepoLayout()
{
return localRepoLayout;
}
public void setLocalRepoLayout( String localRepoLayout )
{
this.localRepoLayout = localRepoLayout;
}
}