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