#667 Run all processor tests with both the JDK compiler and the Eclipse compiler.

All generated sources and compilation results are kept in a new directory structure, making manual inspection easier, and also simplifying parallel test-class execution.
This commit is contained in:
Andreas Gudian 2015-10-22 20:41:21 +02:00
parent af9b54fa4f
commit aba26328ba
22 changed files with 714 additions and 304 deletions

View File

@ -64,7 +64,7 @@
<dependency> <dependency>
<groupId>org.eclipse.tycho</groupId> <groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId> <artifactId>tycho-compiler-jdt</artifactId>
<version>0.21.0</version> <version>${org.eclipse.tyco.compiler-jdt.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>

View File

@ -46,6 +46,7 @@
<org.apache.maven.plugins.surefire.version>2.18.1</org.apache.maven.plugins.surefire.version> <org.apache.maven.plugins.surefire.version>2.18.1</org.apache.maven.plugins.surefire.version>
<org.apache.maven.plugins.javadoc.version>2.10.3</org.apache.maven.plugins.javadoc.version> <org.apache.maven.plugins.javadoc.version>2.10.3</org.apache.maven.plugins.javadoc.version>
<org.springframework.version>4.0.3.RELEASE</org.springframework.version> <org.springframework.version>4.0.3.RELEASE</org.springframework.version>
<org.eclipse.tyco.compiler-jdt.version>0.23.1</org.eclipse.tyco.compiler-jdt.version>
<add.release.arguments /> <add.release.arguments />
<forkCount>1</forkCount> <forkCount>1</forkCount>
</properties> </properties>
@ -192,6 +193,23 @@
<version>2.9</version> <version>2.9</version>
</dependency> </dependency>
<!-- Plexus Eclipse Compiler -->
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<version>${org.eclipse.tyco.compiler-jdt.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.20</version>
</dependency>
<!-- Project modules --> <!-- Project modules -->
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>

View File

@ -84,6 +84,17 @@
<artifactId>javax.inject</artifactId> <artifactId>javax.inject</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<scope>test</scope>
</dependency>
<!-- plexus-container-default is a runtime-dependency of the tyco-compiler -->
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring --> <!-- Spring -->
<dependency> <dependency>
@ -109,7 +120,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -126,11 +136,6 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<mapper.test.output.dir>compilation-tests_fork-${surefire.forkNumber}</mapper.test.output.dir>
</systemPropertyVariables>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -18,6 +18,10 @@
*/ */
package org.mapstruct.ap.internal.processor; package org.mapstruct.ap.internal.processor;
import static org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism.AUTO_INHERIT_FROM_CONFIG;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.join;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -60,10 +64,6 @@ import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.internal.version.VersionInformation;
import static org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism.AUTO_INHERIT_FROM_CONFIG;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.join;
/** /**
* A {@link ModelElementProcessor} which creates a {@link Mapper} from the given * A {@link ModelElementProcessor} which creates a {@link Mapper} from the given
* list of {@link SourceMethod}s. * list of {@link SourceMethod}s.
@ -173,7 +173,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
TypeElement decoratorElement = (TypeElement) typeUtils.asElement( decoratorPrism.value() ); TypeElement decoratorElement = (TypeElement) typeUtils.asElement( decoratorPrism.value() );
if ( !typeUtils.isAssignable( decoratorElement.asType(), element.asType() ) ) { if ( !typeUtils.isAssignable( decoratorElement.asType(), element.asType() ) ) {
messager.printMessage( element, decoratorPrism.mirror, Message.DECORATOR_NO_SUBTYPE); messager.printMessage( element, decoratorPrism.mirror, Message.DECORATOR_NO_SUBTYPE );
} }
List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>( methods.size() ); List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>( methods.size() );
@ -491,8 +491,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
else if ( nameFilteredcandidates.size() > 1 ) { else if ( nameFilteredcandidates.size() > 1 ) {
reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism ); reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism );
} }
else {
if ( resultMethod == null ) {
reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism ); reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism );
} }
} }
@ -571,8 +570,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
else if ( nameFilteredcandidates.size() > 1 ) { else if ( nameFilteredcandidates.size() > 1 ) {
reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, forwardPrism ); reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, forwardPrism );
} }
else {
if ( resultMethod == null ) {
reportErrorWhenAmbigousMapping( candidates, method, forwardPrism ); reportErrorWhenAmbigousMapping( candidates, method, forwardPrism );
} }
} }
@ -675,7 +673,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
prism.mirror, prism.mirror,
Message.INHERITCONFIGURATION_DUPLICATE_MATCHES, Message.INHERITCONFIGURATION_DUPLICATE_MATCHES,
prism.name(), prism.name(),
Strings.join( candidates, "(), " ) Strings.join( candidates, ", " )
); );
} }

View File

@ -100,11 +100,11 @@ public enum Message {
INHERITCONFIGURATION_BOTH( "Method cannot be annotated with both a @InheritConfiguration and @InheritInverseConfiguration." ), INHERITCONFIGURATION_BOTH( "Method cannot be annotated with both a @InheritConfiguration and @InheritInverseConfiguration." ),
INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ),
INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ),
INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s." ),
INHERITINVERSECONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), INHERITINVERSECONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ),
INHERITCONFIGURATION_DUPLICATES( "Several matching methods exist: %s(). Specify a name explicitly." ), INHERITCONFIGURATION_DUPLICATES( "Several matching methods exist: %s(). Specify a name explicitly." ),
INHERITCONFIGURATION_INVALIDNAME( "None of the candidates %s() matches given name: \"%s\"." ), INHERITCONFIGURATION_INVALIDNAME( "None of the candidates %s() matches given name: \"%s\"." ),
INHERITCONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), INHERITCONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s." ),
INHERITCONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), INHERITCONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ),
INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritConfiguration to select one of them explicitly: %s." ), INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritConfiguration to select one of them explicitly: %s." ),
INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ); INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" );
@ -113,12 +113,12 @@ public enum Message {
private final String description; private final String description;
private final Diagnostic.Kind kind; private final Diagnostic.Kind kind;
private Message(String description) { Message(String description) {
this.description = description; this.description = description;
this.kind = Diagnostic.Kind.ERROR; this.kind = Diagnostic.Kind.ERROR;
} }
private Message(String description, Diagnostic.Kind kind) { Message(String description, Diagnostic.Kind kind) {
this.description = description; this.description = description;
this.kind = kind; this.kind = kind;
} }

View File

@ -18,6 +18,8 @@
*/ */
package org.mapstruct.ap.test.defaultvalue; package org.mapstruct.ap.test.defaultvalue;
import java.text.ParseException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.IssueKey;
@ -27,8 +29,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import java.text.ParseException;
import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Assertions.assertThat;
@IssueKey( "600" ) @IssueKey( "600" )
@ -136,6 +136,10 @@ public class DefaultValueTest {
line = 33, line = 33,
messageRegExp = "Constant and default value are both defined in @Mapping," messageRegExp = "Constant and default value are both defined in @Mapping,"
+ " either define a defaultValue or a constant." ), + " either define a defaultValue or a constant." ),
@Diagnostic(type = ErroneousMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 33,
messageRegExp = "Can't map property \".*Region region\" to \".*String region\"\\. Consider")
} }
) )
public void errorOnDefaultValueAndConstant() throws ParseException { public void errorOnDefaultValueAndConstant() throws ParseException {
@ -154,6 +158,10 @@ public class DefaultValueTest {
line = 33, line = 33,
messageRegExp = "Expression and default value are both defined in @Mapping," messageRegExp = "Expression and default value are both defined in @Mapping,"
+ " either define a defaultValue or an expression." ), + " either define a defaultValue or an expression." ),
@Diagnostic(type = ErroneousMapper2.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 33,
messageRegExp = "Can't map property \".*Region region\" to \".*String region\"\\. Consider")
} }
) )
public void errorOnDefaultValueAndExpression() throws ParseException { public void errorOnDefaultValueAndExpression() throws ParseException {

View File

@ -19,6 +19,7 @@
package org.mapstruct.ap.test.erroneous.annotationnotfound; package org.mapstruct.ap.test.erroneous.annotationnotfound;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.IssueKey;
@ -45,9 +46,7 @@ public class AnnotationNotFoundTest {
@Diagnostic( type = ErroneousMapper.class, @Diagnostic( type = ErroneousMapper.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 30, line = 30,
messageRegExp = "cannot find symbol\n" messageRegExp = "NotFoundAnnotation")
+ " symbol: class NotFoundAnnotation\n"
+ " location: interface org.mapstruct.ap.test.erroneous.annotationnotfound.ErroneousMapper" )
} }
) )
public void shouldFailToGenerateMappings() { public void shouldFailToGenerateMappings() {

View File

@ -108,8 +108,8 @@ public class InheritInverseConfigurationTest {
@Diagnostic(type = SourceTargetMapperAmbiguous3.class, @Diagnostic(type = SourceTargetMapperAmbiguous3.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 50, line = 50,
messageRegExp = "Given name \"forward\" matches several candidate methods: .*forward\\(.*\\), " messageRegExp = "Given name \"forward\" matches several candidate methods: .*forward\\(.+\\), "
+ ".*forward\\(.*\\)"), + ".*forward\\(.+\\)"),
@Diagnostic(type = SourceTargetMapperAmbiguous3.class, @Diagnostic(type = SourceTargetMapperAmbiguous3.class,
kind = Kind.WARNING, kind = Kind.WARNING,
line = 55, line = 55,

View File

@ -18,7 +18,6 @@
*/ */
package org.mapstruct.ap.test.severalsources; package org.mapstruct.ap.test.severalsources;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.junit.Before; import org.junit.Before;
@ -145,7 +144,7 @@ public class SeveralSourceParametersTest {
assertThat( deliveryAddress.getZipCode() ).isEqualTo( 12345 ); assertThat( deliveryAddress.getZipCode() ).isEqualTo( 12345 );
assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 42 ); assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 42 );
assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" ); assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" );
assertThat( deliveryAddress.getStreet()).isEqualTo( "Main street" ); assertThat( deliveryAddress.getStreet() ).isEqualTo( "Main street" );
} }
@Test @Test
@ -154,21 +153,18 @@ public class SeveralSourceParametersTest {
@ExpectedCompilationOutcome( @ExpectedCompilationOutcome(
value = CompilationResult.FAILED, value = CompilationResult.FAILED,
diagnostics = { diagnostics = {
@Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR,
line = 29,
messageRegExp = "Several possible source properties for target property \"description\".",
javaVersions = { SourceVersion.RELEASE_6 } ),
@Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR,
line = 29,
messageRegExp = "Several possible source properties for target property \"zipCode\".",
javaVersions = { SourceVersion.RELEASE_6 } ),
@Diagnostic(type = ErroneousSourceTargetMapper.class, @Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 29, line = 29,
messageRegExp = "Several possible source properties for target property \"street\"."), messageRegExp = "Several possible source properties for target property \"street\"."),
@Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR,
line = 29,
messageRegExp = "Several possible source properties for target property \"zipCode\"."),
@Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR,
line = 29,
messageRegExp = "Several possible source properties for target property \"description\".")
}) })
public void shouldFailToGenerateMappingsForAmbigiousSourceProperty() { public void shouldFailToGenerateMappingsForAmbigiousSourceProperty() {
} }

