Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static org.assertj.core.api.Assertions.assertThat;
      4 import static org.robolectric.Shadows.shadowOf;
      5 
      6 import android.app.Application;
      7 import android.content.Context;
      8 import android.os.Handler;
      9 import android.os.HandlerThread;
     10 import android.os.Looper;
     11 import java.util.ArrayList;
     12 import java.util.concurrent.CountDownLatch;
     13 import java.util.concurrent.atomic.AtomicReference;
     14 import org.junit.After;
     15 import org.junit.Rule;
     16 import org.junit.Test;
     17 import org.junit.rules.TestName;
     18 import org.junit.runner.RunWith;
     19 import org.robolectric.RoboSettings;
     20 import org.robolectric.RobolectricTestRunner;
     21 import org.robolectric.RuntimeEnvironment;
     22 import org.robolectric.util.ReflectionHelpers;
     23 import org.robolectric.util.Scheduler;
     24 
     25 @RunWith(RobolectricTestRunner.class)
     26 public class ShadowLooperTest {
     27 
     28   // testName is used when creating background threads. Makes it
     29   // easier to debug exceptions on background threads when you
     30   // know what test they are associated with.
     31   @Rule
     32   public TestName testName = new TestName();
     33 
     34   // Helper method that starts the thread with the same name as the
     35   // current test, so that you will know which test invoked it if
     36   // it has an exception.
     37   private HandlerThread getHandlerThread() {
     38     HandlerThread ht = new HandlerThread(testName.getMethodName());
     39     ht.start();
     40     return ht;
     41   }
     42 
     43   // Useful class for checking that a thread's loop() has exited.
     44   private class QuitThread extends Thread {
     45     private boolean hasContinued = false;
     46     private Looper looper;
     47     private CountDownLatch started = new CountDownLatch(1);
     48 
     49     public QuitThread() {
     50       super(testName.getMethodName());
     51     }
     52 
     53     @Override
     54     public void run() {
     55       Looper.prepare();
     56       looper = Looper.myLooper();
     57       started.countDown();
     58       Looper.loop();
     59       hasContinued = true;
     60     }
     61   }
     62 
     63   private QuitThread getQuitThread() throws InterruptedException {
     64     QuitThread qt = new QuitThread();
     65     qt.start();
     66     qt.started.await();
     67     return qt;
     68   }
     69 
     70   @Test
     71   public void mainLooper_andMyLooper_shouldBeSame_onMainThread() {
     72     assertThat(Looper.myLooper()).isSameAs(Looper.getMainLooper());
     73   }
     74 
     75   @Test
     76   public void differentThreads_getDifferentLoopers() {
     77     HandlerThread ht = getHandlerThread();
     78     assertThat(ht.getLooper()).isNotSameAs(Looper.getMainLooper());
     79   }
     80 
     81   @Test
     82   public void mainLooperThread_shouldBeTestThread() {
     83     assertThat(Looper.getMainLooper().getThread()).isSameAs(Thread.currentThread());
     84   }
     85 
     86   @Test
     87   public void shadowMainLooper_shouldBeShadowOfMainLooper() {
     88     assertThat(ShadowLooper.getShadowMainLooper()).isSameAs(shadowOf(Looper.getMainLooper()));
     89   }
     90 
     91   @Test
     92   public void getLooperForThread_returnsLooperForAThreadThatHasOne() throws InterruptedException {
     93     QuitThread qt = getQuitThread();
     94     assertThat(ShadowLooper.getLooperForThread(qt)).isSameAs(qt.looper);
     95   }
     96 
     97   @Test
     98   public void getLooperForThread_returnsLooperForMainThread() {
     99     assertThat(ShadowLooper.getLooperForThread(Thread.currentThread())).isSameAs(Looper.getMainLooper());
    100   }
    101 
    102   @Test
    103   public void idleMainLooper_executesScheduledTasks() {
    104     final boolean[] wasRun = new boolean[]{false};
    105     new Handler().postDelayed(new Runnable() {
    106       @Override
    107       public void run() {
    108         wasRun[0] = true;
    109       }
    110     }, 2000);
    111 
    112     assertThat(wasRun[0]).as("first").isFalse();
    113     ShadowLooper.idleMainLooper(1999);
    114     assertThat(wasRun[0]).as("second").isFalse();
    115     ShadowLooper.idleMainLooper(1);
    116     assertThat(wasRun[0]).as("last").isTrue();
    117   }
    118 
    119   @Test
    120   public void idleConstantly_runsPostDelayedTasksImmediately() {
    121     ShadowLooper.idleMainLooperConstantly(true);
    122     final boolean[] wasRun = new boolean[]{false};
    123     new Handler().postDelayed(new Runnable() {
    124       @Override
    125       public void run() {
    126         wasRun[0] = true;
    127       }
    128     }, 2000);
    129 
    130     assertThat(wasRun[0]).isTrue();
    131   }
    132 
    133   @Test(expected = RuntimeException.class)
    134   public void shouldThrowRuntimeExceptionIfTryingToQuitMainLooper() throws Exception {
    135     Looper.getMainLooper().quit();
    136   }
    137 
    138   @Test
    139   public void shouldNotQueueMessagesIfLooperIsQuit() throws Exception {
    140     HandlerThread ht = getHandlerThread();
    141     Looper looper = ht.getLooper();
    142     looper.quit();
    143     assertThat(shadowOf(looper).hasQuit()).as("hasQuit").isTrue();
    144     assertThat(shadowOf(looper).post(new Runnable() {
    145       @Override
    146       public void run() {
    147       }
    148     }, 0)).as("post").isFalse();
    149 
    150     assertThat(shadowOf(looper).postAtFrontOfQueue(new Runnable() {
    151       @Override
    152       public void run() {
    153       }
    154     })).as("postAtFrontOfQueue").isFalse();
    155     assertThat(shadowOf(looper).getScheduler().areAnyRunnable()).as("areAnyRunnable").isFalse();
    156   }
    157 
    158   @Test
    159   public void shouldThrowawayRunnableQueueIfLooperQuits() throws Exception {
    160     HandlerThread ht = getHandlerThread();
    161     Looper looper = ht.getLooper();
    162     shadowOf(looper).pause();
    163     shadowOf(looper).post(new Runnable() {
    164       @Override
    165       public void run() {
    166       }
    167     }, 0);
    168     looper.quit();
    169     assertThat(shadowOf(looper).hasQuit()).as("hasQuit").isTrue();
    170     assertThat(shadowOf(looper).getScheduler().areAnyRunnable()).as("areAnyRunnable").isFalse();
    171     assertThat(shadowOf(looper.getQueue()).getHead()).as("queue").isNull();
    172   }
    173 
    174   @Test
    175   public void threadShouldContinue_whenLooperQuits() throws InterruptedException {
    176     QuitThread test = getQuitThread();
    177     assertThat(test.hasContinued).as("beforeJoin").isFalse();
    178     test.looper.quit();
    179     test.join(5000);
    180     assertThat(test.hasContinued).as("afterJoin").isTrue();
    181   }
    182 
    183   @Test
    184   public void shouldResetQueue_whenLooperIsReset() {
    185     HandlerThread ht = getHandlerThread();
    186     Looper looper = ht.getLooper();
    187     Handler h = new Handler(looper);
    188     ShadowLooper sLooper = shadowOf(looper);
    189     sLooper.pause();
    190     h.post(new Runnable() {
    191       @Override
    192       public void run() {
    193       }
    194     });
    195     assertThat(shadowOf(looper.getQueue()).getHead()).as("queue").isNotNull();
    196     sLooper.reset();
    197     assertThat(sLooper.getScheduler().areAnyRunnable()).as("areAnyRunnable").isFalse();
    198     assertThat(shadowOf(looper.getQueue()).getHead()).as("queue").isNull();
    199   }
    200 
    201   @Test
    202   public void shouldSetNewScheduler_whenLooperIsReset() {
    203     HandlerThread ht = getHandlerThread();
    204     Looper looper = ht.getLooper();
    205     ShadowLooper sLooper = shadowOf(looper);
    206     Scheduler old = sLooper.getScheduler();
    207     sLooper.reset();
    208     assertThat(old).isNotSameAs(sLooper.getScheduler());
    209   }
    210 
    211   @Test
    212   public void resetThreadLoopers_shouldQuitAllNonMainLoopers() throws InterruptedException {
    213     QuitThread test = getQuitThread();
    214     assertThat(test.hasContinued).as("hasContinued:before").isFalse();
    215     ShadowLooper.resetThreadLoopers();
    216     test.join(5000);
    217     assertThat(test.hasContinued).as("hasContinued:after").isTrue();
    218   }
    219 
    220   @Test(timeout = 1000)
    221   public void whenTestHarnessUsesDifferentThread_shouldStillHaveMainLooper() {
    222     assertThat(Looper.myLooper()).isSameAs(Looper.getMainLooper());
    223   }
    224 
    225   @Test
    226   public void resetThreadLoopers_fromNonMainThread_shouldThrowISE() throws InterruptedException {
    227     final AtomicReference<Throwable> ex = new AtomicReference<>();
    228     Thread t = new Thread() {
    229       @Override
    230       public void run() {
    231         try {
    232           ShadowLooper.resetThreadLoopers();
    233         } catch (Throwable t) {
    234           ex.set(t);
    235         }
    236       }
    237     };
    238     t.start();
    239     t.join();
    240     assertThat(ex.get()).isInstanceOf(IllegalStateException.class);
    241   }
    242 
    243   @Test
    244   public void soStaticRefsToLoopersInAppWorksAcrossTests_shouldRetainSameLooperForMainThreadBetweenResetsButGiveItAFreshScheduler() throws Exception {
    245     Looper mainLooper = Looper.getMainLooper();
    246     Scheduler scheduler = shadowOf(mainLooper).getScheduler();
    247     shadowOf(mainLooper).quit = true;
    248     assertThat(RuntimeEnvironment.application.getMainLooper()).isSameAs(mainLooper);
    249     Scheduler s = new Scheduler();
    250     RuntimeEnvironment.setMasterScheduler(s);
    251     ShadowLooper.resetThreadLoopers();
    252     Application application = new Application();
    253     ReflectionHelpers.callInstanceMethod(application, "attach", ReflectionHelpers.ClassParameter.from(Context.class, RuntimeEnvironment.application.getBaseContext()));
    254 
    255     assertThat(Looper.getMainLooper()).as("Looper.getMainLooper()").isSameAs(mainLooper);
    256     assertThat(application.getMainLooper()).as("app.getMainLooper()").isSameAs(mainLooper);
    257     assertThat(shadowOf(mainLooper).getScheduler()).as("scheduler").isNotSameAs(scheduler).isSameAs(s);
    258     assertThat(shadowOf(mainLooper).hasQuit()).as("quit").isFalse();
    259   }
    260 
    261   @Test
    262   public void getMainLooperReturnsNonNullOnMainThreadWhenRobolectricApplicationIsNull() {
    263     RuntimeEnvironment.application = null;
    264     assertThat(Looper.getMainLooper()).isNotNull();
    265   }
    266 
    267   private void setAdvancedScheduling() {
    268     RoboSettings.setUseGlobalScheduler(true);
    269   }
    270 
    271   @Test
    272   public void reset_setsGlobalScheduler_forMainLooper_byDefault() {
    273     ShadowLooper sMainLooper = ShadowLooper.getShadowMainLooper();
    274     Scheduler s = new Scheduler();
    275     RuntimeEnvironment.setMasterScheduler(s);
    276     sMainLooper.reset();
    277     assertThat(sMainLooper.getScheduler()).isSameAs(s);
    278   }
    279 
    280   @Test
    281   public void reset_setsGlobalScheduler_forMainLooper_withAdvancedScheduling() {
    282     setAdvancedScheduling();
    283     ShadowLooper sMainLooper = ShadowLooper.getShadowMainLooper();
    284     Scheduler s = new Scheduler();
    285     RuntimeEnvironment.setMasterScheduler(s);
    286     sMainLooper.reset();
    287     assertThat(sMainLooper.getScheduler()).isSameAs(s);
    288   }
    289 
    290   @Test
    291   public void reset_setsNewScheduler_forNonMainLooper_byDefault() {
    292     HandlerThread ht = getHandlerThread();
    293     ShadowLooper sLooper = shadowOf(ht.getLooper());
    294     Scheduler old = sLooper.getScheduler();
    295     sLooper.reset();
    296     assertThat(sLooper.getScheduler())
    297         .isNotSameAs(old)
    298         .isNotSameAs(RuntimeEnvironment.getMasterScheduler());
    299   }
    300 
    301   @Test
    302   public void reset_setsSchedulerToMaster_forNonMainLooper_withAdvancedScheduling() {
    303     HandlerThread ht = getHandlerThread();
    304     ShadowLooper sLooper = shadowOf(ht.getLooper());
    305     Scheduler s = new Scheduler();
    306     RuntimeEnvironment.setMasterScheduler(s);
    307     setAdvancedScheduling();
    308     sLooper.reset();
    309     assertThat(sLooper.getScheduler()).isSameAs(s);
    310   }
    311 
    312   @Test
    313   public void resetThreadLoopers_resets_background_thread_schedulers() {
    314     HandlerThread backgroundThread = new HandlerThread("resetTest");
    315     backgroundThread.start();
    316     Looper backgroundLooper = backgroundThread.getLooper();
    317     Handler handler = new Handler(backgroundLooper);
    318     Runnable empty = new Runnable() {
    319       @Override
    320       public void run() {}
    321     };
    322     // There should be at least two iterations of this loop because resetThreadLoopers calls
    323     // 'quit' on background loopers once, which also resets the scheduler.
    324     for (int i = 0; i < 5; i++) {
    325       assertThat(shadowOf(backgroundLooper).getScheduler().size()).isZero();
    326       assertThat(shadowOf(backgroundLooper).getScheduler().getCurrentTime()).isEqualTo(100L);
    327       handler.post(empty);
    328       handler.postDelayed(empty, 5000);
    329       // increment scheduler's time by 5000
    330       shadowOf(backgroundLooper).runToEndOfTasks();
    331       assertThat(shadowOf(backgroundLooper).getScheduler().getCurrentTime()).isEqualTo(5100L);
    332       ShadowLooper.resetThreadLoopers();
    333     }
    334   }
    335 
    336   @Test
    337   public void myLooper_returnsMainLooper_ifMainThreadIsSwitched() throws InterruptedException {
    338     final AtomicReference<Looper> myLooper = new AtomicReference<>();
    339     Thread t = new Thread(testName.getMethodName()) {
    340       @Override
    341       public void run() {
    342         myLooper.set(Looper.myLooper());
    343       }
    344     };
    345     RuntimeEnvironment.setMainThread(t);
    346     t.start();
    347     try {
    348       t.join(1000);
    349       assertThat(myLooper.get()).isSameAs(Looper.getMainLooper());
    350     } finally {
    351       RuntimeEnvironment.setMainThread(Thread.currentThread());
    352     }
    353   }
    354 
    355   @Test
    356   public void getMainLooper_shouldBeInitialized_onBackgroundThread_evenWhenRobolectricApplicationIsNull() throws Exception {
    357     RuntimeEnvironment.application = null;
    358     final AtomicReference<Looper> mainLooperAtomicReference = new AtomicReference<>();
    359 
    360     Thread backgroundThread = new Thread(new Runnable() {
    361       @Override
    362       public void run() {
    363         Looper mainLooper = Looper.getMainLooper();
    364         mainLooperAtomicReference.set(mainLooper);
    365       }
    366     }, testName.getMethodName());
    367     backgroundThread.start();
    368     backgroundThread.join();
    369 
    370     assertThat(mainLooperAtomicReference.get()).as("mainLooper").isSameAs(Looper.getMainLooper());
    371   }
    372 
    373   @Test
    374   public void schedulerOnAnotherLooper_shouldNotBeMaster_byDefault() {
    375     HandlerThread ht = getHandlerThread();
    376     assertThat(shadowOf(ht.getLooper()).getScheduler()).isNotSameAs(RuntimeEnvironment.getMasterScheduler());
    377   }
    378 
    379   @Test
    380   public void schedulerOnAnotherLooper_shouldBeMaster_ifAdvancedSchedulingEnabled() {
    381     setAdvancedScheduling();
    382     HandlerThread ht = getHandlerThread();
    383     assertThat(shadowOf(ht.getLooper()).getScheduler()).isSameAs(RuntimeEnvironment.getMasterScheduler());
    384   }
    385 
    386   @Test
    387   public void withAdvancedScheduling_shouldDispatchMessagesOnBothLoopers_whenAdvancingForegroundThread() {
    388     setAdvancedScheduling();
    389     ShadowLooper.pauseMainLooper();
    390     HandlerThread ht = getHandlerThread();
    391     Handler handler1 = new Handler(ht.getLooper());
    392     Handler handler2 = new Handler();
    393     final ArrayList<String> events = new ArrayList<>();
    394     handler1.postDelayed(new Runnable() {
    395       @Override
    396       public void run() {
    397         events.add("handler1");
    398       }
    399     }, 100);
    400     handler2.postDelayed(new Runnable() {
    401       @Override
    402       public void run() {
    403         events.add("handler2");
    404       }
    405     }, 200);
    406     assertThat(events).as("start").isEmpty();
    407     Scheduler s = ShadowLooper.getShadowMainLooper().getScheduler();
    408     assertThat(s).isSameAs(RuntimeEnvironment.getMasterScheduler())
    409       .isSameAs(shadowOf(ht.getLooper()).getScheduler());
    410     final long startTime = s.getCurrentTime();
    411     s.runOneTask();
    412     assertThat(events).as("firstEvent").containsExactly("handler1");
    413     assertThat(s.getCurrentTime()).as("firstEvent:time").isEqualTo(100 + startTime);
    414     s.runOneTask();
    415     assertThat(events).as("secondEvent").containsExactly("handler1", "handler2");
    416     assertThat(s.getCurrentTime()).as("secondEvent:time").isEqualTo(200 + startTime);
    417   }
    418 
    419   @After
    420   public void tearDown() {
    421     RoboSettings.setUseGlobalScheduler(false);
    422   }
    423 }
    424