• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric;
2 
3 import static com.google.common.truth.Truth.assertThat;
4 import static java.util.stream.Collectors.toSet;
5 import static org.junit.Assert.fail;
6 import static org.mockito.Mockito.mock;
7 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
8 
9 import android.annotation.SuppressLint;
10 import android.app.Application;
11 import android.os.Build;
12 import android.os.Handler;
13 import android.os.Looper;
14 import java.io.FileOutputStream;
15 import java.lang.reflect.Method;
16 import java.nio.charset.StandardCharsets;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.nio.file.spi.FileSystemProvider;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Set;
24 import java.util.jar.JarEntry;
25 import java.util.jar.JarOutputStream;
26 import javax.annotation.Nonnull;
27 import javax.inject.Inject;
28 import javax.inject.Named;
29 import org.junit.After;
30 import org.junit.AfterClass;
31 import org.junit.AssumptionViolatedException;
32 import org.junit.Before;
33 import org.junit.BeforeClass;
34 import org.junit.FixMethodOrder;
35 import org.junit.Ignore;
36 import org.junit.Rule;
37 import org.junit.Test;
38 import org.junit.runner.Description;
39 import org.junit.runner.Result;
40 import org.junit.runner.RunWith;
41 import org.junit.runner.notification.Failure;
42 import org.junit.runner.notification.RunListener;
43 import org.junit.runner.notification.RunNotifier;
44 import org.junit.runners.JUnit4;
45 import org.junit.runners.MethodSorters;
46 import org.robolectric.RobolectricTestRunner.RobolectricFrameworkMethod;
47 import org.robolectric.android.internal.AndroidTestEnvironment;
48 import org.robolectric.annotation.Config;
49 import org.robolectric.annotation.experimental.LazyApplication;
50 import org.robolectric.annotation.experimental.LazyApplication.LazyLoad;
51 import org.robolectric.config.ConfigurationRegistry;
52 import org.robolectric.internal.AndroidSandbox.TestEnvironmentSpec;
53 import org.robolectric.internal.ShadowProvider;
54 import org.robolectric.junit.rules.SetSystemPropertyRule;
55 import org.robolectric.manifest.AndroidManifest;
56 import org.robolectric.pluginapi.Sdk;
57 import org.robolectric.pluginapi.TestEnvironmentLifecyclePlugin;
58 import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration;
59 import org.robolectric.pluginapi.perf.Metric;
60 import org.robolectric.pluginapi.perf.PerfStatsReporter;
61 import org.robolectric.plugins.DefaultSdkPicker;
62 import org.robolectric.plugins.SdkCollection;
63 import org.robolectric.util.TempDirectory;
64 import org.robolectric.util.TestUtil;
65 
66 @SuppressWarnings("NewApi")
67 @RunWith(JUnit4.class)
68 public class RobolectricTestRunnerTest {
69 
70   private RunNotifier notifier;
71   private List<String> events;
72   private String priorEnabledSdks;
73   private String priorAlwaysInclude;
74   private SdkCollection sdkCollection;
75 
76   @Rule public SetSystemPropertyRule setSystemPropertyRule = new SetSystemPropertyRule();
77 
78   @Before
setUp()79   public void setUp() throws Exception {
80     notifier = new RunNotifier();
81     events = new ArrayList<>();
82     notifier.addListener(new MyRunListener());
83 
84     priorEnabledSdks = System.getProperty("robolectric.enabledSdks");
85     System.clearProperty("robolectric.enabledSdks");
86 
87     priorAlwaysInclude = System.getProperty("robolectric.alwaysIncludeVariantMarkersInTestName");
88     System.clearProperty("robolectric.alwaysIncludeVariantMarkersInTestName");
89 
90     sdkCollection = TestUtil.getSdkCollection();
91   }
92 
93   @After
tearDown()94   public void tearDown() throws Exception {
95     TestUtil.resetSystemProperty(
96         "robolectric.alwaysIncludeVariantMarkersInTestName", priorAlwaysInclude);
97     TestUtil.resetSystemProperty("robolectric.enabledSdks", priorEnabledSdks);
98   }
99 
100   @Test
ignoredTestCanSpecifyUnsupportedSdkWithoutExploding()101   public void ignoredTestCanSpecifyUnsupportedSdkWithoutExploding() throws Exception {
102     RobolectricTestRunner runner =
103         new RobolectricTestRunner(
104             TestWithOldSdk.class,
105             org.robolectric.RobolectricTestRunner.defaultInjector()
106                 .bind(org.robolectric.pluginapi.SdkPicker.class, AllEnabledSdkPicker.class)
107                 .build());
108     runner.run(notifier);
109     assertThat(events)
110         .containsExactly(
111             "started: oldSdkMethod",
112             "failure: API level 11 is not available",
113             "finished: oldSdkMethod",
114             "ignored: ignoredOldSdkMethod");
115   }
116 
117   @Test
failureInResetterDoesntBreakAllTests()118   public void failureInResetterDoesntBreakAllTests() throws Exception {
119     RobolectricTestRunner runner =
120         new SingleSdkRobolectricTestRunner(
121             TestWithTwoMethods.class,
122             SingleSdkRobolectricTestRunner.defaultInjector()
123                 .bind(
124                     TestEnvironmentSpec.class,
125                     new TestEnvironmentSpec(AndroidTestEnvironmentWithFailingSetUp.class))
126                 .build());
127     runner.run(notifier);
128     assertThat(events)
129         .containsExactly(
130             "started: first",
131             "failure: fake error in setUpApplicationState",
132             "finished: first",
133             "started: second",
134             "failure: fake error in setUpApplicationState",
135             "finished: second")
136         .inOrder();
137   }
138 
139   @Test
noClassDefError_isReplacedByBetterLinkageError()140   public void noClassDefError_isReplacedByBetterLinkageError() throws Exception {
141     RobolectricTestRunner runner =
142         new SingleSdkRobolectricTestRunner(
143             TestWithTwoMethods.class,
144             SingleSdkRobolectricTestRunner.defaultInjector()
145                 .bind(
146                     TestEnvironmentSpec.class,
147                     new TestEnvironmentSpec(AndroidTestEnvironmentThrowsLinkageError.class))
148                 .build());
149     runner.run(notifier);
150     assertThat(events)
151         .containsExactly(
152             "started: first",
153             "failure: java.lang.ExceptionInInitializerError",
154             "finished: first",
155             "started: second",
156             "failure: java.lang.ExceptionInInitializerError",
157             "finished: second")
158         .inOrder();
159   }
160 
161   @Test
failureInAppOnCreateDoesntBreakAllTests()162   public void failureInAppOnCreateDoesntBreakAllTests() throws Exception {
163     RobolectricTestRunner runner =
164         new SingleSdkRobolectricTestRunner(TestWithBrokenAppCreate.class);
165     runner.run(notifier);
166     assertThat(events)
167         .containsExactly(
168             "started: first",
169             "failure: fake error in application.onCreate",
170             "finished: first",
171             "started: second",
172             "failure: fake error in application.onCreate",
173             "finished: second")
174         .inOrder();
175   }
176 
177   @Test
failureInAppOnTerminateDoesntBreakAllTests()178   public void failureInAppOnTerminateDoesntBreakAllTests() throws Exception {
179     RobolectricTestRunner runner =
180         new SingleSdkRobolectricTestRunner(TestWithBrokenAppTerminate.class);
181     runner.run(notifier);
182     assertThat(events)
183         .containsExactly(
184             "started: first",
185             "failure: fake error in application.onTerminate",
186             "finished: first",
187             "started: second",
188             "failure: fake error in application.onTerminate",
189             "finished: second")
190         .inOrder();
191   }
192 
193   @Test
equalityOfRobolectricFrameworkMethod()194   public void equalityOfRobolectricFrameworkMethod() throws Exception {
195     Method method = TestWithTwoMethods.class.getMethod("first");
196     RobolectricFrameworkMethod rfm16 =
197         new RobolectricFrameworkMethod(
198             method,
199             mock(AndroidManifest.class),
200             sdkCollection.getSdk(16),
201             mock(Configuration.class),
202             false);
203     RobolectricFrameworkMethod rfm17 =
204         new RobolectricFrameworkMethod(
205             method,
206             mock(AndroidManifest.class),
207             sdkCollection.getSdk(17),
208             mock(Configuration.class),
209             false);
210     RobolectricFrameworkMethod rfm16b =
211         new RobolectricFrameworkMethod(
212             method,
213             mock(AndroidManifest.class),
214             sdkCollection.getSdk(16),
215             mock(Configuration.class),
216             false);
217 
218     assertThat(rfm16).isNotEqualTo(rfm17);
219     assertThat(rfm16).isEqualTo(rfm16b);
220 
221     assertThat(rfm16.hashCode()).isEqualTo(rfm16b.hashCode());
222   }
223 
224   @Test
shouldReportPerfStats()225   public void shouldReportPerfStats() throws Exception {
226     List<Metric> metrics = new ArrayList<>();
227     PerfStatsReporter reporter = (metadata, metrics1) -> metrics.addAll(metrics1);
228 
229     RobolectricTestRunner runner =
230         new SingleSdkRobolectricTestRunner(
231             TestWithTwoMethods.class,
232             SingleSdkRobolectricTestRunner.defaultInjector()
233                 .bind(PerfStatsReporter[].class, new PerfStatsReporter[] {reporter})
234                 .build());
235 
236     runner.run(notifier);
237 
238     Set<String> metricNames = metrics.stream().map(Metric::getName).collect(toSet());
239     assertThat(metricNames).contains("initialization");
240   }
241 
242   @Test
failedTest_shouldStillReportPerfStats()243   public void failedTest_shouldStillReportPerfStats() throws Exception {
244     List<Metric> metrics = new ArrayList<>();
245     PerfStatsReporter reporter = (metadata, metrics1) -> metrics.addAll(metrics1);
246 
247     RobolectricTestRunner runner =
248         new SingleSdkRobolectricTestRunner(
249             TestThatFails.class,
250             SingleSdkRobolectricTestRunner.defaultInjector()
251                 .bind(PerfStatsReporter[].class, new PerfStatsReporter[] {reporter})
252                 .build());
253 
254     runner.run(notifier);
255 
256     Set<String> metricNames = metrics.stream().map(Metric::getName).collect(toSet());
257     assertThat(metricNames).contains("initialization");
258   }
259 
260   @Test
shouldResetThreadInterrupted()261   public void shouldResetThreadInterrupted() throws Exception {
262     RobolectricTestRunner runner = new SingleSdkRobolectricTestRunner(TestWithInterrupt.class);
263     runner.run(notifier);
264     assertThat(events)
265         .containsExactly(
266             "started: first",
267             "finished: first",
268             "started: second",
269             "failure: failed for the right reason",
270             "finished: second");
271   }
272 
273   @Test
shouldDiagnoseUnexecutedRunnables()274   public void shouldDiagnoseUnexecutedRunnables() throws Exception {
275     RobolectricTestRunner runner =
276         new SingleSdkRobolectricTestRunner(TestWithUnexecutedRunnables.class);
277     runner.run(notifier);
278     assertThat(events)
279         .containsExactly(
280             "started: failWithNoRunnables",
281             "failure: failing with no runnables",
282             "finished: failWithNoRunnables",
283             "started: failWithUnexecutedRunnables",
284             "failure: failing with unexecuted runnable\n"
285                 + "Suppressed: Main looper has queued unexecuted runnables. "
286                 + "This might be the cause of the test failure. "
287                 + "You might need a shadowOf(Looper.getMainLooper()).idle() call.",
288             "finished: failWithUnexecutedRunnables",
289             "started: assumptionViolationWithNoRunnables",
290             "ignored: assumptionViolationWithNoRunnables: assumption violated",
291             "finished: assumptionViolationWithNoRunnables",
292             "started: assumptionViolationWithUnexecutedRunnables",
293             "ignored: assumptionViolationWithUnexecutedRunnables: assumption violated",
294             "finished: assumptionViolationWithUnexecutedRunnables");
295   }
296 
297   /////////////////////////////
298 
299   /** To simulate failures. */
300   public static class AndroidTestEnvironmentWithFailingSetUp extends AndroidTestEnvironment {
301 
AndroidTestEnvironmentWithFailingSetUp( @amed"runtimeSdk") Sdk runtimeSdk, @Named("compileSdk") Sdk compileSdk, ShadowProvider[] shadowProviders, TestEnvironmentLifecyclePlugin[] lifecyclePlugins)302     public AndroidTestEnvironmentWithFailingSetUp(
303         @Named("runtimeSdk") Sdk runtimeSdk,
304         @Named("compileSdk") Sdk compileSdk,
305         ShadowProvider[] shadowProviders,
306         TestEnvironmentLifecyclePlugin[] lifecyclePlugins) {
307       super(runtimeSdk, compileSdk, shadowProviders, lifecyclePlugins);
308     }
309 
310     @Override
setUpApplicationState( String tmpDirName, Configuration configuration, AndroidManifest appManifest)311     public void setUpApplicationState(
312         String tmpDirName, Configuration configuration, AndroidManifest appManifest) {
313       // ConfigurationRegistry.instance is required for resetters.
314       ConfigurationRegistry.instance = new ConfigurationRegistry(configuration.map());
315       throw new RuntimeException("fake error in setUpApplicationState");
316     }
317   }
318 
319   public static class AndroidTestEnvironmentThrowsLinkageError extends AndroidTestEnvironment {
320 
321     public static final class UnloadableClass {
322       static {
323         if (true) {
324           throw new RuntimeException("error in static initializer");
325         }
326       }
327 
doStuff()328       public static void doStuff() {}
329 
UnloadableClass()330       private UnloadableClass() {}
331     }
332 
AndroidTestEnvironmentThrowsLinkageError( @amed"runtimeSdk") Sdk runtimeSdk, @Named("compileSdk") Sdk compileSdk, ShadowProvider[] shadowProviders, TestEnvironmentLifecyclePlugin[] lifecyclePlugins)333     public AndroidTestEnvironmentThrowsLinkageError(
334         @Named("runtimeSdk") Sdk runtimeSdk,
335         @Named("compileSdk") Sdk compileSdk,
336         ShadowProvider[] shadowProviders,
337         TestEnvironmentLifecyclePlugin[] lifecyclePlugins) {
338       super(runtimeSdk, compileSdk, shadowProviders, lifecyclePlugins);
339     }
340 
341     @Override
setUpApplicationState( String tmpDirName, Configuration configuration, AndroidManifest appManifest)342     public void setUpApplicationState(
343         String tmpDirName, Configuration configuration, AndroidManifest appManifest) {
344       UnloadableClass.doStuff();
345     }
346 
347     @Override
resetState()348     public void resetState() {}
349   }
350 
351   @Ignore
352   public static class TestWithOldSdk {
353     @Config(sdk = Build.VERSION_CODES.HONEYCOMB)
354     @Test
oldSdkMethod()355     public void oldSdkMethod() throws Exception {
356       fail("I should not be run!");
357     }
358 
359     @Ignore("This test shouldn't run, and shouldn't cause the test runner to fail")
360     @Config(sdk = Build.VERSION_CODES.HONEYCOMB)
361     @Test
ignoredOldSdkMethod()362     public void ignoredOldSdkMethod() throws Exception {
363       fail("I should not be run!");
364     }
365   }
366 
367   @Ignore
368   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
369   @Config(qualifiers = "w123dp-h456dp-land-hdpi")
370   public static class TestWithTwoMethods {
371     @Test
first()372     public void first() throws Exception {}
373 
374     @Test
second()375     public void second() throws Exception {}
376   }
377 
378   @Ignore
379   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
380   public static class TestThatFails {
381     @Test
first()382     public void first() throws Exception {
383       throw new AssertionError();
384     }
385   }
386 
387   @Ignore
388   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
389   @Config(application = TestWithBrokenAppCreate.MyTestApplication.class)
390   @LazyApplication(LazyLoad.OFF)
391   public static class TestWithBrokenAppCreate {
392     @Test
first()393     public void first() throws Exception {}
394 
395     @Test
second()396     public void second() throws Exception {}
397 
398     public static class MyTestApplication extends Application {
399       @SuppressLint("MissingSuperCall")
400       @Override
onCreate()401       public void onCreate() {
402         throw new RuntimeException("fake error in application.onCreate");
403       }
404     }
405   }
406 
407   @Ignore
408   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
409   @Config(application = TestWithBrokenAppTerminate.MyTestApplication.class)
410   @LazyApplication(LazyLoad.OFF)
411   public static class TestWithBrokenAppTerminate {
412     @Test
first()413     public void first() throws Exception {}
414 
415     @Test
second()416     public void second() throws Exception {}
417 
418     public static class MyTestApplication extends Application {
419       @SuppressLint("MissingSuperCall")
420       @Override
onTerminate()421       public void onTerminate() {
422         throw new RuntimeException("fake error in application.onTerminate");
423       }
424     }
425   }
426 
427   @Ignore
428   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
429   public static class TestWithInterrupt {
430     @Test
first()431     public void first() throws Exception {
432       Thread.currentThread().interrupt();
433     }
434 
435     @Test
second()436     public void second() throws Exception {
437       TempDirectory tempDirectory = new TempDirectory("test");
438 
439       try {
440         Path jarPath = tempDirectory.create("some-jar").resolve("some.jar");
441         try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jarPath.toFile()))) {
442           out.putNextEntry(new JarEntry("README.txt"));
443           out.write("hi!".getBytes(StandardCharsets.UTF_8));
444         }
445 
446         FileSystemProvider jarFSP =
447             FileSystemProvider.installedProviders().stream()
448                 .filter(p -> p.getScheme().equals("jar"))
449                 .findFirst()
450                 .get();
451         Path fakeJarFile = Paths.get(jarPath.toUri());
452 
453         // if Thread.interrupted() was true, this would fail in AbstractInterruptibleChannel:
454         jarFSP.newFileSystem(fakeJarFile, new HashMap<>());
455       } finally {
456         tempDirectory.destroy();
457       }
458 
459       fail("failed for the right reason");
460     }
461   }
462 
463   /** Fixture for #shouldDiagnoseUnexecutedRunnables() */
464   @Ignore
465   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
466   public static class TestWithUnexecutedRunnables {
467 
468     @Test
failWithUnexecutedRunnables()469     public void failWithUnexecutedRunnables() {
470       shadowMainLooper().pause();
471       new Handler(Looper.getMainLooper()).post(() -> {});
472       fail("failing with unexecuted runnable");
473     }
474 
475     @Test
failWithNoRunnables()476     public void failWithNoRunnables() {
477       fail("failing with no runnables");
478     }
479 
480     @Test
assumptionViolationWithUnexecutedRunnables()481     public void assumptionViolationWithUnexecutedRunnables() {
482       shadowMainLooper().pause();
483       new Handler(Looper.getMainLooper()).post(() -> {});
484       throw new AssumptionViolatedException("assumption violated");
485     }
486 
487     @Test
assumptionViolationWithNoRunnables()488     public void assumptionViolationWithNoRunnables() {
489       throw new AssumptionViolatedException("assumption violated");
490     }
491   }
492 
493   /** Ignore the value of --Drobolectric.enabledSdks */
494   public static class AllEnabledSdkPicker extends DefaultSdkPicker {
495     @Inject
AllEnabledSdkPicker(@onnull SdkCollection sdkCollection)496     public AllEnabledSdkPicker(@Nonnull SdkCollection sdkCollection) {
497       super(sdkCollection, (String) null);
498     }
499   }
500 
501   private class MyRunListener extends RunListener {
502 
503     @Override
testRunStarted(Description description)504     public void testRunStarted(Description description) {
505       events.add("run started: " + description.getMethodName());
506     }
507 
508     @Override
testRunFinished(Result result)509     public void testRunFinished(Result result) {
510       events.add("run finished: " + result);
511     }
512 
513     @Override
testStarted(Description description)514     public void testStarted(Description description) {
515       events.add("started: " + description.getMethodName());
516     }
517 
518     @Override
testFinished(Description description)519     public void testFinished(Description description) {
520       events.add("finished: " + description.getMethodName());
521     }
522 
523     @Override
testAssumptionFailure(Failure failure)524     public void testAssumptionFailure(Failure failure) {
525       events.add(
526           "ignored: " + failure.getDescription().getMethodName() + ": " + failure.getMessage());
527     }
528 
529     @Override
testIgnored(Description description)530     public void testIgnored(Description description) {
531       events.add("ignored: " + description.getMethodName());
532     }
533 
534     @Override
testFailure(Failure failure)535     public void testFailure(Failure failure) {
536       Throwable exception = failure.getException();
537       String message = exception.getMessage();
538       if (message == null) {
539         message = exception.toString();
540       }
541       for (Throwable suppressed : exception.getSuppressed()) {
542         message += "\nSuppressed: " + suppressed.getMessage();
543       }
544       events.add("failure: " + message);
545     }
546   }
547 
548   @Test
shouldReportExceptionsInBeforeClass()549   public void shouldReportExceptionsInBeforeClass() throws Exception {
550     RobolectricTestRunner runner =
551         new SingleSdkRobolectricTestRunner(TestWithBeforeClassThatThrowsRuntimeException.class);
552     runner.run(notifier);
553     if (Boolean.getBoolean("robolectric.useLegacySandboxFlow")) {
554       assertThat(events.get(1)).startsWith("failure: fail");
555     } else {
556       assertThat(events.get(0)).isEqualTo("failure: fail");
557     }
558   }
559 
560   @Ignore
561   public static class TestWithBeforeClassThatThrowsRuntimeException {
562     @BeforeClass
beforeClass()563     public static void beforeClass() {
564       throw new RuntimeException("fail");
565     }
566 
567     @Test
test()568     public void test() {}
569   }
570 
571   @Test
shouldInvokeAfterClass()572   public void shouldInvokeAfterClass() throws Exception {
573     RobolectricTestRunner runner =
574         new SingleSdkRobolectricTestRunner(TestClassWithAfterClass.class);
575     setSystemPropertyRule.set("RobolectricTestRunnerTest.wasAfterClassCalled", "false");
576     runner.run(notifier);
577     assertThat(System.getProperty("RobolectricTestRunnerTest.wasAfterClassCalled"))
578         .isEqualTo("true");
579   }
580 
581   @Ignore
582   public static class TestClassWithAfterClass {
583     @AfterClass
afterClass()584     public static void afterClass() {
585       System.setProperty("RobolectricTestRunnerTest.wasAfterClassCalled", "true");
586     }
587 
588     @Test
test()589     public void test() {}
590   }
591 
592   @Ignore
593   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
594   public static class TestWithIgnore {
595     @Test
test()596     public void test() {}
597 
598     // to verify @Ignore behavior
599     @Ignore
600     @Test
ignoredTest()601     public void ignoredTest() {}
602   }
603 
604   @Test
shouldNotifyIgnoredTests()605   public void shouldNotifyIgnoredTests() throws Exception {
606     RobolectricTestRunner runner = new SingleSdkRobolectricTestRunner(TestWithIgnore.class);
607     runner.run(notifier);
608     assertThat(events)
609         .containsExactly("ignored: ignoredTest", "started: test", "finished: test")
610         .inOrder();
611   }
612 }
613