blob: 7f629a016ad5ce86d501a05ccacf4dfa8ed7cfd2 [file] [log] [blame]
package org.robolectric.internal;
import static java.util.Arrays.asList;
import com.google.common.collect.Lists;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ServiceLoader;
import javax.annotation.Nonnull;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.robolectric.internal.bytecode.ClassHandler;
import org.robolectric.internal.bytecode.InstrumentationConfiguration;
import org.robolectric.internal.bytecode.Interceptor;
import org.robolectric.internal.bytecode.Interceptors;
import org.robolectric.internal.bytecode.Sandbox;
import org.robolectric.internal.bytecode.SandboxClassLoader;
import org.robolectric.internal.bytecode.SandboxConfig;
import org.robolectric.internal.bytecode.ShadowInfo;
import org.robolectric.internal.bytecode.ShadowMap;
import org.robolectric.internal.bytecode.ShadowWrangler;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.PerfStatsCollector.Event;
import org.robolectric.util.PerfStatsCollector.Metadata;
import org.robolectric.util.PerfStatsCollector.Metric;
import org.robolectric.util.PerfStatsReporter;
public class SandboxTestRunner extends BlockJUnit4ClassRunner {
private static final ShadowMap BASE_SHADOW_MAP;
static {
ServiceLoader<ShadowProvider> shadowProviders = ServiceLoader.load(ShadowProvider.class);
BASE_SHADOW_MAP = ShadowMap.createFromShadowProviders(shadowProviders);
}
private final Interceptors interceptors;
private final List<PerfStatsReporter> perfStatsReporters;
private final HashSet<Class<?>> loadedTestClasses = new HashSet<>();
public SandboxTestRunner(Class<?> klass) throws InitializationError {
super(klass);
interceptors = new Interceptors(findInterceptors());
perfStatsReporters = Lists.newArrayList(getPerfStatsReporters().iterator());
}
@Nonnull
protected Iterable<PerfStatsReporter> getPerfStatsReporters() {
return ServiceLoader.load(PerfStatsReporter.class);
}
@Nonnull
protected Collection<Interceptor> findInterceptors() {
return Collections.emptyList();
}
@Nonnull
protected Interceptors getInterceptors() {
return interceptors;
}
@Override
protected Statement classBlock(RunNotifier notifier) {
final Statement statement = childrenInvoker(notifier);
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
statement.evaluate();
for (Class<?> testClass : loadedTestClasses) {
invokeAfterClass(testClass);
}
} finally {
afterClass();
loadedTestClasses.clear();
}
}
};
}
private void invokeBeforeClass(final Class clazz) throws Throwable {
if (!loadedTestClasses.contains(clazz)) {
loadedTestClasses.add(clazz);
final TestClass testClass = new TestClass(clazz);
final List<FrameworkMethod> befores = testClass.getAnnotatedMethods(BeforeClass.class);
for (FrameworkMethod before : befores) {
before.invokeExplosively(null);
}
}
}
private static void invokeAfterClass(final Class<?> clazz) throws Throwable {
final TestClass testClass = new TestClass(clazz);
final List<FrameworkMethod> afters = testClass.getAnnotatedMethods(AfterClass.class);
for (FrameworkMethod after : afters) {
after.invokeExplosively(null);
}
}
protected void afterClass() {
}
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
if (shouldIgnore(method)) {
eachNotifier.fireTestIgnored();
} else {
eachNotifier.fireTestStarted();
try {
methodBlock(method).evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
eachNotifier.addFailure(e);
} finally {
eachNotifier.fireTestFinished();
}
}
}
@Nonnull
protected Sandbox getSandbox(FrameworkMethod method) {
InstrumentationConfiguration instrumentationConfiguration = createClassLoaderConfig(method);
ClassLoader sandboxClassLoader = new SandboxClassLoader(ClassLoader.getSystemClassLoader(), instrumentationConfiguration);
return new Sandbox(sandboxClassLoader);
}
/**
* Create an {@link InstrumentationConfiguration} suitable for the provided {@link FrameworkMethod}.
*
* Custom TestRunner subclasses may wish to override this method to provide alternate configuration.
*
* @param method the test method that's about to run
* @return an {@link InstrumentationConfiguration}
*/
@Nonnull
protected InstrumentationConfiguration createClassLoaderConfig(FrameworkMethod method) {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder()
.doNotAcquirePackage("java.")
.doNotAcquirePackage("sun.")
.doNotAcquirePackage("org.robolectric.annotation.")
.doNotAcquirePackage("org.robolectric.internal.")
.doNotAcquirePackage("org.robolectric.util.")
.doNotAcquirePackage("org.junit.");
String customPackages = System.getProperty("org.robolectric.packagesToNotAcquire", "");
for (String pkg : customPackages.split(",")) {
if (!pkg.isEmpty()) {
builder.doNotAcquirePackage(pkg);
}
}
for (Class<?> shadowClass : getExtraShadows(method)) {
ShadowInfo shadowInfo = ShadowMap.obtainShadowInfo(shadowClass);
builder.addInstrumentedClass(shadowInfo.shadowedClassName);
}
addInstrumentedPackages(method, builder);
return builder.build();
}
private void addInstrumentedPackages(FrameworkMethod method, InstrumentationConfiguration.Builder builder) {
SandboxConfig classConfig = getTestClass().getJavaClass().getAnnotation(SandboxConfig.class);
if (classConfig != null) {
for (String pkgName : classConfig.instrumentedPackages()) {
builder.addInstrumentedPackage(pkgName);
}
}
SandboxConfig methodConfig = method.getAnnotation(SandboxConfig.class);
if (methodConfig != null) {
for (String pkgName : methodConfig.instrumentedPackages()) {
builder.addInstrumentedPackage(pkgName);
}
}
}
protected void configureSandbox(Sandbox sandbox, FrameworkMethod method) {
ShadowMap.Builder builder = createShadowMap().newBuilder();
// Configure shadows *BEFORE* setting the ClassLoader. This is necessary because
// creating the ShadowMap loads all ShadowProviders via ServiceLoader and this is
// not available once we install the Robolectric class loader.
Class<?>[] shadows = getExtraShadows(method);
if (shadows.length > 0) {
builder.addShadowClasses(shadows);
}
ShadowMap shadowMap = builder.build();
sandbox.replaceShadowMap(shadowMap);
sandbox.configure(createClassHandler(shadowMap, sandbox), getInterceptors());
}
@Override protected Statement methodBlock(final FrameworkMethod method) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance();
perfStatsCollector.reset();
perfStatsCollector.setEnabled(!perfStatsReporters.isEmpty());
Event initialization = perfStatsCollector.startEvent("initialization");
Sandbox sandbox = getSandbox(method);
// Configure sandbox *BEFORE* setting the ClassLoader. This is necessary because
// creating the ShadowMap loads all ShadowProviders via ServiceLoader and this is
// not available once we install the Robolectric class loader.
configureSandbox(sandbox, method);
final ClassLoader priorContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(sandbox.getRobolectricClassLoader());
//noinspection unchecked
Class bootstrappedTestClass = sandbox.bootstrappedClass(getTestClass().getJavaClass());
HelperTestRunner helperTestRunner = getHelperTestRunner(bootstrappedTestClass);
helperTestRunner.frameworkMethod = method;
final Method bootstrappedMethod;
try {
//noinspection unchecked
bootstrappedMethod = bootstrappedTestClass.getMethod(method.getMethod().getName());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
// Only invoke @BeforeClass once per class
invokeBeforeClass(bootstrappedTestClass);
beforeTest(sandbox, method, bootstrappedMethod);
initialization.finished();
final Statement statement = helperTestRunner.methodBlock(new FrameworkMethod(bootstrappedMethod));
// todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
try {
statement.evaluate();
} finally {
afterTest(method, bootstrappedMethod);
}
} finally {
Thread.currentThread().setContextClassLoader(priorContextClassLoader);
finallyAfterTest(method);
reportPerfStats(perfStatsCollector);
perfStatsCollector.reset();
}
}
};
}
private void reportPerfStats(PerfStatsCollector perfStatsCollector) {
if (perfStatsReporters.isEmpty()) {
return;
}
Metadata metadata = perfStatsCollector.getMetadata();
Collection<Metric> metrics = perfStatsCollector.getMetrics();
for (PerfStatsReporter perfStatsReporter : perfStatsReporters) {
try {
perfStatsReporter.report(metadata, metrics);
} catch (Exception e) {
e.printStackTrace();
}
}
}
protected void beforeTest(Sandbox sandbox, FrameworkMethod method, Method bootstrappedMethod) throws Throwable {
}
protected void afterTest(FrameworkMethod method, Method bootstrappedMethod) {
}
protected void finallyAfterTest(FrameworkMethod method) {
}
protected HelperTestRunner getHelperTestRunner(Class bootstrappedTestClass) {
try {
return new HelperTestRunner(bootstrappedTestClass);
} catch (InitializationError initializationError) {
throw new RuntimeException(initializationError);
}
}
protected static class HelperTestRunner extends BlockJUnit4ClassRunner {
public FrameworkMethod frameworkMethod;
public HelperTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
// cuz accessibility
@Override
protected Statement methodBlock(FrameworkMethod method) {
return super.methodBlock(method);
}
}
@Nonnull
protected Class<?>[] getExtraShadows(FrameworkMethod method) {
List<Class<?>> shadowClasses = new ArrayList<>();
addShadows(shadowClasses, getTestClass().getJavaClass().getAnnotation(SandboxConfig.class));
addShadows(shadowClasses, method.getAnnotation(SandboxConfig.class));
return shadowClasses.toArray(new Class[shadowClasses.size()]);
}
private void addShadows(List<Class<?>> shadowClasses, SandboxConfig annotation) {
if (annotation != null) {
shadowClasses.addAll(asList(annotation.shadows()));
}
}
protected ShadowMap createShadowMap() {
return BASE_SHADOW_MAP;
}
@Nonnull
protected ClassHandler createClassHandler(ShadowMap shadowMap, Sandbox sandbox) {
return new ShadowWrangler(shadowMap, 0, interceptors);
}
protected boolean shouldIgnore(FrameworkMethod method) {
return method.getAnnotation(Ignore.class) != null;
}
}