();
+
+ 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 extends JavaFileObject> 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 extends JavaFileObject> 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 extends JavaFileObject> 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 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 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";
+ }
+}