#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>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.12</version>
</dependency>
<!-- CDI, Weld, Arquillian -->

View File

@ -23,6 +23,7 @@ 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;
@ -45,6 +46,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
*/
public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader();
private final Class<?> klass;
private Class<?> klassToUse;
private ReplacableTestClass replacableTestClass;
/**
* @param klass the test class
@ -52,7 +56,8 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner {
* @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)}
*/
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
*/
private static Class<?> replaceClassLoaderAndClass(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() );
TEST_CLASS_LOADER.addURL( basePath );
Thread.currentThread().setContextClassLoader( TEST_CLASS_LOADER );
replaceContextClassLoader( klass );
try {
return TEST_CLASS_LOADER.loadClass( klass.getName() );
return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() );
}
catch ( ClassNotFoundException 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
protected Statement methodBlock(FrameworkMethod method) {
Statement statement = super.methodBlock( method );
statement = new CompilingStatement( statement, method, TEST_CLASS_LOADER );
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;
}

View File

@ -30,6 +30,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
@ -94,20 +95,24 @@ class CompilingStatement extends Statement {
"joda-time.jar"
);
private final Statement next;
private Statement next;
private final FrameworkMethod method;
private final ModifiableURLClassLoader classloader;
private JavaCompiler compiler;
private String sourceDir;
private String classOutputDir;
private String sourceOutputDir;
private List<File> classPath;
private CompilationRequest compilationRequest;
public CompilingStatement(Statement next, FrameworkMethod method, ModifiableURLClassLoader classloader) {
this.next = next;
public CompilingStatement(FrameworkMethod method) {
this.method = method;
this.classloader = classloader;
this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() );
}
public void setNextStatement(Statement next) {
this.next = next;
}
@Override
@ -141,11 +146,11 @@ class CompilingStatement extends Statement {
createOutputDirs();
classloader.addOutputDir( classOutputDir );
( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir );
}
protected void generateMapperImplementation() throws Exception {
CompilationResultHolder compilationResult = compile( getTestClasses(), getProcessorOptions() );
CompilationResultHolder compilationResult = compile();
CompilationOutcomeDescriptor actualResult =
CompilationOutcomeDescriptor.forResult(
@ -285,12 +290,11 @@ class CompilingStatement extends Statement {
return sourceFiles;
}
private CompilationResultHolder compile(Set<Class<?>> sourceClasses, List<String> processorOptions)
private CompilationResultHolder compile()
throws Exception {
CompilationRequest request = new CompilationRequest( sourceClasses, processorOptions );
CompilationCache cache = COMPILATION_CACHE.get();
if ( request.equals( cache.lastRequest ) ) {
if ( !needsRecompilation() ) {
return cache.lastResult;
}
@ -301,7 +305,7 @@ class CompilingStatement extends Statement {
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles( getSourceFiles( sourceClasses ) );
fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.sourceClasses ) );
try {
fileManager.setLocation( StandardLocation.CLASS_PATH, classPath );
@ -313,16 +317,27 @@ class CompilingStatement extends Statement {
}
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() ) );
CompilationResultHolder resultHolder = new CompilationResultHolder( diagnostics, task.call() );
cache.lastRequest = request;
cache.lastRequest = compilationRequest;
cache.lastResult = resultHolder;
return resultHolder;
}
public boolean needsRecompilation() {
return !compilationRequest.equals( COMPILATION_CACHE.get().lastRequest );
}
private String getBasePath() {
try {
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 FilteringParentClassLoader PARENT_CLASS_LOADER = new FilteringParentClassLoader(
ORG_MAPSTRUCT_AP_TEST );
static {
tryRegisterAsParallelCapable();
}
@ -43,7 +46,7 @@ public class ModifiableURLClassLoader extends URLClassLoader {
private final ConcurrentMap<URL, URL> addedURLs = new ConcurrentHashMap<URL, URL>();
public ModifiableURLClassLoader() {
super( new URL[] { }, new FilteringParentClassLoader() );
super( new URL[] { }, PARENT_CLASS_LOADER );
}
@Override
@ -96,9 +99,15 @@ public class ModifiableURLClassLoader extends URLClassLoader {
}
private static final class FilteringParentClassLoader extends ClassLoader {
private String excludedPackage;
public FilteringParentClassLoader(String excludedPackage) {
this.excludedPackage = excludedPackage;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ( name.startsWith( ORG_MAPSTRUCT_AP_TEST ) ) {
if ( name.startsWith( excludedPackage ) ) {
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 );
}
}
}