View File

@ -53,7 +53,7 @@ public class InheritConfigurationTest {
assertThat( createdTarget ).isNotNull(); assertThat( createdTarget ).isNotNull();
assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" ); assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" );
assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 ); assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 );
assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested"); assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested" );
assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" ); assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" );
assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" ); assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" );
@ -80,7 +80,7 @@ public class InheritConfigurationTest {
assertThat( createdTarget ).isNotNull(); assertThat( createdTarget ).isNotNull();
assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" ); assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" );
assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 ); assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 );
assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested"); assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested" );
assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" ); assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" );
assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" ); assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" );
@ -108,7 +108,7 @@ public class InheritConfigurationTest {
assertThat( createdTarget ).isNotNull(); assertThat( createdTarget ).isNotNull();
assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" ); assertThat( createdTarget.getStringPropY() ).isEqualTo( "1" );
assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 ); assertThat( createdTarget.getIntegerPropY() ).isEqualTo( 2 );
assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested"); assertThat( createdTarget.getNestedResultProp() ).isEqualTo( "nested" );
assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" ); assertThat( createdTarget.getExpressionProp() ).isEqualTo( "expression" );
assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" ); assertThat( createdTarget.getConstantProp() ).isEqualTo( "constant" );
@ -172,7 +172,7 @@ public class InheritConfigurationTest {
kind = Kind.ERROR, kind = Kind.ERROR,
line = 54, line = 54,
messageRegExp = "Given name \"forwardCreate\" matches several candidate methods: " messageRegExp = "Given name \"forwardCreate\" matches several candidate methods: "
+ ".*forwardCreate.*\\(\\), .*forwardCreate.*\\(\\)"), + ".*forwardCreate.*, .*forwardCreate.*"),
@Diagnostic(type = SourceTargetMapperAmbiguous3.class, @Diagnostic(type = SourceTargetMapperAmbiguous3.class,
kind = Kind.WARNING, kind = Kind.WARNING,
line = 55, line = 55,

View File

@ -18,7 +18,6 @@
*/ */
package org.mapstruct.ap.testutil.compilation.annotation; package org.mapstruct.ap.testutil.compilation.annotation;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
/** /**
@ -58,11 +57,4 @@ public @interface Diagnostic {
* diagnostic. * diagnostic.
*/ */
String messageRegExp() default ".*"; String messageRegExp() default ".*";
/**
* The java version for which this this diagnostic is valid.
*
* @return versions for which this Diagnostic should be evaluated. Default it evaluates for all
*/
SourceVersion[] javaVersions() default { };
} }

