blob: f4e7ef1b716902eac3f4dec2bae54149346d3eda [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.buildcache.its.junit;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.apache.maven.buildcache.CacheUtils;
import org.apache.maven.it.VerificationException;
import org.apache.maven.it.Verifier;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import org.junit.jupiter.api.parallel.ResourceLock;
import org.junit.jupiter.api.parallel.Resources;
@ResourceLock( Resources.SYSTEM_PROPERTIES )
public class IntegrationTestExtension implements BeforeAllCallback, TestTemplateInvocationContextProvider
{
private static boolean initialized;
private static Path maven3;
private static Path maven4;
@Override
public void beforeAll( ExtensionContext context ) throws Exception
{
buildMaven();
}
@Override
public boolean supportsTestTemplate( ExtensionContext context )
{
return context.getTestMethod()
.filter( m -> m.isAnnotationPresent( Test.class ) )
.isPresent();
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
ExtensionContext extensionContext )
{
Method m = extensionContext.getRequiredTestMethod();
return Stream.of( maven3, maven4 ).map( p -> new MavenTemplate( m, p ) );
}
private static void buildMaven() throws Exception
{
if ( initialized )
{
return;
}
String root = Objects.requireNonNull( System.getProperty( "maven.multiModuleProjectDirectory" ),
"The 'maven.multiModuleProjectDirectory' system property need to be set" );
// maven3
Path maven3Zip = Paths.get( root, "maven/maven3/apache-maven/target/apache-maven-bin.zip" );
if ( !Files.exists( maven3Zip ) )
{
throw new IllegalStateException( "Unable to find " + maven3Zip + "\n"
+ "Please build the maven3 and maven4 distributions using the build-maven.sh script" );
}
Path outMaven3 = Paths.get( "target/maven3" );
deleteDir( outMaven3 );
Files.createDirectories( outMaven3 );
CacheUtils.unzip( maven3Zip, outMaven3 );
maven3 = outMaven3.resolve( "apache-maven" ).toAbsolutePath();
maven3.resolve( "bin/mvn" ).toFile().setExecutable( true );
// maven4
Path maven4Zip = Paths.get( root, "maven/maven4/apache-maven/target/apache-maven-bin.zip" );
if ( !Files.exists( maven4Zip ) )
{
throw new IllegalStateException( "Unable to find " + maven4Zip + "\n"
+ "Please build the maven3 and maven4 distributions using the build-maven.sh script" );
}
Path outMaven4 = Paths.get( "target/maven4" );
deleteDir( outMaven4 );
Files.createDirectories( outMaven4 );
CacheUtils.unzip( maven4Zip, outMaven4 );
maven4 = outMaven4.resolve( "apache-maven" ).toAbsolutePath();
maven4.resolve( "bin/mvn" ).toFile().setExecutable( true );
initialized = true;
}
public static Path deleteDir( Path dir )
{
return deleteDir( dir, true );
}
public static Path deleteDir( Path dir, boolean failOnError )
{
if ( Files.exists( dir ) )
{
try ( Stream<Path> files = Files.walk( dir ) )
{
files.sorted( Comparator.reverseOrder() ).forEach( f -> deleteFile( f, failOnError ) );
}
catch ( Exception e )
{
throw new RuntimeException( "Could not walk " + dir, e );
}
}
return dir;
}
private static void deleteFile( Path f, boolean failOnError )
{
try
{
Files.delete( f );
}
catch ( Exception e )
{
if ( failOnError )
{
throw new RuntimeException( "Could not delete " + f, e );
}
else
{
System.err.println( "Error deleting " + f + ": " + e );
}
}
}
public static class MavenTemplate implements TestTemplateInvocationContext
{
private final Method method;
private final Path mavenPath;
public MavenTemplate( Method method, Path mavenPath )
{
this.method = method;
this.mavenPath = mavenPath;
}
@Override
public String getDisplayName( int invocationIndex )
{
return mavenPath == maven3 ? "[maven3]" : "[maven4]";
}
@Override
public List<Extension> getAdditionalExtensions()
{
return Arrays.asList( (TestInstancePostProcessor) this::postProcessTestInstance,
new VerifierParameterResolver() );
}
protected void postProcessTestInstance( Object testInstance, ExtensionContext context ) throws Exception
{
if ( !context.getTestMethod().isPresent() )
{
return;
}
final Class<?> testClass = context.getRequiredTestClass();
final IntegrationTest test = testClass.getAnnotation( IntegrationTest.class );
final String rawProjectDir = test.value();
final String className = context.getRequiredTestClass().getSimpleName();
String methodName = context.getRequiredTestMethod().getName();
if ( rawProjectDir == null )
{
throw new IllegalStateException( "@IntegrationTest must be set" );
}
final Path testDir = Paths.get( "target/maven-tests/" + className + "/" + methodName + "/"
+ ( mavenPath == maven3 ? "maven3" : "maven4" ) ).toAbsolutePath();
deleteDir( testDir );
Files.createDirectories( testDir );
final Path testExecutionDir;
final Path testSrcDir = Paths.get( rawProjectDir ).toAbsolutePath().normalize();
if ( !Files.exists( testSrcDir ) )
{
throw new IllegalStateException( "@IntegrationTest(\"" + testSrcDir
+ "\") points at a path that does not exist: " + testSrcDir );
}
testExecutionDir = testDir.resolve( "project" );
try ( Stream<Path> files = Files.walk( testSrcDir ) )
{
files.forEach( source ->
{
final Path dest = testExecutionDir.resolve( testSrcDir.relativize( source ) );
try
{
if ( Files.isDirectory( source ) )
{
Files.createDirectories( dest );
}
else
{
Files.createDirectories( dest.getParent() );
Files.copy( source, dest );
}
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
} );
}
}
private class VerifierParameterResolver implements ParameterResolver
{
@Override
public boolean supportsParameter( ParameterContext parameterContext,
ExtensionContext extensionContext )
throws ParameterResolutionException
{
return parameterContext.getParameter().getType() == Verifier.class;
}
@Override
public Object resolveParameter( ParameterContext parameterContext,
ExtensionContext context )
throws ParameterResolutionException
{
String prevMavenHome = System.getProperty( "maven.home" );
try
{
final IntegrationTest test = context.getRequiredTestClass().getAnnotation( IntegrationTest.class );
final String rawProjectDir = test.value();
if ( rawProjectDir == null )
{
throw new IllegalStateException( "value of @IntegrationTest must be set" );
}
final String className = context.getRequiredTestClass().getSimpleName();
String methodName = context.getRequiredTestMethod().getName();
final Path testDir = Paths.get( "target/mvnd-tests/" + className + "/" + methodName + "/"
+ ( mavenPath == maven3 ? "maven3" : "maven4" ) ).toAbsolutePath();
deleteDir( testDir );
Files.createDirectories( testDir );
final Path testSrcDir = Paths.get( rawProjectDir ).toAbsolutePath().normalize();
if ( !Files.exists( testSrcDir ) )
{
throw new IllegalStateException( "@IntegrationTest(\"" + testSrcDir
+ "\") points at a path that does not exist: " + testSrcDir );
}
final Path testExecutionDir = testDir.resolve( "project" );
try ( Stream<Path> files = Files.walk( testSrcDir ) )
{
files.forEach( source ->
{
final Path dest = testExecutionDir.resolve( testSrcDir.relativize( source ) );
try
{
if ( Files.isDirectory( source ) )
{
Files.createDirectories( dest );
}
else
{
Files.createDirectories( dest.getParent() );
Files.copy( source, dest );
}
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
} );
}
System.setProperty( "maven.home", mavenPath.toString() );
Verifier verifier = new Verifier( testExecutionDir.toString() );
verifier.setLogFileName( "../log.txt" );
return verifier;
}
catch ( VerificationException | IOException e )
{
throw new ParameterResolutionException( "Unable to create Verifier", e );
}
finally
{
if ( prevMavenHome != null )
{
System.setProperty( "maven.home", prevMavenHome );
}
else
{
System.clearProperty( "maven.home" );
}
}
}
}
}
}