All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lithium.classloadertest.UnfinalizingTestRunner Maven / Gradle / Ivy

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);
	}
	


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy