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