diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index 0e0cc4f3e..198ed8aaa 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -64,7 +64,7 @@ org.eclipse.tycho tycho-compiler-jdt - 0.21.0 + ${org.eclipse.tyco.compiler-jdt.version} diff --git a/parent/pom.xml b/parent/pom.xml index 7badea2ee..737d99032 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -46,6 +46,7 @@ 2.18.1 2.10.3 4.0.3.RELEASE + 0.23.1 1 @@ -192,6 +193,23 @@ 2.9 + + + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tyco.compiler-jdt.version} + + + org.codehaus.plexus + plexus-container-default + 1.6 + + + org.codehaus.plexus + plexus-utils + 3.0.20 + + ${project.groupId} diff --git a/processor/pom.xml b/processor/pom.xml index a22c6a136..652436ce3 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -84,6 +84,17 @@ javax.inject test + + org.eclipse.tycho + tycho-compiler-jdt + test + + + + org.codehaus.plexus + plexus-container-default + test + @@ -109,7 +120,6 @@ test - @@ -126,11 +136,6 @@ org.apache.maven.plugins maven-surefire-plugin - - - compilation-tests_fork-${surefire.forkNumber} - - org.apache.maven.plugins diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 7baf98730..f6f4789e0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -18,6 +18,10 @@ */ 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.LinkedList; 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.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 * list of {@link SourceMethod}s. @@ -173,7 +173,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mappingMethods = new ArrayList( methods.size() ); @@ -491,8 +491,7 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism ); } - - if ( resultMethod == null ) { + else { reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism ); } } @@ -571,8 +570,7 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, forwardPrism ); } - - if ( resultMethod == null ) { + else { reportErrorWhenAmbigousMapping( candidates, method, forwardPrism ); } } @@ -675,7 +673,7 @@ public class MapperCreationProcessor implements ModelElementProcessor diagnosticDescriptors = new ArrayList(); for ( org.mapstruct.ap.testutil.compilation.annotation.Diagnostic diagnostic : expectedCompilationResult.diagnostics() ) { - if ( requiresEvaluation( diagnostic.javaVersions() ) ) { - diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) ); - } + diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) ); } 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, List> diagnostics) { CompilationResult compilationResult = @@ -94,6 +82,21 @@ public class CompilationOutcomeDescriptor { return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors ); } + public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerResult compilerResult) { + CompilationResult compilationResult = + compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; + + List diagnosticDescriptors = new ArrayList(); + + 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() { return compilationResult; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index db224a163..2d62a9c85 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -22,9 +22,11 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; + import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; +import org.codehaus.plexus.compiler.CompilerMessage; 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 diagnostic) { if ( diagnostic.getSource() == null ) { return null; @@ -77,15 +107,19 @@ public class DiagnosticDescriptor { try { String sourceName = new File( uri ).getCanonicalPath(); - return sourceName.length() > sourceDir.length() ? - sourceName.substring( sourceDir.length() + 1 ) : - sourceName; + return removeSourceDirPrefix( sourceDir, sourceName ); } catch ( IOException 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 diagnostic) { URI uri = diagnostic.getSource().toUri(); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java index 4f3227241..da470c9d9 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java @@ -18,12 +18,14 @@ */ 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.model.FrameworkMethod; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; +import org.junit.runners.ParentRunner; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; 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. *

- * 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. *

* 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 : @@ -44,11 +46,8 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; * @author Gunnar Morling * @author Andreas Gudian */ -public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { - static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader(); - private final Class klass; - private Class klassToUse; - private ReplacableTestClass replacableTestClass; +public class AnnotationProcessorTestRunner extends ParentRunner { + private final List runners; /** * @param klass the test class @@ -57,72 +56,24 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { */ public AnnotationProcessorTestRunner(Class klass) throws Exception { super( klass ); - this.klass = klass; - } - /** - * 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 ); + runners = Arrays. asList( + new InnerAnnotationProcessorRunner( klass, Compiler.JDK ), + new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) ); } @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 ); - } + protected List getChildren() { + return runners; } @Override - protected Statement methodBlock(FrameworkMethod method) { - CompilingStatement statement = new CompilingStatement( method ); - if ( statement.needsRecompilation() ) { - klassToUse = replaceClassLoaderAndClass( klass ); - - replacableTestClass.replaceClass( klassToUse ); - } - - method = replaceFrameworkMethod( method ); - - Statement next = super.methodBlock( method ); - - statement.setNextStatement( next ); - - return statement; + protected Description describeChild(Runner child) { + return child.getDescription(); } + @Override + protected void runChild(Runner child, RunNotifier notifier) { + child.run( notifier ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationCache.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationCache.java new file mode 100644 index 000000000..e99af9cf3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationCache.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java new file mode 100644 index 000000000..e98a7be0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -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> sourceClasses; + private final List processorOptions; + + CompilationRequest(Set> sourceClasses, List 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> getSourceClasses() { + return sourceClasses; + } + + public List getProcessorOptions() { + return processorOptions; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java new file mode 100644 index 000000000..c9c1612e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java @@ -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; +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java index ff972348a..c2f4e7ffd 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java @@ -31,19 +31,9 @@ import java.util.Iterator; import java.util.List; import java.util.Properties; 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.Statement; -import org.mapstruct.ap.MappingProcessor; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -67,58 +57,34 @@ import static org.fest.assertions.Assertions.assertThat; * * @author Andreas Gudian */ -class CompilingStatement extends Statement { +abstract class CompilingStatement extends Statement { - /** - * 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 TARGET_COMPILATION_TESTS = "/target/compilation-tests/"; private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator(); - private static final ThreadLocal THREAD_NUMBER = new ThreadLocal() { - private final AtomicInteger nextThreadId = new AtomicInteger( 0 ); + protected static final String SOURCE_DIR = getBasePath() + "/src/test/java"; - @Override - protected Integer initialValue() { - return nextThreadId.getAndIncrement(); - } - }; + protected static final List COMPILER_CLASSPATH = buildCompilerClasspath(); - /** - * 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 COMPILATION_CACHE = new ThreadLocal() { - @Override - protected CompilationCache initialValue() { - return new CompilationCache(); - } - }; - - private static final List COMPILER_CLASSPATH = buildCompilerClasspath(); - - private Statement next; private final FrameworkMethod method; + private final CompilationCache compilationCache; + private Statement next; - private JavaCompiler compiler; private String classOutputDir; private String sourceOutputDir; private CompilationRequest compilationRequest; - public CompilingStatement(FrameworkMethod method) { + CompilingStatement(FrameworkMethod method, CompilationCache compilationCache) { this.method = method; + this.compilationCache = compilationCache; this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() ); } - public void setNextStatement(Statement next) { + void setNextStatement(Statement next) { this.next = next; } @@ -126,29 +92,33 @@ class CompilingStatement extends Statement { public void evaluate() throws Throwable { generateMapperImplementation(); + GeneratedSource.setCompilingStatement( this ); next.evaluate(); + GeneratedSource.clearCompilingStatement(); } - static String getSourceOutputDir() { - return COMPILATION_CACHE.get().lastSourceOutputDir; + String getSourceOutputDir() { + return compilationCache.getLastSourceOutputDir(); } - protected void setupCompiler() throws Exception { - compiler = ToolProvider.getSystemJavaCompiler(); + protected void setupDirectories() throws Exception { + String compilationRoot = getBasePath() + + TARGET_COMPILATION_TESTS + + method.getDeclaringClass().getName() + + "/" + method.getName() + + getPathSuffix(); - String basePath = getBasePath(); - - Integer i = THREAD_NUMBER.get(); - - classOutputDir = basePath + TARGET_COMPILATION_TESTS + i + "/classes"; - sourceOutputDir = basePath + TARGET_COMPILATION_TESTS + i + "/generated-sources/mapping"; + classOutputDir = compilationRoot + "/classes"; + sourceOutputDir = compilationRoot + "/generated-sources"; createOutputDirs(); ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir ); } - private static List buildCompilerClasspath() { + protected abstract String getPathSuffix(); + + private static List buildCompilerClasspath() { String[] bootClasspath = System.getProperty( "java.class.path" ).split( System.getProperty( "path.separator" ) ); String fs = System.getProperty( "file.separator" ); @@ -166,10 +136,10 @@ class CompilingStatement extends Statement { "spring-context", "joda-time" }; - List classpath = new ArrayList(); + List classpath = new ArrayList(); for ( String path : bootClasspath ) { 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 { - CompilationResultHolder compilationResult = compile(); + CompilationOutcomeDescriptor actualResult = compile(); - CompilationOutcomeDescriptor actualResult = - CompilationOutcomeDescriptor.forResult( - SOURCE_DIR, - compilationResult.compilationSuccessful, - compilationResult.diagnostics.getDiagnostics() - ); CompilationOutcomeDescriptor expectedResult = CompilationOutcomeDescriptor.forExpectedCompilationResult( method.getAnnotation( ExpectedCompilationOutcome.class ) @@ -201,7 +165,7 @@ class CompilingStatement extends Statement { if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { assertThat( actualResult.getCompilationResult() ).describedAs( - "Compilation failed. Diagnostics: " + compilationResult.diagnostics.getDiagnostics() + "Compilation failed. Diagnostics: " + actualResult.getDiagnostics() ).isEqualTo( CompilationResult.SUCCEEDED ); @@ -263,6 +227,7 @@ class CompilingStatement extends Statement { Collections.sort( actualDiagnostics, COMPARATOR ); Collections.sort( expectedDiagnostics, COMPARATOR ); + expectedDiagnostics = filterExpectedDiagnostics( expectedDiagnostics ); Iterator actualIterator = actualDiagnostics.iterator(); Iterator expectedIterator = expectedDiagnostics.iterator(); @@ -299,10 +264,18 @@ class CompilingStatement extends Statement { actual.getLine(), actual.getKind() ) - ).matches( ".*" + expected.getMessage() + ".*" ); + ).matches( "(?ms).*" + expected.getMessage() + ".*" ); } } + /** + * @param expectedDiagnostics expected diagnostics + * @return a possibly filtered list of expected diagnostics + */ + protected List filterExpectedDiagnostics(List expectedDiagnostics) { + return expectedDiagnostics; + } + /** * 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() ); } - private Set getSourceFiles(Collection> classes) { + protected Set getSourceFiles(Collection> classes) { Set sourceFiles = new HashSet( classes.size() ); for ( Class clazz : classes ) { @@ -386,52 +359,30 @@ class CompilingStatement extends Statement { return sourceFiles; } - private CompilationResultHolder compile() + private CompilationOutcomeDescriptor compile() throws Exception { - CompilationCache cache = COMPILATION_CACHE.get(); if ( !needsRecompilation() ) { - return cache.lastResult; + return compilationCache.getLastResult(); } - setupCompiler(); - cache.lastSourceOutputDir = sourceOutputDir; + setupDirectories(); + compilationCache.setLastSourceOutputDir( sourceOutputDir ); - DiagnosticCollector diagnostics = new DiagnosticCollector(); - StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + CompilationOutcomeDescriptor resultHolder = + compileWithSpecificCompiler( compilationRequest, sourceOutputDir, classOutputDir ); - Iterable compilationUnits = - 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; + compilationCache.update( compilationRequest, resultHolder ); return resultHolder; } - public boolean needsRecompilation() { - return !compilationRequest.equals( COMPILATION_CACHE.get().lastRequest ); + protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir); + + boolean needsRecompilation() { + return !compilationRequest.equals( compilationCache.getLastRequest() ); } private static String getBasePath() { @@ -485,66 +436,7 @@ class CompilingStatement extends Statement { return result; } - // Using the message is not perfect when using regular expressions, - // 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 diagnostics; - private final boolean compilationSuccessful; - - public CompilationResultHolder(DiagnosticCollector 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> sourceClasses; - private final List processorOptions; - - public CompilationRequest(Set> sourceClasses, List 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 ); + return o1.getKind().compareTo( o2.getKind() ); } } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java new file mode 100644 index 000000000..81bf44f26 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java @@ -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"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java index 3363fb2de..0f30cdf67 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -39,11 +39,21 @@ import org.mapstruct.ap.testutil.assertions.JavaFileAssert; */ public class GeneratedSource implements TestRule { + private static ThreadLocal compilingStatement = new ThreadLocal();; + @Override public Statement apply(Statement base, Description description) { return base; } + static void setCompilingStatement(CompilingStatement compilingStatement) { + GeneratedSource.compilingStatement.set( compilingStatement ); + } + + static void clearCompilingStatement() { + GeneratedSource.compilingStatement.remove(); + } + /** * @param mapperClass the class annotated with {@code @Mapper} * @@ -60,6 +70,6 @@ public class GeneratedSource implements TestRule { * @return an assert for the file specified by the given path */ public JavaFileAssert forJavaFile(String path) { - return new JavaFileAssert( new File( CompilingStatement.getSourceOutputDir() + "/" + path ) ); + return new JavaFileAssert( new File( compilingStatement.get().getSourceOutputDir() + "/" + path ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java new file mode 100644 index 000000000..661b753f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java @@ -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. + *

+ * Test classes and test methods are safe to be executed in parallel. + *

+ * 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 : + *

    + *
  • Processor options to be considered during compilation via {@link ProcessorOption}.
  • + *
  • The expected compilation outcome and expected diagnostics can be specified via {@link ExpectedCompilationOutcome} + * . If no outcome is specified, a successful compilation is assumed.
  • + *
+ * + * @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(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java new file mode 100644 index 000000000..1d9131e49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java @@ -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 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 diagnostics = new DiagnosticCollector(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + + Iterable 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 asFiles(List paths) { + List classpath = new ArrayList(); + 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 filterExpectedDiagnostics(List expectedDiagnostics) { + List filtered = new ArrayList( 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"; + } +}