View File

@ -21,11 +21,13 @@ package org.mapstruct.ap.testutil.compilation.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import org.codehaus.plexus.compiler.CompilerMessage;
import org.codehaus.plexus.compiler.CompilerResult;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
@ -57,27 +59,13 @@ public class CompilationOutcomeDescriptor {
List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>(); List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>();
for ( org.mapstruct.ap.testutil.compilation.annotation.Diagnostic diagnostic : for ( org.mapstruct.ap.testutil.compilation.annotation.Diagnostic diagnostic :
expectedCompilationResult.diagnostics() ) { expectedCompilationResult.diagnostics() ) {
if ( requiresEvaluation( diagnostic.javaVersions() ) ) { diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) );
diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) );
}
} }
return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors ); return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors );
} }
} }
private static boolean requiresEvaluation(SourceVersion[] sourceVersions) {
if ( sourceVersions.length == 0 ) {
return true;
}
for ( SourceVersion sourceVersion : sourceVersions ) {
if ( SourceVersion.latestSupported().equals( sourceVersion ) ) {
return true;
}
}
return false;
}
public static CompilationOutcomeDescriptor forResult(String sourceDir, boolean compilationSuccessful, public static CompilationOutcomeDescriptor forResult(String sourceDir, boolean compilationSuccessful,
List<Diagnostic<? extends JavaFileObject>> diagnostics) { List<Diagnostic<? extends JavaFileObject>> diagnostics) {
CompilationResult compilationResult = CompilationResult compilationResult =
@ -94,6 +82,21 @@ public class CompilationOutcomeDescriptor {
return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors ); return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors );
} }
public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerResult compilerResult) {
CompilationResult compilationResult =
compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED;
List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>();
for ( CompilerMessage message : compilerResult.getCompilerMessages() ) {
if ( message.getKind() != CompilerMessage.Kind.NOTE ) {
diagnosticDescriptors.add( DiagnosticDescriptor.forCompilerMessage( sourceDir, message ) );
}
}
return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors );
}
public CompilationResult getCompilationResult() { public CompilationResult getCompilationResult() {
return compilationResult; return compilationResult;
} }

View File

