1 package org.robolectric; 2 3 4 import com.google.common.annotations.VisibleForTesting; 5 import com.google.common.collect.ImmutableMap; 6 import com.google.common.collect.Iterators; 7 import java.io.File; 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.lang.reflect.Constructor; 11 import java.lang.reflect.InvocationTargetException; 12 import java.lang.reflect.Method; 13 import java.net.URL; 14 import java.security.SecureRandom; 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.HashMap; 18 import java.util.List; 19 import java.util.Map; 20 import java.util.Properties; 21 import java.util.ServiceLoader; 22 import javax.annotation.Nonnull; 23 import org.junit.Ignore; 24 import org.junit.runners.model.FrameworkMethod; 25 import org.junit.runners.model.InitializationError; 26 import org.junit.runners.model.Statement; 27 import org.robolectric.android.AndroidInterceptors; 28 import org.robolectric.android.internal.ParallelUniverse; 29 import org.robolectric.annotation.Config; 30 import org.robolectric.internal.AndroidConfigurer; 31 import org.robolectric.internal.BuckManifestFactory; 32 import org.robolectric.internal.DefaultManifestFactory; 33 import org.robolectric.internal.ManifestFactory; 34 import org.robolectric.internal.ManifestIdentifier; 35 import org.robolectric.internal.MavenManifestFactory; 36 import org.robolectric.internal.ParallelUniverseInterface; 37 import org.robolectric.internal.SandboxFactory; 38 import org.robolectric.internal.SandboxTestRunner; 39 import org.robolectric.internal.SdkConfig; 40 import org.robolectric.internal.SdkEnvironment; 41 import org.robolectric.internal.ShadowProvider; 42 import org.robolectric.internal.bytecode.ClassHandler; 43 import org.robolectric.internal.bytecode.InstrumentationConfiguration; 44 import org.robolectric.internal.bytecode.InstrumentationConfiguration.Builder; 45 import org.robolectric.internal.bytecode.Interceptor; 46 import org.robolectric.internal.bytecode.Sandbox; 47 import org.robolectric.internal.bytecode.SandboxClassLoader; 48 import org.robolectric.internal.bytecode.ShadowMap; 49 import org.robolectric.internal.bytecode.ShadowWrangler; 50 import org.robolectric.internal.dependency.CachedDependencyResolver; 51 import org.robolectric.internal.dependency.DependencyResolver; 52 import org.robolectric.internal.dependency.LocalDependencyResolver; 53 import org.robolectric.internal.dependency.PropertiesDependencyResolver; 54 import org.robolectric.manifest.AndroidManifest; 55 import org.robolectric.res.Fs; 56 import org.robolectric.res.FsFile; 57 import org.robolectric.util.Logger; 58 import org.robolectric.util.PerfStatsCollector; 59 import org.robolectric.util.ReflectionHelpers; 60 61 /** 62 * Loads and runs a test in a {@link SandboxClassLoader} in order to 63 * provide a simulation of the Android runtime environment. 64 */ 65 public class RobolectricTestRunner extends SandboxTestRunner { 66 67 public static final String CONFIG_PROPERTIES = "robolectric.properties"; 68 69 private static ApkLoader apkLoader; 70 private static final Map<ManifestIdentifier, AndroidManifest> appManifestsCache = new HashMap<>(); 71 72 private final SdkPicker sdkPicker; 73 private final ConfigMerger configMerger; 74 private ServiceLoader<ShadowProvider> providers; 75 private transient DependencyResolver dependencyResolver; 76 private final ResourcesMode resourcesMode = getResourcesMode(); 77 private boolean alwaysIncludeVariantMarkersInName = 78 Boolean.parseBoolean( 79 System.getProperty("robolectric.alwaysIncludeVariantMarkersInTestName", "false")); 80 81 static { SecureRandom()82 new SecureRandom(); // this starts up the Poller SunPKCS11-Darwin thread early, outside of any Robolectric classloader 83 } 84 85 /** 86 * Creates a runner to run {@code testClass}. Use the {@link Config} annotation to configure. 87 * 88 * @param testClass the test class to be run 89 * @throws InitializationError if junit says so 90 */ RobolectricTestRunner(final Class<?> testClass)91 public RobolectricTestRunner(final Class<?> testClass) throws InitializationError { 92 super(testClass); 93 this.configMerger = createConfigMerger(); 94 this.sdkPicker = createSdkPicker(); 95 96 synchronized (RobolectricTestRunner.class) { 97 if (apkLoader == null) { 98 apkLoader = new ApkLoader(getJarResolver()); 99 } 100 } 101 } 102 getJarResolver()103 protected DependencyResolver getJarResolver() { 104 if (dependencyResolver == null) { 105 if (Boolean.getBoolean("robolectric.offline")) { 106 String propPath = System.getProperty("robolectric-deps.properties"); 107 if (propPath != null) { 108 try { 109 dependencyResolver = new PropertiesDependencyResolver( 110 Fs.newFile(propPath), 111 null); 112 } catch (IOException e) { 113 throw new RuntimeException("couldn't read dependencies" , e); 114 } 115 } else { 116 String dependencyDir = System.getProperty("robolectric.dependency.dir", "."); 117 dependencyResolver = new LocalDependencyResolver(new File(dependencyDir)); 118 } 119 } else { 120 // cacheDir bumped to 'robolectric-2' to invalidate caching of bad URLs on windows prior 121 // to fix for https://github.com/robolectric/robolectric/issues/3955 122 File cacheDir = new File(new File(System.getProperty("java.io.tmpdir")), "robolectric-2"); 123 124 Class<?> mavenDependencyResolverClass = ReflectionHelpers.loadClass(RobolectricTestRunner.class.getClassLoader(), 125 "org.robolectric.internal.dependency.MavenDependencyResolver"); 126 DependencyResolver dependencyResolver = (DependencyResolver) ReflectionHelpers.callConstructor(mavenDependencyResolverClass); 127 if (cacheDir.exists() || cacheDir.mkdir()) { 128 Logger.info("Dependency cache location: %s", cacheDir.getAbsolutePath()); 129 this.dependencyResolver = new CachedDependencyResolver(dependencyResolver, cacheDir, 60 * 60 * 24 * 1000); 130 } else { 131 this.dependencyResolver = dependencyResolver; 132 } 133 } 134 135 URL buildPathPropertiesUrl = getClass().getClassLoader().getResource("robolectric-deps.properties"); 136 if (buildPathPropertiesUrl != null) { 137 Logger.info("Using Robolectric classes from %s", buildPathPropertiesUrl.getPath()); 138 139 FsFile propertiesFile = Fs.fileFromPath(buildPathPropertiesUrl.getFile()); 140 try { 141 dependencyResolver = new PropertiesDependencyResolver(propertiesFile, dependencyResolver); 142 } catch (IOException e) { 143 throw new RuntimeException("couldn't read " + buildPathPropertiesUrl, e); 144 } 145 } 146 } 147 148 return dependencyResolver; 149 } 150 151 /** 152 * Create a {@link ClassHandler} appropriate for the given arguments. 153 * 154 * Robolectric may chose to cache the returned instance, keyed by <tt>shadowMap</tt> and <tt>sdkConfig</tt>. 155 * 156 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 157 * 158 * @param shadowMap the {@link ShadowMap} in effect for this test 159 * @param sandbox the {@link SdkConfig} in effect for this test 160 * @return an appropriate {@link ClassHandler}. This implementation returns a {@link ShadowWrangler}. 161 * @since 2.3 162 */ 163 @Override 164 @Nonnull createClassHandler(ShadowMap shadowMap, Sandbox sandbox)165 protected ClassHandler createClassHandler(ShadowMap shadowMap, Sandbox sandbox) { 166 return new ShadowWrangler(shadowMap, ((SdkEnvironment) sandbox).getSdkConfig().getApiLevel(), getInterceptors()); 167 } 168 169 /** 170 * Create a {@link ConfigMerger} for calculating the {@link Config} tests. 171 * 172 * Alternate implementations may be provided using a ServiceLoader. 173 * 174 * @return a {@link ConfigMerger} 175 * @since 3.2 176 */ 177 @Nonnull createConfigMerger()178 private ConfigMerger createConfigMerger() { 179 ServiceLoader<ConfigMerger> serviceLoader = ServiceLoader.load(ConfigMerger.class); 180 ConfigMerger merger; 181 if (serviceLoader != null && serviceLoader.iterator().hasNext()) { 182 merger = Iterators.getOnlyElement(serviceLoader.iterator()); 183 } else { 184 merger = new ConfigMerger(); 185 } 186 return merger; 187 } 188 189 /** 190 * Create a {@link SdkPicker} for determining which SDKs will be tested. 191 * 192 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 193 * 194 * @return an {@link SdkPicker}. 195 * @since 3.2 196 */ 197 @Nonnull createSdkPicker()198 protected SdkPicker createSdkPicker() { 199 return new SdkPicker( 200 SdkConfig.getSupportedSdkConfigs(), 201 SdkPicker.enumerateEnabledSdks(System.getProperty("robolectric.enabledSdks"))); 202 } 203 204 @Override 205 @Nonnull // todo findInterceptors()206 protected Collection<Interceptor> findInterceptors() { 207 return AndroidInterceptors.all(); 208 } 209 210 /** 211 * Create an {@link InstrumentationConfiguration} suitable for the provided 212 * {@link FrameworkMethod}. 213 * 214 * Adds configuration for Android using {@link AndroidConfigurer}. 215 * 216 * Custom TestRunner subclasses may wish to override this method to provide additional 217 * configuration. 218 * 219 * @param method the test method that's about to run 220 * @return an {@link InstrumentationConfiguration} 221 */ 222 @Override @Nonnull createClassLoaderConfig(final FrameworkMethod method)223 protected InstrumentationConfiguration createClassLoaderConfig(final FrameworkMethod method) { 224 Builder builder = new Builder(super.createClassLoaderConfig(method)); 225 AndroidConfigurer.configure(builder, getInterceptors()); 226 AndroidConfigurer.withConfig(builder, ((RobolectricFrameworkMethod) method).config); 227 return builder.build(); 228 } 229 230 @Override configureSandbox(Sandbox sandbox, FrameworkMethod method)231 protected void configureSandbox(Sandbox sandbox, FrameworkMethod method) { 232 SdkEnvironment sdkEnvironment = (SdkEnvironment) sandbox; 233 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) method; 234 boolean isLegacy = roboMethod.isLegacy(); 235 roboMethod.parallelUniverseInterface = getHooksInterface(sdkEnvironment); 236 roboMethod.parallelUniverseInterface.setSdkConfig(roboMethod.sdkConfig); 237 roboMethod.parallelUniverseInterface.setResourcesMode(isLegacy); 238 239 super.configureSandbox(sandbox, method); 240 } 241 242 /** 243 * An instance of the returned class will be created for each test invocation. 244 * 245 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 246 * 247 * @return a class which implements {@link TestLifecycle}. This implementation returns a {@link DefaultTestLifecycle}. 248 */ 249 @Nonnull getTestLifecycleClass()250 protected Class<? extends TestLifecycle> getTestLifecycleClass() { 251 return DefaultTestLifecycle.class; 252 } 253 254 enum ResourcesMode { 255 legacy, 256 binary, 257 best, 258 both; 259 260 static final ResourcesMode DEFAULT = best; 261 getFromProperties()262 private static ResourcesMode getFromProperties() { 263 String resourcesMode = System.getProperty("robolectric.resourcesMode"); 264 return resourcesMode == null ? DEFAULT : valueOf(resourcesMode); 265 } 266 includeLegacy(AndroidManifest appManifest)267 boolean includeLegacy(AndroidManifest appManifest) { 268 return appManifest.supportsLegacyResourcesMode() 269 && 270 (this == legacy 271 || (this == best && !appManifest.supportsBinaryResourcesMode()) 272 || this == both); 273 } 274 includeBinary(AndroidManifest appManifest)275 boolean includeBinary(AndroidManifest appManifest) { 276 return appManifest.supportsBinaryResourcesMode() 277 && (this == binary || this == best || this == both); 278 } 279 } 280 281 @Override getChildren()282 protected List<FrameworkMethod> getChildren() { 283 List<FrameworkMethod> children = new ArrayList<>(); 284 for (FrameworkMethod frameworkMethod : super.getChildren()) { 285 try { 286 Config config = getConfig(frameworkMethod.getMethod()); 287 AndroidManifest appManifest = getAppManifest(config); 288 289 List<SdkConfig> sdksToRun = sdkPicker.selectSdks(config, appManifest); 290 RobolectricFrameworkMethod last = null; 291 for (SdkConfig sdkConfig : sdksToRun) { 292 if (resourcesMode.includeLegacy(appManifest)) { 293 children.add( 294 last = 295 new RobolectricFrameworkMethod( 296 frameworkMethod.getMethod(), 297 appManifest, 298 sdkConfig, 299 config, 300 ResourcesMode.legacy, 301 resourcesMode, 302 alwaysIncludeVariantMarkersInName)); 303 } 304 if (resourcesMode.includeBinary(appManifest)) { 305 children.add( 306 last = 307 new RobolectricFrameworkMethod( 308 frameworkMethod.getMethod(), 309 appManifest, 310 sdkConfig, 311 config, 312 ResourcesMode.binary, 313 resourcesMode, 314 alwaysIncludeVariantMarkersInName)); 315 } 316 } 317 if (last != null) { 318 last.dontIncludeVariantMarkersInTestName(); 319 } 320 } catch (IllegalArgumentException e) { 321 throw new IllegalArgumentException("failed to configure " + 322 getTestClass().getName() + "." + frameworkMethod.getMethod().getName() + 323 ": " + e.getMessage(), e); 324 } 325 } 326 return children; 327 } 328 shouldIgnore(FrameworkMethod method)329 @Override protected boolean shouldIgnore(FrameworkMethod method) { 330 return method.getAnnotation(Ignore.class) != null; 331 } 332 333 @Override 334 @Nonnull getSandbox(FrameworkMethod method)335 protected SdkEnvironment getSandbox(FrameworkMethod method) { 336 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) method; 337 SdkConfig sdkConfig = roboMethod.sdkConfig; 338 return getSandboxFactory().getSdkEnvironment( 339 createClassLoaderConfig(method), sdkConfig, roboMethod.isLegacy(), getJarResolver()); 340 } 341 getSandboxFactory()342 protected SandboxFactory getSandboxFactory() { 343 return SandboxFactory.INSTANCE; 344 } 345 346 @Override beforeTest(Sandbox sandbox, FrameworkMethod method, Method bootstrappedMethod)347 protected void beforeTest(Sandbox sandbox, FrameworkMethod method, Method bootstrappedMethod) throws Throwable { 348 SdkEnvironment sdkEnvironment = (SdkEnvironment) sandbox; 349 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) method; 350 351 PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance(); 352 SdkConfig sdkConfig = roboMethod.sdkConfig; 353 perfStatsCollector.putMetadata( 354 AndroidMetadata.class, 355 new AndroidMetadata( 356 ImmutableMap.of("ro.build.version.sdk", "" + sdkConfig.getApiLevel()), 357 roboMethod.resourcesMode.name())); 358 359 System.out.println( 360 "[Robolectric] " + roboMethod.getDeclaringClass().getName() + "." 361 + roboMethod.getMethod().getName() + ": sdk=" + sdkConfig.getApiLevel() 362 + "; resources=" + roboMethod.resourcesMode); 363 364 if (roboMethod.resourcesMode == ResourcesMode.legacy) { 365 System.out.println( 366 "[Robolectric] NOTICE: legacy resources mode is deprecated; see http://robolectric.org/migrating/#migrating-to-40"); 367 } 368 369 roboMethod.parallelUniverseInterface = getHooksInterface(sdkEnvironment); 370 Class<TestLifecycle> cl = sdkEnvironment.bootstrappedClass(getTestLifecycleClass()); 371 roboMethod.testLifecycle = ReflectionHelpers.newInstance(cl); 372 373 providers = ServiceLoader.load(ShadowProvider.class, sdkEnvironment.getRobolectricClassLoader()); 374 375 roboMethod.parallelUniverseInterface.setSdkConfig(sdkConfig); 376 377 AndroidManifest appManifest = roboMethod.getAppManifest(); 378 379 roboMethod.parallelUniverseInterface.setUpApplicationState( 380 apkLoader, 381 bootstrappedMethod, 382 roboMethod.config, appManifest, 383 sdkEnvironment 384 ); 385 386 roboMethod.testLifecycle.beforeTest(bootstrappedMethod); 387 } 388 389 @Override afterTest(FrameworkMethod method, Method bootstrappedMethod)390 protected void afterTest(FrameworkMethod method, Method bootstrappedMethod) { 391 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) method; 392 393 try { 394 roboMethod.parallelUniverseInterface.tearDownApplication(); 395 } finally { 396 internalAfterTest(method, bootstrappedMethod); 397 } 398 } 399 resetStaticState()400 private void resetStaticState() { 401 for (ShadowProvider provider : providers) { 402 provider.reset(); 403 } 404 } 405 406 @Override finallyAfterTest(FrameworkMethod method)407 protected void finallyAfterTest(FrameworkMethod method) { 408 try { 409 // reset static state afterward too, so statics don't defeat GC? 410 PerfStatsCollector.getInstance() 411 .measure("reset Android state (after test)", this::resetStaticState); 412 } finally { 413 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) method; 414 roboMethod.testLifecycle = null; 415 roboMethod.parallelUniverseInterface = null; 416 } 417 } 418 getHelperTestRunner(Class bootstrappedTestClass)419 @Override protected SandboxTestRunner.HelperTestRunner getHelperTestRunner(Class bootstrappedTestClass) { 420 try { 421 return new HelperTestRunner(bootstrappedTestClass); 422 } catch (InitializationError initializationError) { 423 throw new RuntimeException(initializationError); 424 } 425 } 426 427 /** 428 * Detects which build system is in use and returns the appropriate ManifestFactory implementation. 429 * 430 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 431 * 432 * @param config Specification of the SDK version, manifest file, package name, etc. 433 */ getManifestFactory(Config config)434 protected ManifestFactory getManifestFactory(Config config) { 435 Properties buildSystemApiProperties = getBuildSystemApiProperties(); 436 if (buildSystemApiProperties != null) { 437 return new DefaultManifestFactory(buildSystemApiProperties); 438 } 439 440 if (BuckManifestFactory.isBuck()) { 441 return new BuckManifestFactory(); 442 } else { 443 return new MavenManifestFactory(); 444 } 445 } 446 getBuildSystemApiProperties()447 protected Properties getBuildSystemApiProperties() { 448 InputStream resourceAsStream = getClass().getResourceAsStream("/com/android/tools/test_config.properties"); 449 if (resourceAsStream == null) { 450 return null; 451 } 452 453 try { 454 Properties properties = new Properties(); 455 properties.load(resourceAsStream); 456 return properties; 457 } catch (IOException e) { 458 return null; 459 } finally { 460 try { 461 resourceAsStream.close(); 462 } catch (IOException e) { 463 // ignore 464 } 465 } 466 } 467 468 /** @deprecated Do not override; provide your own {@link ManifestFactory} instead. */ 469 @Deprecated getAppManifest(Config config)470 protected AndroidManifest getAppManifest(Config config) { 471 ManifestFactory manifestFactory = getManifestFactory(config); 472 ManifestIdentifier identifier = manifestFactory.identify(config); 473 474 return cachedCreateAppManifest(identifier); 475 } 476 cachedCreateAppManifest(ManifestIdentifier identifier)477 private AndroidManifest cachedCreateAppManifest(ManifestIdentifier identifier) { 478 synchronized (appManifestsCache) { 479 AndroidManifest appManifest; 480 appManifest = appManifestsCache.get(identifier); 481 if (appManifest == null) { 482 appManifest = createAndroidManifest(identifier); 483 appManifestsCache.put(identifier, appManifest); 484 } 485 486 return appManifest; 487 } 488 } 489 490 /** 491 * Internal use only. 492 * @deprecated Do not use. 493 */ 494 @Deprecated 495 @VisibleForTesting createAndroidManifest(ManifestIdentifier manifestIdentifier)496 public static AndroidManifest createAndroidManifest(ManifestIdentifier manifestIdentifier) { 497 List<ManifestIdentifier> libraries = manifestIdentifier.getLibraries(); 498 499 List<AndroidManifest> libraryManifests = new ArrayList<>(); 500 for (ManifestIdentifier library : libraries) { 501 libraryManifests.add(createAndroidManifest(library)); 502 } 503 504 return new AndroidManifest(manifestIdentifier.getManifestFile(), manifestIdentifier.getResDir(), 505 manifestIdentifier.getAssetDir(), libraryManifests, manifestIdentifier.getPackageName(), 506 manifestIdentifier.getApkFile()); 507 } 508 509 510 /** 511 * Compute the effective Robolectric configuration for a given test method. 512 * 513 * Configuration information is collected from package-level <tt>robolectric.properties</tt> files 514 * and {@link Config} annotations on test classes, superclasses, and methods. 515 * 516 * Custom TestRunner subclasses may wish to override this method to provide alternate configuration. 517 * 518 * @param method the test method 519 * @return the effective Robolectric configuration for the given test method 520 * @since 2.0 521 */ getConfig(Method method)522 public Config getConfig(Method method) { 523 return configMerger.getConfig(getTestClass().getJavaClass(), method, buildGlobalConfig()); 524 } 525 526 /** 527 * Provides the base Robolectric configuration {@link Config} used for all tests. 528 * 529 * Configuration provided for specific packages, test classes, and test method 530 * configurations will override values provided here. 531 * 532 * Custom TestRunner subclasses may wish to override this method to provide 533 * alternate configuration. Consider using a {@link Config.Builder}. 534 * 535 * The default implementation has appropriate values for most use cases. 536 * 537 * @return global {@link Config} object 538 * @since 3.1.3 539 */ buildGlobalConfig()540 protected Config buildGlobalConfig() { 541 return new Config.Builder().build(); 542 } 543 544 @Override @Nonnull getExtraShadows(FrameworkMethod frameworkMethod)545 protected Class<?>[] getExtraShadows(FrameworkMethod frameworkMethod) { 546 Config config = ((RobolectricFrameworkMethod) frameworkMethod).config; 547 return config.shadows(); 548 } 549 getHooksInterface(SdkEnvironment sdkEnvironment)550 ParallelUniverseInterface getHooksInterface(SdkEnvironment sdkEnvironment) { 551 ClassLoader robolectricClassLoader = sdkEnvironment.getRobolectricClassLoader(); 552 try { 553 Class<?> clazz = robolectricClassLoader.loadClass(ParallelUniverse.class.getName()); 554 Class<? extends ParallelUniverseInterface> typedClazz = clazz.asSubclass(ParallelUniverseInterface.class); 555 Constructor<? extends ParallelUniverseInterface> constructor = typedClazz.getConstructor(); 556 return constructor.newInstance(); 557 } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { 558 throw new RuntimeException(e); 559 } 560 } 561 internalAfterTest(FrameworkMethod frameworkMethod, Method method)562 protected void internalAfterTest(FrameworkMethod frameworkMethod, Method method) { 563 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) frameworkMethod; 564 roboMethod.testLifecycle.afterTest(method); 565 } 566 567 @Override afterClass()568 protected void afterClass() { 569 } 570 571 @Override createTest()572 public Object createTest() throws Exception { 573 throw new UnsupportedOperationException("this should always be invoked on the HelperTestRunner!"); 574 } 575 576 @VisibleForTesting getResourcesMode()577 ResourcesMode getResourcesMode() { 578 return ResourcesMode.getFromProperties(); 579 } 580 581 public static class HelperTestRunner extends SandboxTestRunner.HelperTestRunner { HelperTestRunner(Class bootstrappedTestClass)582 public HelperTestRunner(Class bootstrappedTestClass) throws InitializationError { 583 super(bootstrappedTestClass); 584 } 585 createTest()586 @Override protected Object createTest() throws Exception { 587 Object test = super.createTest(); 588 RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) this.frameworkMethod; 589 roboMethod.testLifecycle.prepareTest(test); 590 return test; 591 } 592 593 @Override methodInvoker(FrameworkMethod method, Object test)594 protected Statement methodInvoker(FrameworkMethod method, Object test) { 595 final Statement invoker = super.methodInvoker(method, test); 596 final RobolectricFrameworkMethod roboMethod = (RobolectricFrameworkMethod) this.frameworkMethod; 597 return new Statement() { 598 @Override 599 public void evaluate() throws Throwable { 600 Thread orig = roboMethod.parallelUniverseInterface.getMainThread(); 601 roboMethod.parallelUniverseInterface.setMainThread(Thread.currentThread()); 602 try { 603 invoker.evaluate(); 604 } finally { 605 roboMethod.parallelUniverseInterface.setMainThread(orig); 606 } 607 } 608 }; 609 } 610 } 611 612 static class RobolectricFrameworkMethod extends FrameworkMethod { 613 private final @Nonnull AndroidManifest appManifest; 614 final @Nonnull SdkConfig sdkConfig; 615 final @Nonnull Config config; 616 final ResourcesMode resourcesMode; 617 private final ResourcesMode defaultResourcesMode; 618 private final boolean alwaysIncludeVariantMarkersInName; 619 620 private boolean includeVariantMarkersInTestName = true; 621 TestLifecycle testLifecycle; 622 ParallelUniverseInterface parallelUniverseInterface; 623 624 RobolectricFrameworkMethod( 625 @Nonnull Method method, 626 @Nonnull AndroidManifest appManifest, 627 @Nonnull SdkConfig sdkConfig, 628 @Nonnull Config config, 629 ResourcesMode resourcesMode, 630 ResourcesMode defaultResourcesMode, 631 boolean alwaysIncludeVariantMarkersInName) { 632 super(method); 633 this.appManifest = appManifest; 634 this.sdkConfig = sdkConfig; 635 this.config = config; 636 this.resourcesMode = resourcesMode; 637 this.defaultResourcesMode = defaultResourcesMode; 638 this.alwaysIncludeVariantMarkersInName = alwaysIncludeVariantMarkersInName; 639 } 640 641 @Override 642 public String getName() { 643 // IDE focused test runs rely on preservation of the test name; we'll use the 644 // latest supported SDK for focused test runs 645 StringBuilder buf = new StringBuilder(super.getName()); 646 647 if (includeVariantMarkersInTestName || alwaysIncludeVariantMarkersInName) { 648 buf.append("[").append(sdkConfig.getApiLevel()).append("]"); 649 650 if (defaultResourcesMode == ResourcesMode.both) { 651 buf.append("[").append(resourcesMode.name()).append("]"); 652 } 653 } 654 655 return buf.toString(); 656 } 657 658 void dontIncludeVariantMarkersInTestName() { 659 includeVariantMarkersInTestName = false; 660 } 661 662 @Nonnull 663 AndroidManifest getAppManifest() { 664 return appManifest; 665 } 666 667 public boolean isLegacy() { 668 return resourcesMode == ResourcesMode.legacy; 669 } 670 671 @Override 672 public boolean equals(Object o) { 673 if (this == o) return true; 674 if (o == null || getClass() != o.getClass()) return false; 675 if (!super.equals(o)) return false; 676 677 RobolectricFrameworkMethod that = (RobolectricFrameworkMethod) o; 678 679 return sdkConfig.equals(that.sdkConfig) && resourcesMode == that.resourcesMode; 680 } 681 682 @Override 683 public int hashCode() { 684 int result = super.hashCode(); 685 result = 31 * result + sdkConfig.hashCode(); 686 result = 31 * result + resourcesMode.ordinal(); 687 return result; 688 } 689 690 @Override 691 public String toString() { 692 return getName(); 693 } 694 } 695 696 } 697