#338 update to JUnit 4.12 and fix classloader leaks among the tests.

This commit is contained in:
Andreas Gudian 2015-01-01 22:23:08 +01:00
parent 668f66eb73
commit 6201e6d61d
5 changed files with 300 additions and 28 deletions

View File

@ -119,7 +119,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.11</version> <version>4.12</version>
</dependency> </dependency>
<!-- CDI, Weld, Arquillian --> <!-- CDI, Weld, Arquillian -->

View File

@ -23,6 +23,7 @@ import java.net.URL;
import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.BlockJUnit4ClassRunner;
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.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;
@ -45,6 +46,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
*/ */
public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader(); static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader();
private final Class<?> klass;
private Class<?> klassToUse;
private ReplacableTestClass replacableTestClass;
/** /**
* @param klass the test class * @param klass the test class
@ -52,7 +56,8 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
* @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)} * @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)}
*/ */
public AnnotationProcessorTestRunner(Class<?> klass) throws Exception { public AnnotationProcessorTestRunner(Class<?> klass) throws Exception {
super( replaceClassLoaderAndClass( klass ) ); super( klass );
this.klass = klass;
} }
/** /**
@ -63,17 +68,10 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
* @return the class loaded with the test class loader * @return the class loaded with the test class loader
*/ */
private static Class<?> replaceClassLoaderAndClass(Class<?> klass) { private static Class<?> replaceClassLoaderAndClass(Class<?> klass) {
String classFileName = klass.getName().replace( ".", "/" ) + ".class"; replaceContextClassLoader( klass );
URL classResource = klass.getClassLoader().getResource( classFileName );
String fullyQualifiedUrl = classResource.toExternalForm();
String basePath = fullyQualifiedUrl.substring( 0, fullyQualifiedUrl.length() - classFileName.length() );
TEST_CLASS_LOADER.addURL( basePath );
Thread.currentThread().setContextClassLoader( TEST_CLASS_LOADER );
try { try {
return TEST_CLASS_LOADER.loadClass( klass.getName() ); return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() );
} }
catch ( ClassNotFoundException e ) { catch ( ClassNotFoundException e ) {
throw new RuntimeException( e ); throw new RuntimeException( e );
@ -81,10 +79,48 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
} }
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 @Override
protected Statement methodBlock(FrameworkMethod method) { protected Statement methodBlock(FrameworkMethod method) {
Statement statement = super.methodBlock( method ); CompilingStatement statement = new CompilingStatement( method );
statement = new CompilingStatement( statement, method, TEST_CLASS_LOADER ); if ( statement.needsRecompilation() ) {
klassToUse = replaceClassLoaderAndClass( klass );
replacableTestClass.replaceClass( klassToUse );
}
method = replaceFrameworkMethod( method );
Statement next = super.methodBlock( method );
statement.setNextStatement( next );
return statement; return statement;
} }

View File

@ -30,6 +30,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.tools.DiagnosticCollector; import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler; import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaCompiler.CompilationTask;
@ -94,20 +95,24 @@ class CompilingStatement extends Statement {
"joda-time.jar" "joda-time.jar"
); );
private final Statement next; private Statement next;
private final FrameworkMethod method; private final FrameworkMethod method;
private final ModifiableURLClassLoader classloader;
private JavaCompiler compiler; private JavaCompiler compiler;
private String sourceDir; private String sourceDir;
private String classOutputDir; private String classOutputDir;
private String sourceOutputDir; private String sourceOutputDir;
private List<File> classPath; private List<File> classPath;
private CompilationRequest compilationRequest;
public CompilingStatement(Statement next, FrameworkMethod method, ModifiableURLClassLoader classloader) { public CompilingStatement(FrameworkMethod method) {
this.next = next;
this.method = method; this.method = method;
this.classloader = classloader;
this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() );
}
public void setNextStatement(Statement next) {
this.next = next;
} }
@Override @Override
@ -141,11 +146,11 @@ class CompilingStatement extends Statement {
createOutputDirs(); createOutputDirs();
classloader.addOutputDir( classOutputDir ); ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir );
} }
protected void generateMapperImplementation() throws Exception { protected void generateMapperImplementation() throws Exception {
CompilationResultHolder compilationResult = compile( getTestClasses(), getProcessorOptions() ); CompilationResultHolder compilationResult = compile();
CompilationOutcomeDescriptor actualResult = CompilationOutcomeDescriptor actualResult =
CompilationOutcomeDescriptor.forResult( CompilationOutcomeDescriptor.forResult(
@ -285,12 +290,11 @@ class CompilingStatement extends Statement {
return sourceFiles; return sourceFiles;
} }
private CompilationResultHolder compile(Set<Class<?>> sourceClasses, List<String> processorOptions) private CompilationResultHolder compile()
throws Exception { throws Exception {
CompilationRequest request = new CompilationRequest( sourceClasses, processorOptions );
CompilationCache cache = COMPILATION_CACHE.get(); CompilationCache cache = COMPILATION_CACHE.get();
if ( request.equals( cache.lastRequest ) ) { if ( !needsRecompilation() ) {
return cache.lastResult; return cache.lastResult;
} }
@ -301,7 +305,7 @@ class CompilingStatement extends Statement {
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> compilationUnits = Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles( getSourceFiles( sourceClasses ) ); fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.sourceClasses ) );
try { try {
fileManager.setLocation( StandardLocation.CLASS_PATH, classPath ); fileManager.setLocation( StandardLocation.CLASS_PATH, classPath );
@ -313,16 +317,27 @@ class CompilingStatement extends Statement {
} }
CompilationTask task = CompilationTask task =
compiler.getTask( null, fileManager, diagnostics, processorOptions, null, compilationUnits ); compiler.getTask(
null,
fileManager,
diagnostics,
compilationRequest.processorOptions,
null,
compilationUnits );
task.setProcessors( Arrays.asList( new MappingProcessor() ) ); task.setProcessors( Arrays.asList( new MappingProcessor() ) );
CompilationResultHolder resultHolder = new CompilationResultHolder( diagnostics, task.call() ); CompilationResultHolder resultHolder = new CompilationResultHolder( diagnostics, task.call() );
cache.lastRequest = request; cache.lastRequest = compilationRequest;
cache.lastResult = resultHolder; cache.lastResult = resultHolder;
return resultHolder; return resultHolder;
} }
public boolean needsRecompilation() {
return !compilationRequest.equals( COMPILATION_CACHE.get().lastRequest );
}
private String getBasePath() { private String getBasePath() {
try { try {
return new File( "." ).getCanonicalPath(); return new File( "." ).getCanonicalPath();

View File

@ -36,6 +36,9 @@ public class ModifiableURLClassLoader extends URLClassLoader {
private static final String ORG_MAPSTRUCT_AP_TEST = "org.mapstruct.ap.test."; private static final String ORG_MAPSTRUCT_AP_TEST = "org.mapstruct.ap.test.";
private static final FilteringParentClassLoader PARENT_CLASS_LOADER = new FilteringParentClassLoader(
ORG_MAPSTRUCT_AP_TEST );
static { static {
tryRegisterAsParallelCapable(); tryRegisterAsParallelCapable();
} }
@ -43,7 +46,7 @@ public class ModifiableURLClassLoader extends URLClassLoader {
private final ConcurrentMap<URL, URL> addedURLs = new ConcurrentHashMap<URL, URL>(); private final ConcurrentMap<URL, URL> addedURLs = new ConcurrentHashMap<URL, URL>();
public ModifiableURLClassLoader() { public ModifiableURLClassLoader() {
super( new URL[] { }, new FilteringParentClassLoader() ); super( new URL[] { }, PARENT_CLASS_LOADER );
} }
@Override @Override
@ -96,9 +99,15 @@ public class ModifiableURLClassLoader extends URLClassLoader {
} }
private static final class FilteringParentClassLoader extends ClassLoader { private static final class FilteringParentClassLoader extends ClassLoader {
private String excludedPackage;
public FilteringParentClassLoader(String excludedPackage) {
this.excludedPackage = excludedPackage;
}
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ( name.startsWith( ORG_MAPSTRUCT_AP_TEST ) ) { if ( name.startsWith( excludedPackage ) ) {
return null; return null;
} }

View File

@ -0,0 +1,212 @@
/**
* Copyright 2012-2014 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.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;
/**
* A {@link TestClass} where the wrapped Class can be replaced
*
* @author Andreas Gudian
*/
class ReplacableTestClass extends TestClass {
private TestClass delegate;
/**
* @see TestClass#TestClass(Class)
*/
ReplacableTestClass(Class<?> clazz) {
super( clazz );
}
/**
* @param clazz the new class
*/
void replaceClass(Class<?> clazz) {
delegate = new TestClass( clazz );
}
@Override
public List<FrameworkMethod> getAnnotatedMethods() {
if ( null == delegate ) {
return super.getAnnotatedMethods();
}
else {
return delegate.getAnnotatedMethods();
}
}
@Override
public List<FrameworkMethod> getAnnotatedMethods(Class<? extends Annotation> annotationClass) {
if ( null == delegate ) {
return super.getAnnotatedMethods( annotationClass );
}
else {
return delegate.getAnnotatedMethods( annotationClass );
}
}
@Override
public List<FrameworkField> getAnnotatedFields() {
if ( null == delegate ) {
return super.getAnnotatedFields();
}
else {
return delegate.getAnnotatedFields();
}
}
@Override
public List<FrameworkField> getAnnotatedFields(Class<? extends Annotation> annotationClass) {
if ( null == delegate ) {
return super.getAnnotatedFields( annotationClass );
}
else {
return delegate.getAnnotatedFields( annotationClass );
}
}
@Override
public Class<?> getJavaClass() {
if ( null == delegate ) {
return super.getJavaClass();
}
else {
return delegate.getJavaClass();
}
}
@Override
public String getName() {
if ( null == delegate ) {
return super.getName();
}
else {
return delegate.getName();
}
}
@Override
public Constructor<?> getOnlyConstructor() {
if ( null == delegate ) {
return super.getOnlyConstructor();
}
else {
return delegate.getOnlyConstructor();
}
}
@Override
public Annotation[] getAnnotations() {
if ( null == delegate ) {
return super.getAnnotations();
}
else {
return delegate.getAnnotations();
}
}
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
if ( null == delegate ) {
return super.getAnnotation( annotationType );
}
else {
return delegate.getAnnotation( annotationType );
}
}
@Override
public <T> List<T> getAnnotatedFieldValues(Object test, Class<? extends Annotation> annotationClass,
Class<T> valueClass) {
if ( null == delegate ) {
return super.getAnnotatedFieldValues( test, annotationClass, valueClass );
}
else {
return delegate.getAnnotatedFieldValues( test, annotationClass, valueClass );
}
}
@Override
public <T> List<T> getAnnotatedMethodValues(Object test, Class<? extends Annotation> annotationClass,
Class<T> valueClass) {
if ( null == delegate ) {
return super.getAnnotatedMethodValues( test, annotationClass, valueClass );
}
else {
return delegate.getAnnotatedMethodValues( test, annotationClass, valueClass );
}
}
@Override
public String toString() {
if ( null == delegate ) {
return super.toString();
}
else {
return delegate.toString();
}
}
@Override
public boolean isPublic() {
if ( null == delegate ) {
return super.isPublic();
}
else {
return delegate.isPublic();
}
}
@Override
public boolean isANonStaticInnerClass() {
if ( null == delegate ) {
return super.isANonStaticInnerClass();
}
else {
return delegate.isANonStaticInnerClass();
}
}
@Override
public int hashCode() {
if ( null == delegate ) {
return super.hashCode();
}
else {
return delegate.hashCode();
}
}
@Override
public boolean equals(Object obj) {
if ( null == delegate ) {
return super.equals( obj );
}
else {
return delegate.equals( obj );
}
}
}