@ -22,9 +22,11 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import org.codehaus.plexus.compiler.CompilerMessage;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
/** /**
@ -68,6 +70,34 @@ public class DiagnosticDescriptor {
); );
} }
public static DiagnosticDescriptor forCompilerMessage(String sourceDir, CompilerMessage compilerMessage) {
String[] lines = compilerMessage.getMessage().split( System.lineSeparator() );
String message = lines[3];
return new DiagnosticDescriptor(
removeSourceDirPrefix( sourceDir, compilerMessage.getFile() ),
toJavaxKind( compilerMessage.getKind() ),
Long.valueOf( compilerMessage.getStartLine() ),
message );
}
private static Kind toJavaxKind(CompilerMessage.Kind kind) {
switch ( kind ) {
case ERROR:
return Kind.ERROR;
case MANDATORY_WARNING:
return Kind.MANDATORY_WARNING;
case NOTE:
return Kind.NOTE;
case OTHER:
return Kind.OTHER;
case WARNING:
return Kind.WARNING;
default:
return null;
}
}
private static String getSourceName(String sourceDir, javax.tools.Diagnostic<? extends JavaFileObject> diagnostic) { private static String getSourceName(String sourceDir, javax.tools.Diagnostic<? extends JavaFileObject> diagnostic) {
if ( diagnostic.getSource() == null ) { if ( diagnostic.getSource() == null ) {
return null; return null;
@ -77,15 +107,19 @@ public class DiagnosticDescriptor {
try { try {
String sourceName = new File( uri ).getCanonicalPath(); String sourceName = new File( uri ).getCanonicalPath();
return sourceName.length() > sourceDir.length() ? return removeSourceDirPrefix( sourceDir, sourceName );
sourceName.substring( sourceDir.length() + 1 ) :
sourceName;
} }
catch ( IOException e ) { catch ( IOException e ) {
throw new RuntimeException( e ); throw new RuntimeException( e );
} }
} }
private static String removeSourceDirPrefix(String sourceDir, String sourceName) {
return sourceName.length() > sourceDir.length() ?
sourceName.substring( sourceDir.length() + 1 ) :
sourceName;
}
private static URI getUri(javax.tools.Diagnostic<? extends JavaFileObject> diagnostic) { private static URI getUri(javax.tools.Diagnostic<? extends JavaFileObject> diagnostic) {
URI uri = diagnostic.getSource().toUri(); URI uri = diagnostic.getSource().toUri();

View File

@ -18,12 +18,14 @@
*/ */
package org.mapstruct.ap.testutil.runner; package org.mapstruct.ap.testutil.runner;
import java.net.URL; import java.util.Arrays;
import java.util.List;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod; import org.junit.runners.ParentRunner;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
@ -31,7 +33,7 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
/** /**
* A JUnit4 runner for Annotation Processor tests. * A JUnit4 runner for Annotation Processor tests.
* <p> * <p>
* Test classes and test methods are safe to be executed in parallel. * Test classes are safe to be executed in parallel, the methods must not executed in parallel.
* <p> * <p>
* The classes to be compiled for a given test method must be specified via {@link WithClasses}. In addition the * The classes to be compiled for a given test method must be specified via {@link WithClasses}. In addition the
* following things can be configured optionally : * following things can be configured optionally :
@ -44,11 +46,8 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
* @author Gunnar Morling * @author Gunnar Morling
* @author Andreas Gudian * @author Andreas Gudian
*/ */
public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { public class AnnotationProcessorTestRunner extends ParentRunner<Runner> {
static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader(); private final List<Runner> runners;
private final Class<?> klass;
private Class<?> klassToUse;
private ReplacableTestClass replacableTestClass;
/** /**
* @param klass the test class * @param klass the test class
@ -57,72 +56,24 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
*/ */
public AnnotationProcessorTestRunner(Class<?> klass) throws Exception { public AnnotationProcessorTestRunner(Class<?> klass) throws Exception {
super( klass ); super( klass );
this.klass = klass;
}
/** runners = Arrays.<Runner> asList(
* newly loads the class with the test class loader and sets that loader as context class loader of the thread new InnerAnnotationProcessorRunner( klass, Compiler.JDK ),
* new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) );
* @param klass the class to replace
*
* @return the class loaded with the test class loader
*/
private static Class<?> replaceClassLoaderAndClass(Class<?> klass) {
replaceContextClassLoader( klass );
try {
return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() );
}
catch ( ClassNotFoundException e ) {
throw new RuntimeException( e );
}
}
private static void replaceContextClassLoader(Class<?> klass) {
String classFileName = klass.getName().replace( ".", "/" ) + ".class";
URL classResource = klass.getClassLoader().getResource( classFileName );
String fullyQualifiedUrl = classResource.toExternalForm();
String basePath = fullyQualifiedUrl.substring( 0, fullyQualifiedUrl.length() - classFileName.length() );
ModifiableURLClassLoader testClassLoader = new ModifiableURLClassLoader();
testClassLoader.addURL( basePath );
Thread.currentThread().setContextClassLoader( testClassLoader );
} }
@Override @Override
protected TestClass createTestClass(final Class<?> testClass) { protected List<Runner> getChildren() {
replacableTestClass = new ReplacableTestClass( testClass ); return runners;
return replacableTestClass;
}
private FrameworkMethod replaceFrameworkMethod(FrameworkMethod m) {
try {
return new FrameworkMethod(
klassToUse.getDeclaredMethod( m.getName(), m.getMethod().getParameterTypes() ) );
}
catch ( NoSuchMethodException e ) {
throw new RuntimeException( e );
}
} }
@Override @Override
protected Statement methodBlock(FrameworkMethod method) { protected Description describeChild(Runner child) {
CompilingStatement statement = new CompilingStatement( method ); return child.getDescription();
if ( statement.needsRecompilation() ) {
klassToUse = replaceClassLoaderAndClass( klass );
replacableTestClass.replaceClass( klassToUse );
}
method = replaceFrameworkMethod( method );
Statement next = super.methodBlock( method );
statement.setNextStatement( next );
return statement;
} }
@Override
protected void runChild(Runner child, RunNotifier notifier) {
child.run( notifier );
}
} }

