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