lithium.classloadertest.UnfinalizingTestRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multiverse-test Show documentation
Show all versions of multiverse-test Show documentation
The Multiverse Test Framework for Java
The newest version!
package lithium.classloadertest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lithium.classloadertest.annotation.ProxyableClasses;
import lithium.classloadertest.common.TransformingURLClassLoader;
import lithium.classloadertest.impl.UnfinalizingClassTransformer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.internal.runners.model.MultipleFailureException;
import org.junit.internal.runners.model.ReflectiveCallable;
import org.junit.internal.runners.statements.Fail;
import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
/**
* Sets up a test and runs it in a different classloader, allowing the user to specify a set of
* classes to filter to remove final designation on classes and methods, while also making all methods and fields
* public. This allows easy proxying and mocking of any concrete class using only EasyMock or {@link FunctionalTestClassLoader}.
*
* Classes to unfinalize are specified in the {@link ProxyableClasses} annotation. The default value for {@link ProxyableClasses}
* is an array of classes, but even non-visible classes can be unfinalized by using the attribute parameter classNames to
* specify fully qualified protected and private classes to unfinalize.
*
* Example:
*
* @RunWith(UnfinalizingTestRunner.class)
* @ProxyableClasses({NeedToBeUnfinal.class})
* public class MyTest {
*
* public void testFinalClass() {
* IMocksControl control = EasyMock.createStrictControl();
* MyTest.NeedToBeUnfinal c = control.createMock(MyTest.NeedToBeUnfinal.class);
* EasyMock.expect(f.cantOverrideThis()).andReturn("yes we can");
* control.replay();
* Assert.assertEquals("yes we can", f.cantOverrideThis());
* }
* ...
* public final class NeedToBeUnfinal {
* public final void cantOverrideThis() {
* // do some action
* }
* }
*
* While other mocking frameworks like PowerMock, mockito, JMockit, etc. also use this strategy,
* this functionality is designed for use with {@link FunctionalTestClassLoader}, which proxies classes from
* other classloaders to simulate multiple containers of the same application.
*
* This test runner can, however, be used in any test to make it possible for EasyMock to mock all objects - whether
* {@link FunctionalTestClassLoader} is used or not.
*
* @author jeff.collins
*/
@SuppressWarnings("deprecation")
public class UnfinalizingTestRunner extends BlockJUnit4ClassRunner {
private final TransformingURLClassLoader classLoader;
private final Class> testClass;
/**
* Store for use by classes that will test this class. Otherwise there's no way to test
* the outcome of the processing.
*/
private static ClassLoader lastCreatedClassLoader = null;
public UnfinalizingTestRunner(Class> klass) throws InitializationError, ClassNotFoundException {
super(klass);
if (klass.isAnnotationPresent(ProxyableClasses.class)) {
ProxyableClasses proxyableClasses = klass.getAnnotation(ProxyableClasses.class);
Class>[] classes = proxyableClasses.value();
String[] classNames = proxyableClasses.classNames();
Class>[] combined = Arrays.copyOf(classes, classes.length + classNames.length);
for (int i = classes.length; i < classes.length + classNames.length; i++) {
combined[i] = Class.forName(classNames[i - classes.length]);
}
classLoader = new TransformingURLClassLoader(combined, new UnfinalizingClassTransformer());
testClass = classLoader.loadTransformedClass(getTestClass().getName());
lastCreatedClassLoader = classLoader;
} else {
throw new RuntimeException("No @ProxyableClasses annotation is present, so no need for this runner - nothing to do.");
}
}
protected final TransformingURLClassLoader getClassLoader() {
return classLoader;
}
@Override
protected Object createTest() throws Exception {
ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
return testClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate test class", e);
} finally {
if (origClassLoader != null) {
Thread.currentThread().setContextClassLoader(origClassLoader);
}
}
}
/**
* Test helper method only - don't call this from other contexts.
* @return the last created classloader for test validation purposes
*/
static ClassLoader getLastCreatedClassLoader() {
ClassLoader cl = lastCreatedClassLoader;
lastCreatedClassLoader = null;
return cl;
}
/**
* Returns a {@link Statement} that invokes {@code method} on {@code test}
*/
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
return new InvokeMethod(convert(method), test);
}
private List convert(List methods) {
List newMethods = new ArrayList();
for(FrameworkMethod m : methods) {
newMethods.add(convert(m));
}
return newMethods;
}
private FrameworkMethod convert(FrameworkMethod method) {
Method m = method.getMethod();
try {
Method otherClassLoaderMethod = testClass.getMethod(m.getName(),
m.getParameterTypes());
return new WrappingFrameworkMethod(otherClassLoaderMethod);
} catch (Exception e) {
throw new RuntimeException("Failed getting method in other classloader: " + m.getName());
}
}
private class WrappingFrameworkMethod extends FrameworkMethod {
@Override
public Object invokeExplosively(Object target, Object... params) throws Throwable {
ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
return super.invokeExplosively(target, params);
} catch (RuntimeException e) {
throw e;
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException)e.getTargetException();
}
throw new RuntimeException("Failed execution of test method", e.getTargetException());
} catch (Exception e) {
throw new RuntimeException("Failed execution of test method", e);
} finally {
if (origClassLoader != null) {
Thread.currentThread().setContextClassLoader(origClassLoader);
}
}
}
public WrappingFrameworkMethod(Method method) {
super(method);
}
}
/**
* Returns a {@link Statement}: run all non-overridden {@code @Before}
* methods on this class and superclasses before running {@code next}; if
* any throws an Exception, stop execution and pass the exception on.
*
* @deprecated Will be private soon: use Rules instead
*/
@Override
protected Statement withBefores(FrameworkMethod method, Object target,
Statement statement) {
List befores= getTestClass().getAnnotatedMethods(
Before.class);
befores = convert(befores);
return befores.isEmpty() ? statement : new RunBefores(statement,
befores, target);
}
/**
* Returns a {@link Statement}: run all non-overridden {@code @After}
* methods on this class and superclasses before running {@code next}; all
* After methods are always executed: exceptions thrown by previous steps
* are combined, if necessary, with exceptions from After methods into a
* {@link MultipleFailureException}.
*
* @deprecated Will be private soon: use Rules instead
*/
@Override
protected Statement withAfters(FrameworkMethod method, Object target,
Statement statement) {
List afters= getTestClass().getAnnotatedMethods(
After.class);
afters = convert(afters);
return afters.isEmpty() ? statement : new RunAfters(statement, afters,
target);
}
/**
* had to override this whole method to get enough overall control to wrap
* all method invocations... javadoc says that this can be overridden
*/
@Override
protected Statement methodBlock(FrameworkMethod method) {
Object test;
try {
test= new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
Statement statement= methodInvoker(method, test);
statement= possiblyExpectingExceptions(method, test, statement);
statement= withPotentialTimeout(method, test, statement);
statement= withBefores(method, test, statement);
statement= withAfters(method, test, statement);
statement= withRules(method, test, statement);
return statement;
}
/**
* Redefined from base
*/
private Statement withRules(FrameworkMethod method, Object target,
Statement statement) {
if (!getTestClass().getAnnotatedMethods(Rule.class).isEmpty()) {
throw new RuntimeException("Junit @Rule annotation is not supported in UnfinalizingTestRunner yet");
}
return statement;
}
@Override
protected Statement withBeforeClasses(Statement statement) {
List befores = getTestClass().getAnnotatedMethods(BeforeClass.class);
befores = convert(befores);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
@Override
protected Statement withAfterClasses(Statement statement) {
List afters = getTestClass().getAnnotatedMethods(AfterClass.class);
afters = convert(afters);
return afters.isEmpty() ? statement :
new RunAfters(statement, afters, null);
}
}