View File

@ -0,0 +1,48 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor;
class CompilationCache {
private String lastSourceOutputDir;
private CompilationRequest lastRequest;
private CompilationOutcomeDescriptor lastResult;
public String getLastSourceOutputDir() {
return lastSourceOutputDir;
}
public void setLastSourceOutputDir(String lastSourceOutputDir) {
this.lastSourceOutputDir = lastSourceOutputDir;
}
public CompilationRequest getLastRequest() {
return lastRequest;
}
public void update(CompilationRequest lastRequest, CompilationOutcomeDescriptor lastResult) {
this.lastRequest = lastRequest;
this.lastResult = lastResult;
}
public CompilationOutcomeDescriptor getLastResult() {
return lastResult;
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
import java.util.List;
import java.util.Set;
/**
* Represents a compilation task for a number of sources with given processor options.
*/
class CompilationRequest {
private final Set<Class<?>> sourceClasses;
private final List<String> processorOptions;
CompilationRequest(Set<Class<?>> sourceClasses, List<String> processorOptions) {
this.sourceClasses = sourceClasses;
this.processorOptions = processorOptions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( processorOptions == null ) ? 0 : processorOptions.hashCode() );
result = prime * result + ( ( sourceClasses == null ) ? 0 : sourceClasses.hashCode() );
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
CompilationRequest other = (CompilationRequest) obj;
return processorOptions.equals( other.processorOptions ) && sourceClasses.equals( other.sourceClasses );
}
public Set<Class<?>> getSourceClasses() {
return sourceClasses;
}
public List<String> getProcessorOptions() {
return processorOptions;
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
/**
* @author Andreas Gudian
*
*/
public enum Compiler {
JDK, ECLIPSE;
}

View File

@ -31,19 +31,9 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement; import org.junit.runners.model.Statement;
import org.mapstruct.ap.MappingProcessor;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
@ -67,58 +57,34 @@ import static org.fest.assertions.Assertions.assertThat;
* *
* @author Andreas Gudian * @author Andreas Gudian
*/ */
class CompilingStatement extends Statement { abstract class CompilingStatement extends Statement {
/** private static final String TARGET_COMPILATION_TESTS = "/target/compilation-tests/";
* Property to specify the sub-directory below /target/ where the generated files are placed
*/
public static final String MAPPER_TEST_OUTPUT_DIR_PROPERTY = "mapper.test.output.dir";
private static final String TARGET_COMPILATION_TESTS = "/target/"
+ System.getProperty( MAPPER_TEST_OUTPUT_DIR_PROPERTY, "compilation-tests" ) + "_thread-";
private static final String SOURCE_DIR = getBasePath() + "/src/test/java";
private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );
private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator(); private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator();
private static final ThreadLocal<Integer> THREAD_NUMBER = new ThreadLocal<Integer>() { protected static final String SOURCE_DIR = getBasePath() + "/src/test/java";
private final AtomicInteger nextThreadId = new AtomicInteger( 0 );
@Override protected static final List<String> COMPILER_CLASSPATH = buildCompilerClasspath();
protected Integer initialValue() {
return nextThreadId.getAndIncrement();
}
};
/**
* Caches the outcome of given compilations. That way we avoid the repeated compilation of the same source files for
* several test methods of one test class.
*/
private static final ThreadLocal<CompilationCache> COMPILATION_CACHE = new ThreadLocal<CompilationCache>() {
@Override
protected CompilationCache initialValue() {
return new CompilationCache();
}
};
private static final List<File> COMPILER_CLASSPATH = buildCompilerClasspath();
private Statement next;
private final FrameworkMethod method; private final FrameworkMethod method;
private final CompilationCache compilationCache;
private Statement next;
private JavaCompiler compiler;
private String classOutputDir; private String classOutputDir;
private String sourceOutputDir; private String sourceOutputDir;
private CompilationRequest compilationRequest; private CompilationRequest compilationRequest;
public CompilingStatement(FrameworkMethod method) { CompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
this.method = method; this.method = method;
this.compilationCache = compilationCache;
this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() ); this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() );
} }
public void setNextStatement(Statement next) { void setNextStatement(Statement next) {
this.next = next; this.next = next;
} }
@ -126,29 +92,33 @@ class CompilingStatement extends Statement {
public void evaluate() throws Throwable { public void evaluate() throws Throwable {
generateMapperImplementation(); generateMapperImplementation();
GeneratedSource.setCompilingStatement( this );
next.evaluate(); next.evaluate();
GeneratedSource.clearCompilingStatement();
} }
static String getSourceOutputDir() { String getSourceOutputDir() {
return COMPILATION_CACHE.get().lastSourceOutputDir; return compilationCache.getLastSourceOutputDir();
} }
protected void setupCompiler() throws Exception { protected void setupDirectories() throws Exception {
compiler = ToolProvider.getSystemJavaCompiler(); String compilationRoot = getBasePath()
+ TARGET_COMPILATION_TESTS
+ method.getDeclaringClass().getName()
+ "/" + method.getName()
+ getPathSuffix();
String basePath = getBasePath(); classOutputDir = compilationRoot + "/classes";
sourceOutputDir = compilationRoot + "/generated-sources";
Integer i = THREAD_NUMBER.get();
classOutputDir = basePath + TARGET_COMPILATION_TESTS + i + "/classes";
sourceOutputDir = basePath + TARGET_COMPILATION_TESTS + i + "/generated-sources/mapping";
createOutputDirs(); createOutputDirs();
( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir ); ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir );
} }
private static List<File> buildCompilerClasspath() { protected abstract String getPathSuffix();
private static List<String> buildCompilerClasspath() {
String[] bootClasspath = String[] bootClasspath =
System.getProperty( "java.class.path" ).split( System.getProperty( "path.separator" ) ); System.getProperty( "java.class.path" ).split( System.getProperty( "path.separator" ) );
String fs = System.getProperty( "file.separator" ); String fs = System.getProperty( "file.separator" );
@ -166,10 +136,10 @@ class CompilingStatement extends Statement {
"spring-context", "spring-context",
"joda-time" }; "joda-time" };
List<File> classpath = new ArrayList<File>(); List<String> classpath = new ArrayList<String>();
for ( String path : bootClasspath ) { for ( String path : bootClasspath ) {
if ( !path.contains( testClasses ) && isWhitelisted( path, whitelist ) ) { if ( !path.contains( testClasses ) && isWhitelisted( path, whitelist ) ) {
classpath.add( new File( path ) ); classpath.add( path );
} }
} }
@ -186,14 +156,8 @@ class CompilingStatement extends Statement {
} }
protected void generateMapperImplementation() throws Exception { protected void generateMapperImplementation() throws Exception {
CompilationResultHolder compilationResult = compile(); CompilationOutcomeDescriptor actualResult = compile();
CompilationOutcomeDescriptor actualResult =
CompilationOutcomeDescriptor.forResult(
SOURCE_DIR,
compilationResult.compilationSuccessful,
compilationResult.diagnostics.getDiagnostics()
);
CompilationOutcomeDescriptor expectedResult = CompilationOutcomeDescriptor expectedResult =
CompilationOutcomeDescriptor.forExpectedCompilationResult( CompilationOutcomeDescriptor.forExpectedCompilationResult(
method.getAnnotation( ExpectedCompilationOutcome.class ) method.getAnnotation( ExpectedCompilationOutcome.class )
@ -201,7 +165,7 @@ class CompilingStatement extends Statement {
if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) {
assertThat( actualResult.getCompilationResult() ).describedAs( assertThat( actualResult.getCompilationResult() ).describedAs(
"Compilation failed. Diagnostics: " + compilationResult.diagnostics.getDiagnostics() "Compilation failed. Diagnostics: " + actualResult.getDiagnostics()
).isEqualTo( ).isEqualTo(
CompilationResult.SUCCEEDED CompilationResult.SUCCEEDED
); );
@ -263,6 +227,7 @@ class CompilingStatement extends Statement {
Collections.sort( actualDiagnostics, COMPARATOR ); Collections.sort( actualDiagnostics, COMPARATOR );
Collections.sort( expectedDiagnostics, COMPARATOR ); Collections.sort( expectedDiagnostics, COMPARATOR );
expectedDiagnostics = filterExpectedDiagnostics( expectedDiagnostics );
Iterator<DiagnosticDescriptor> actualIterator = actualDiagnostics.iterator(); Iterator<DiagnosticDescriptor> actualIterator = actualDiagnostics.iterator();
Iterator<DiagnosticDescriptor> expectedIterator = expectedDiagnostics.iterator(); Iterator<DiagnosticDescriptor> expectedIterator = expectedDiagnostics.iterator();
@ -299,10 +264,18 @@ class CompilingStatement extends Statement {
actual.getLine(), actual.getLine(),
actual.getKind() actual.getKind()
) )
).matches( ".*" + expected.getMessage() + ".*" ); ).matches( "(?ms).*" + expected.getMessage() + ".*" );
} }
} }
/**
* @param expectedDiagnostics expected diagnostics
* @return a possibly filtered list of expected diagnostics
*/
protected List<DiagnosticDescriptor> filterExpectedDiagnostics(List<DiagnosticDescriptor> expectedDiagnostics) {
return expectedDiagnostics;
}
/** /**
* Returns the classes to be compiled for this test. * Returns the classes to be compiled for this test.
* *
@ -371,7 +344,7 @@ class CompilingStatement extends Statement {
return String.format( "-A%s=%s", processorOption.name(), processorOption.value() ); return String.format( "-A%s=%s", processorOption.name(), processorOption.value() );
} }
private Set<File> getSourceFiles(Collection<Class<?>> classes) { protected Set<File> getSourceFiles(Collection<Class<?>> classes) {
Set<File> sourceFiles = new HashSet<File>( classes.size() ); Set<File> sourceFiles = new HashSet<File>( classes.size() );
for ( Class<?> clazz : classes ) { for ( Class<?> clazz : classes ) {
@ -386,52 +359,30 @@ class CompilingStatement extends Statement {
return sourceFiles; return sourceFiles;
} }
private CompilationResultHolder compile() private CompilationOutcomeDescriptor compile()
throws Exception { throws Exception {
CompilationCache cache = COMPILATION_CACHE.get();
if ( !needsRecompilation() ) { if ( !needsRecompilation() ) {
return cache.lastResult; return compilationCache.getLastResult();
} }
setupCompiler(); setupDirectories();
cache.lastSourceOutputDir = sourceOutputDir; compilationCache.setLastSourceOutputDir( sourceOutputDir );
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); CompilationOutcomeDescriptor resultHolder =
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); compileWithSpecificCompiler( compilationRequest, sourceOutputDir, classOutputDir );
Iterable<? extends JavaFileObject> compilationUnits = compilationCache.update( compilationRequest, resultHolder );
fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.sourceClasses ) );
try {
fileManager.setLocation( StandardLocation.CLASS_PATH, COMPILER_CLASSPATH );
fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) );
fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) );
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
CompilationTask task =
compiler.getTask(
null,
fileManager,
diagnostics,
compilationRequest.processorOptions,
null,
compilationUnits );
task.setProcessors( Arrays.asList( new MappingProcessor() ) );
CompilationResultHolder resultHolder = new CompilationResultHolder( diagnostics, task.call() );
cache.lastRequest = compilationRequest;
cache.lastResult = resultHolder;
return resultHolder; return resultHolder;
} }
public boolean needsRecompilation() { protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler(
return !compilationRequest.equals( COMPILATION_CACHE.get().lastRequest ); CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir);
boolean needsRecompilation() {
return !compilationRequest.equals( compilationCache.getLastRequest() );
} }
private static String getBasePath() { private static String getBasePath() {
@ -485,66 +436,7 @@ class CompilingStatement extends Statement {
return result; return result;
} }
// Using the message is not perfect when using regular expressions, return o1.getKind().compareTo( o2.getKind() );
// but it's better than nothing
return o1.getMessage().compareTo( o2.getMessage() );
}
}
private static class CompilationCache {
private String lastSourceOutputDir;
private CompilationRequest lastRequest;
private CompilationResultHolder lastResult;
}
/**
* Represents the result of a compilation.
*/
private static class CompilationResultHolder {
private final DiagnosticCollector<JavaFileObject> diagnostics;
private final boolean compilationSuccessful;
public CompilationResultHolder(DiagnosticCollector<JavaFileObject> diagnostics, boolean compilationSuccessful) {
this.diagnostics = diagnostics;
this.compilationSuccessful = compilationSuccessful;
}
}
/**
* Represents a compilation task for a number of sources with given processor options.
*/
private static class CompilationRequest {
private final Set<Class<?>> sourceClasses;
private final List<String> processorOptions;
public CompilationRequest(Set<Class<?>> sourceClasses, List<String> processorOptions) {
this.sourceClasses = sourceClasses;
this.processorOptions = processorOptions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( processorOptions == null ) ? 0 : processorOptions.hashCode() );
result = prime * result + ( ( sourceClasses == null ) ? 0 : sourceClasses.hashCode() );
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
CompilationRequest other = (CompilationRequest) obj;
return processorOptions.equals( other.processorOptions ) && sourceClasses.equals( other.sourceClasses );
} }
} }
} }

