1 package org.robolectric.internal; 2 3 import static java.util.Arrays.asList; 4 5 import com.google.common.collect.Lists; 6 import java.lang.reflect.Method; 7 import java.util.ArrayList; 8 import java.util.Collection; 9 import java.util.Collections; 10 import java.util.HashSet; 11 import java.util.List; 12 import java.util.ServiceLoader; 13 import javax.annotation.Nonnull; 14 import org.junit.AfterClass; 15 import org.junit.BeforeClass; 16 import org.junit.Ignore; 17 import org.junit.internal.AssumptionViolatedException; 18 import org.junit.internal.runners.model.EachTestNotifier; 19 import org.junit.runner.Description; 20 import org.junit.runner.notification.RunNotifier; 21 import org.junit.runners.BlockJUnit4ClassRunner; 22 import org.junit.runners.model.FrameworkMethod; 23 import org.junit.runners.model.InitializationError; 24 import org.junit.runners.model.Statement; 25 import org.junit.runners.model.TestClass; 26 import org.robolectric.internal.bytecode.ClassHandler; 27 import org.robolectric.internal.bytecode.InstrumentationConfiguration; 28 import org.robolectric.internal.bytecode.Interceptor; 29 import org.robolectric.internal.bytecode.Interceptors; 30 import org.robolectric.internal.bytecode.Sandbox; 31 import org.robolectric.internal.bytecode.SandboxClassLoader; 32 import org.robolectric.internal.bytecode.SandboxConfig; 33 import org.robolectric.internal.bytecode.ShadowInfo; 34 import org.robolectric.internal.bytecode.ShadowMap; 35 import org.robolectric.internal.bytecode.ShadowWrangler; 36 import org.robolectric.util.PerfStatsCollector; 37 import org.robolectric.util.PerfStatsCollector.Event; 38 import org.robolectric.util.PerfStatsCollector.Metadata; 39 import org.robolectric.util.PerfStatsCollector.Metric; 40 import org.robolectric.util.PerfStatsReporter; 41 42 public class SandboxTestRunner extends BlockJUnit4ClassRunner { 43 44 private static final ShadowMap BASE_SHADOW_MAP; 45 46 static { 47 ServiceLoader<ShadowProvider> shadowProviders = ServiceLoader.load(ShadowProvider.class); 48 BASE_SHADOW_MAP = ShadowMap.createFromShadowProviders(shadowProviders); 49 } 50 51 private final Interceptors interceptors; 52 private final List<PerfStatsReporter> perfStatsReporters; 53 private final HashSet<Class<?>> loadedTestClasses = new HashSet<>(); 54 SandboxTestRunner(Class<?> klass)55 public SandboxTestRunner(Class<?> klass) throws InitializationError { 56 super(klass); 57 58 interceptors = new Interceptors(findInterceptors()); 59 perfStatsReporters = Lists.newArrayList(getPerfStatsReporters().iterator()); 60 } 61 62 @Nonnull getPerfStatsReporters()63 protected Iterable<PerfStatsReporter> getPerfStatsReporters() { 64 return ServiceLoader.load(PerfStatsReporter.class); 65 } 66 67 @Nonnull findInterceptors()68 protected Collection<Interceptor> findInterceptors() { 69 return Collections.emptyList(); 70 } 71 72 @Nonnull getInterceptors()73 protected Interceptors getInterceptors() { 74 return interceptors; 75 } 76 77 @Override classBlock(RunNotifier notifier)78 protected Statement classBlock(RunNotifier notifier) { 79 final Statement statement = childrenInvoker(notifier); 80 return new Statement() { 81 @Override 82 public void evaluate() throws Throwable { 83 try { 84 statement.evaluate(); 85 for (Class<?> testClass : loadedTestClasses) { 86 invokeAfterClass(testClass); 87 } 88 } finally { 89 afterClass(); 90 loadedTestClasses.clear(); 91 } 92 } 93 }; 94 } 95 96 private void invokeBeforeClass(final Class clazz) throws Throwable { 97 if (!loadedTestClasses.contains(clazz)) { 98 loadedTestClasses.add(clazz); 99 100 final TestClass testClass = new TestClass(clazz); 101 final List<FrameworkMethod> befores = testClass.getAnnotatedMethods(BeforeClass.class); 102 for (FrameworkMethod before : befores) { 103 before.invokeExplosively(null); 104 } 105 } 106 } 107 108 private static void invokeAfterClass(final Class<?> clazz) throws Throwable { 109 final TestClass testClass = new TestClass(clazz); 110 final List<FrameworkMethod> afters = testClass.getAnnotatedMethods(AfterClass.class); 111 for (FrameworkMethod after : afters) { 112 after.invokeExplosively(null); 113 } 114 } 115 116 protected void afterClass() { 117 } 118 119 @Override 120 protected void runChild(FrameworkMethod method, RunNotifier notifier) { 121 Description description = describeChild(method); 122 EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description); 123 124 if (shouldIgnore(method)) { 125 eachNotifier.fireTestIgnored(); 126 } else { 127 eachNotifier.fireTestStarted(); 128 129 try { 130 methodBlock(method).evaluate(); 131 } catch (AssumptionViolatedException e) { 132 eachNotifier.addFailedAssumption(e); 133 } catch (Throwable e) { 134 eachNotifier.addFailure(e); 135 } finally { 136 eachNotifier.fireTestFinished(); 137 } 138 } 139 } 140 141 @Nonnull 142 protected Sandbox getSandbox(FrameworkMethod method) { 143 InstrumentationConfiguration instrumentationConfiguration = createClassLoaderConfig(method); 144 ClassLoader sandboxClassLoader = new SandboxClassLoader(ClassLoader.getSystemClassLoader(), instrumentationConfiguration); 145 return new Sandbox(sandboxClassLoader); 146 } 147 148 /** 149 * Create an {@link InstrumentationConfiguration} suitable for the provided {@link FrameworkMethod}. 150 * 151 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 152 * 153 * @param method the test method that's about to run 154 * @return an {@link InstrumentationConfiguration} 155 */ 156 @Nonnull 157 protected InstrumentationConfiguration createClassLoaderConfig(FrameworkMethod method) { 158 InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder() 159 .doNotAcquirePackage("java.") 160 .doNotAcquirePackage("sun.") 161 .doNotAcquirePackage("org.robolectric.annotation.") 162 .doNotAcquirePackage("org.robolectric.internal.") 163 .doNotAcquirePackage("org.robolectric.util.") 164 .doNotAcquirePackage("org.junit."); 165 166 String customPackages = System.getProperty("org.robolectric.packagesToNotAcquire", ""); 167 for (String pkg : customPackages.split(",")) { 168 if (!pkg.isEmpty()) { 169 builder.doNotAcquirePackage(pkg); 170 } 171 } 172 173 for (Class<?> shadowClass : getExtraShadows(method)) { 174 ShadowInfo shadowInfo = ShadowMap.obtainShadowInfo(shadowClass); 175 builder.addInstrumentedClass(shadowInfo.shadowedClassName); 176 } 177 178 addInstrumentedPackages(method, builder); 179 180 return builder.build(); 181 } 182 183 private void addInstrumentedPackages(FrameworkMethod method, InstrumentationConfiguration.Builder builder) { 184 SandboxConfig classConfig = getTestClass().getJavaClass().getAnnotation(SandboxConfig.class); 185 if (classConfig != null) { 186 for (String pkgName : classConfig.instrumentedPackages()) { 187 builder.addInstrumentedPackage(pkgName); 188 } 189 } 190 191 SandboxConfig methodConfig = method.getAnnotation(SandboxConfig.class); 192 if (methodConfig != null) { 193 for (String pkgName : methodConfig.instrumentedPackages()) { 194 builder.addInstrumentedPackage(pkgName); 195 } 196 } 197 } 198 199 protected void configureSandbox(Sandbox sandbox, FrameworkMethod method) { 200 ShadowMap.Builder builder = createShadowMap().newBuilder(); 201 202 // Configure shadows *BEFORE* setting the ClassLoader. This is necessary because 203 // creating the ShadowMap loads all ShadowProviders via ServiceLoader and this is 204 // not available once we install the Robolectric class loader. 205 Class<?>[] shadows = getExtraShadows(method); 206 if (shadows.length > 0) { 207 builder.addShadowClasses(shadows); 208 } 209 ShadowMap shadowMap = builder.build(); 210 sandbox.replaceShadowMap(shadowMap); 211 212 sandbox.configure(createClassHandler(shadowMap, sandbox), getInterceptors()); 213 } 214 215 @Override protected Statement methodBlock(final FrameworkMethod method) { 216 return new Statement() { 217 @Override 218 public void evaluate() throws Throwable { 219 PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance(); 220 perfStatsCollector.reset(); 221 perfStatsCollector.setEnabled(!perfStatsReporters.isEmpty()); 222 223 Event initialization = perfStatsCollector.startEvent("initialization"); 224 225 Sandbox sandbox = getSandbox(method); 226 227 // Configure sandbox *BEFORE* setting the ClassLoader. This is necessary because 228 // creating the ShadowMap loads all ShadowProviders via ServiceLoader and this is 229 // not available once we install the Robolectric class loader. 230 configureSandbox(sandbox, method); 231 232 final ClassLoader priorContextClassLoader = Thread.currentThread().getContextClassLoader(); 233 Thread.currentThread().setContextClassLoader(sandbox.getRobolectricClassLoader()); 234 235 //noinspection unchecked 236 Class bootstrappedTestClass = sandbox.bootstrappedClass(getTestClass().getJavaClass()); 237 HelperTestRunner helperTestRunner = getHelperTestRunner(bootstrappedTestClass); 238 helperTestRunner.frameworkMethod = method; 239 240 final Method bootstrappedMethod; 241 try { 242 //noinspection unchecked 243 bootstrappedMethod = bootstrappedTestClass.getMethod(method.getMethod().getName()); 244 } catch (NoSuchMethodException e) { 245 throw new RuntimeException(e); 246 } 247 248 try { 249 // Only invoke @BeforeClass once per class 250 invokeBeforeClass(bootstrappedTestClass); 251 252 beforeTest(sandbox, method, bootstrappedMethod); 253 254 initialization.finished(); 255 256 final Statement statement = helperTestRunner.methodBlock(new FrameworkMethod(bootstrappedMethod)); 257 258 // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw] 259 try { 260 statement.evaluate(); 261 } finally { 262 afterTest(method, bootstrappedMethod); 263 } 264 } finally { 265 Thread.currentThread().setContextClassLoader(priorContextClassLoader); 266 finallyAfterTest(method); 267 268 reportPerfStats(perfStatsCollector); 269 perfStatsCollector.reset(); 270 } 271 } 272 }; 273 } 274 275 private void reportPerfStats(PerfStatsCollector perfStatsCollector) { 276 if (perfStatsReporters.isEmpty()) { 277 return; 278 } 279 280 Metadata metadata = perfStatsCollector.getMetadata(); 281 Collection<Metric> metrics = perfStatsCollector.getMetrics(); 282 283 for (PerfStatsReporter perfStatsReporter : perfStatsReporters) { 284 try { 285 perfStatsReporter.report(metadata, metrics); 286 } catch (Exception e) { 287 e.printStackTrace(); 288 } 289 } 290 } 291 292 protected void beforeTest(Sandbox sandbox, FrameworkMethod method, Method bootstrappedMethod) throws Throwable { 293 } 294 295 protected void afterTest(FrameworkMethod method, Method bootstrappedMethod) { 296 } 297 298 protected void finallyAfterTest(FrameworkMethod method) { 299 } 300 301 protected HelperTestRunner getHelperTestRunner(Class bootstrappedTestClass) { 302 try { 303 return new HelperTestRunner(bootstrappedTestClass); 304 } catch (InitializationError initializationError) { 305 throw new RuntimeException(initializationError); 306 } 307 } 308 309 protected static class HelperTestRunner extends BlockJUnit4ClassRunner { 310 public FrameworkMethod frameworkMethod; 311 312 public HelperTestRunner(Class<?> klass) throws InitializationError { 313 super(klass); 314 } 315 316 // cuz accessibility 317 @Override 318 protected Statement methodBlock(FrameworkMethod method) { 319 return super.methodBlock(method); 320 } 321 } 322 323 @Nonnull 324 protected Class<?>[] getExtraShadows(FrameworkMethod method) { 325 List<Class<?>> shadowClasses = new ArrayList<>(); 326 addShadows(shadowClasses, getTestClass().getJavaClass().getAnnotation(SandboxConfig.class)); 327 addShadows(shadowClasses, method.getAnnotation(SandboxConfig.class)); 328 return shadowClasses.toArray(new Class[shadowClasses.size()]); 329 } 330 331 private void addShadows(List<Class<?>> shadowClasses, SandboxConfig annotation) { 332 if (annotation != null) { 333 shadowClasses.addAll(asList(annotation.shadows())); 334 } 335 } 336 337 protected ShadowMap createShadowMap() { 338 return BASE_SHADOW_MAP; 339 } 340 341 @Nonnull 342 protected ClassHandler createClassHandler(ShadowMap shadowMap, Sandbox sandbox) { 343 return new ShadowWrangler(shadowMap, 0, interceptors); 344 } 345 346 protected boolean shouldIgnore(FrameworkMethod method) { 347 return method.getAnnotation(Ignore.class) != null; 348 } 349 }