View File

@ -0,0 +1,82 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
import java.io.File;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerResult;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.eclipse.tycho.compiler.jdt.JDTCompiler;
import org.junit.runners.model.FrameworkMethod;
import org.mapstruct.ap.MappingProcessor;
import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor;
/**
* Statement that uses the JDK compiler to compile.
*
* @author Andreas Gudian
*/
class EclipseCompilingStatement extends CompilingStatement {
EclipseCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
super( method, compilationCache );
}
@Override
protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir) {
JDTCompiler compiler = new JDTCompiler();
compiler.enableLogging( new ConsoleLogger( 5, "JDT-Compiler" ) );
CompilerConfiguration config = new CompilerConfiguration();
config.setClasspathEntries( COMPILER_CLASSPATH );
config.setOutputLocation( classOutputDir );
config.setGeneratedSourcesDirectory( new File( sourceOutputDir ) );
config.setAnnotationProcessors( new String[] { MappingProcessor.class.getName() } );
config.setSourceFiles( getSourceFiles( compilationRequest.getSourceClasses() ) );
config.setShowWarnings( false );
config.setSourceVersion( "1.6" );
config.setTargetVersion( "1.6" );
for ( String option : compilationRequest.getProcessorOptions() ) {
config.addCompilerCustomArgument( option, null );
}
CompilerResult compilerResult;
try {
compilerResult = compiler.performCompile( config );
}
catch ( CompilerException e ) {
throw new RuntimeException( e );
}
return CompilationOutcomeDescriptor.forResult(
SOURCE_DIR,
compilerResult );
}
@Override
protected String getPathSuffix() {
return "_eclipse";
}
}

View File

@ -39,11 +39,21 @@ import org.mapstruct.ap.testutil.assertions.JavaFileAssert;
*/ */
public class GeneratedSource implements TestRule { public class GeneratedSource implements TestRule {
private static ThreadLocal<CompilingStatement> compilingStatement = new ThreadLocal<CompilingStatement>();;
@Override @Override
public Statement apply(Statement base, Description description) { public Statement apply(Statement base, Description description) {
return base; return base;
} }
static void setCompilingStatement(CompilingStatement compilingStatement) {
GeneratedSource.compilingStatement.set( compilingStatement );
}
static void clearCompilingStatement() {
GeneratedSource.compilingStatement.remove();
}
/** /**
* @param mapperClass the class annotated with {@code &#064;Mapper} * @param mapperClass the class annotated with {@code &#064;Mapper}
* *
@ -60,6 +70,6 @@ public class GeneratedSource implements TestRule {
* @return an assert for the file specified by the given path * @return an assert for the file specified by the given path
*/ */
public JavaFileAssert forJavaFile(String path) { public JavaFileAssert forJavaFile(String path) {
return new JavaFileAssert( new File( CompilingStatement.getSourceOutputDir() + "/" + path ) ); return new JavaFileAssert( new File( compilingStatement.get().getSourceOutputDir() + "/" + path ) );
} }
} }

View File

@ -0,0 +1,150 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
import java.net.URL;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
/**
* A JUnit4 runner for Annotation Processor tests.
* <p>
* Test classes and test methods are safe to be executed in parallel.
* <p>
* The classes to be compiled for a given test method must be specified via {@link WithClasses}. In addition the
* following things can be configured optionally :
* <ul>
* <li>Processor options to be considered during compilation via {@link ProcessorOption}.</li>
* <li>The expected compilation outcome and expected diagnostics can be specified via {@link ExpectedCompilationOutcome}
* . If no outcome is specified, a successful compilation is assumed.</li>
* </ul>
*
* @author Gunnar Morling
* @author Andreas Gudian
*/
class InnerAnnotationProcessorRunner extends BlockJUnit4ClassRunner {
static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader();
private final Class<?> klass;
private final Compiler compiler;
private final CompilationCache compilationCache;
private Class<?> klassToUse;
private ReplacableTestClass replacableTestClass;
/**
* @param klass the test class
*
* @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)}
*/
InnerAnnotationProcessorRunner(Class<?> klass, Compiler compiler) throws Exception {
super( klass );
this.klass = klass;
this.compiler = compiler;
this.compilationCache = new CompilationCache();
}
/**
* newly loads the class with the test class loader and sets that loader as context class loader of the thread
*
* @param klass the class to replace
*
* @return the class loaded with the test class loader
*/
private static Class<?> replaceClassLoaderAndClass(Class<?> klass) {
replaceContextClassLoader( klass );
try {
return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() );
}
catch ( ClassNotFoundException e ) {
throw new RuntimeException( e );
}
}
private static void replaceContextClassLoader(Class<?> klass) {
String classFileName = klass.getName().replace( ".", "/" ) + ".class";
URL classResource = klass.getClassLoader().getResource( classFileName );
String fullyQualifiedUrl = classResource.toExternalForm();
String basePath = fullyQualifiedUrl.substring( 0, fullyQualifiedUrl.length() - classFileName.length() );
ModifiableURLClassLoader testClassLoader = new ModifiableURLClassLoader();
testClassLoader.addURL( basePath );
Thread.currentThread().setContextClassLoader( testClassLoader );
}
@Override
protected TestClass createTestClass(final Class<?> testClass) {
replacableTestClass = new ReplacableTestClass( testClass );
return replacableTestClass;
}
private FrameworkMethod replaceFrameworkMethod(FrameworkMethod m) {
try {
return new FrameworkMethod(
klassToUse.getDeclaredMethod( m.getName(), m.getMethod().getParameterTypes() ) );
}
catch ( NoSuchMethodException e ) {
throw new RuntimeException( e );
}
}
@Override
protected Statement methodBlock(FrameworkMethod method) {
CompilingStatement statement = createCompilingStatement( method );
if ( statement.needsRecompilation() ) {
klassToUse = replaceClassLoaderAndClass( klass );
replacableTestClass.replaceClass( klassToUse );
}
method = replaceFrameworkMethod( method );
Statement next = super.methodBlock( method );
statement.setNextStatement( next );
return statement;
}
private CompilingStatement createCompilingStatement(FrameworkMethod method) {
if ( compiler == Compiler.JDK ) {
return new JdkCompilingStatement( method, compilationCache );
}
else {
return new EclipseCompilingStatement( method, compilationCache );
}
}
@Override
protected String getName() {
return "[" + compiler.name().toLowerCase() + "]";
}
@Override
protected String testName(FrameworkMethod method) {
return method.getName() + getName();
}
}

View File

@ -0,0 +1,129 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.mapstruct.ap.testutil.runner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.junit.runners.model.FrameworkMethod;
import org.mapstruct.ap.MappingProcessor;
import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor;
import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor;
/**
* Statement that uses the JDK compiler to compile.
*
* @author Andreas Gudian
*/
class JdkCompilingStatement extends CompilingStatement {
private static final List<File> COMPILER_CLASSPATH_FILES = asFiles( COMPILER_CLASSPATH );
JdkCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
super( method, compilationCache );
}
@Override
protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) );
try {
fileManager.setLocation( StandardLocation.CLASS_PATH, COMPILER_CLASSPATH_FILES );
fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) );
fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) );
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
CompilationTask task =
compiler.getTask(
null,
fileManager,
diagnostics,
compilationRequest.getProcessorOptions(),
null,
compilationUnits );
task.setProcessors( Arrays.asList( new MappingProcessor() ) );
Boolean compilationSuccessful = task.call();
return CompilationOutcomeDescriptor.forResult(
SOURCE_DIR,
compilationSuccessful,
diagnostics.getDiagnostics() );
}
private static List<File> asFiles(List<String> paths) {
List<File> classpath = new ArrayList<File>();
for ( String path : paths ) {
classpath.add( new File( path ) );
}
return classpath;
}
/**
* The JDK compiler only reports the first message of kind ERROR that is reported for one source file line, so we
* filter out the surplus diagnostics. The input list is already sorted by file name and line number, with the order
* for the diagnostics in the same line being kept at the order as given in the test.
*/
@Override
protected List<DiagnosticDescriptor> filterExpectedDiagnostics(List<DiagnosticDescriptor> expectedDiagnostics) {
List<DiagnosticDescriptor> filtered = new ArrayList<DiagnosticDescriptor>( expectedDiagnostics.size() );
DiagnosticDescriptor previous = null;
for ( DiagnosticDescriptor diag : expectedDiagnostics ) {
if ( diag.getKind() != Kind.ERROR
|| previous == null
|| !previous.getSourceFileName().equals( diag.getSourceFileName() )
|| !previous.getLine().equals( diag.getLine() ) ) {
filtered.add( diag );
previous = diag;
}
}
return filtered;
}
@Override
protected String getPathSuffix() {
return "_jdk";
}
}