1 package org.robolectric.util; 2 3 import static com.google.common.truth.Truth.assertThat; 4 import static com.google.common.truth.Truth.assertWithMessage; 5 import static org.robolectric.util.Scheduler.IdleState.CONSTANT_IDLE; 6 import static org.robolectric.util.Scheduler.IdleState.PAUSED; 7 import static org.robolectric.util.Scheduler.IdleState.UNPAUSED; 8 9 import com.google.common.collect.ImmutableList; 10 import com.google.common.collect.Iterables; 11 import java.util.ArrayList; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.Random; 15 import java.util.TreeMap; 16 import java.util.concurrent.atomic.AtomicLong; 17 import org.junit.Before; 18 import org.junit.Test; 19 import org.junit.runner.RunWith; 20 import org.junit.runners.JUnit4; 21 22 @RunWith(JUnit4.class) 23 public class SchedulerTest { 24 private final Scheduler scheduler = new Scheduler(); 25 private final List<String> transcript = new ArrayList<>(); 26 27 private long startTime; 28 29 @Before setUp()30 public void setUp() throws Exception { 31 scheduler.pause(); 32 startTime = scheduler.getCurrentTime(); 33 } 34 35 @Test whenIdleStateIsConstantIdle_isPausedReturnsFalse()36 public void whenIdleStateIsConstantIdle_isPausedReturnsFalse() { 37 scheduler.setIdleState(CONSTANT_IDLE); 38 assertThat(scheduler.isPaused()).isFalse(); 39 } 40 41 @Test whenIdleStateIsUnPaused_isPausedReturnsFalse()42 public void whenIdleStateIsUnPaused_isPausedReturnsFalse() { 43 scheduler.setIdleState(UNPAUSED); 44 assertThat(scheduler.isPaused()).isFalse(); 45 } 46 47 @Test whenIdleStateIsPaused_isPausedReturnsTrue()48 public void whenIdleStateIsPaused_isPausedReturnsTrue() { 49 scheduler.setIdleState(PAUSED); 50 assertThat(scheduler.isPaused()).isTrue(); 51 } 52 53 @Test pause_setsIdleState()54 public void pause_setsIdleState() { 55 scheduler.setIdleState(UNPAUSED); 56 scheduler.pause(); 57 assertThat(scheduler.getIdleState()).isSameInstanceAs(PAUSED); 58 } 59 60 @Test 61 @SuppressWarnings("deprecation") idleConstantly_setsIdleState()62 public void idleConstantly_setsIdleState() { 63 scheduler.setIdleState(UNPAUSED); 64 scheduler.idleConstantly(true); 65 assertThat(scheduler.getIdleState()).isSameInstanceAs(CONSTANT_IDLE); 66 scheduler.idleConstantly(false); 67 assertThat(scheduler.getIdleState()).isSameInstanceAs(UNPAUSED); 68 } 69 70 @Test unPause_setsIdleState()71 public void unPause_setsIdleState() { 72 scheduler.setIdleState(PAUSED); 73 scheduler.unPause(); 74 assertThat(scheduler.getIdleState()).isSameInstanceAs(UNPAUSED); 75 } 76 77 @Test setIdleStateToUnPause_shouldRunPendingTasks()78 public void setIdleStateToUnPause_shouldRunPendingTasks() { 79 scheduler.postDelayed(new AddToTranscript("one"), 0); 80 scheduler.postDelayed(new AddToTranscript("two"), 0); 81 scheduler.postDelayed(new AddToTranscript("three"), 1000); 82 assertThat(transcript).isEmpty(); 83 final long time = scheduler.getCurrentTime(); 84 scheduler.setIdleState(UNPAUSED); 85 assertThat(transcript).containsExactly("one", "two"); 86 assertWithMessage("time").that(scheduler.getCurrentTime()).isEqualTo(time); 87 } 88 89 @Test setIdleStateToConstantIdle_shouldRunAllTasks()90 public void setIdleStateToConstantIdle_shouldRunAllTasks() { 91 scheduler.postDelayed(new AddToTranscript("one"), 0); 92 scheduler.postDelayed(new AddToTranscript("two"), 0); 93 scheduler.postDelayed(new AddToTranscript("three"), 1000); 94 assertThat(transcript).isEmpty(); 95 final long time = scheduler.getCurrentTime(); 96 scheduler.setIdleState(CONSTANT_IDLE); 97 assertThat(transcript).containsExactly("one", "two", "three"); 98 assertWithMessage("time").that(scheduler.getCurrentTime()).isEqualTo(time + 1000); 99 } 100 101 @Test unPause_shouldRunPendingTasks()102 public void unPause_shouldRunPendingTasks() { 103 scheduler.postDelayed(new AddToTranscript("one"), 0); 104 scheduler.postDelayed(new AddToTranscript("two"), 0); 105 scheduler.postDelayed(new AddToTranscript("three"), 1000); 106 assertThat(transcript).isEmpty(); 107 final long time = scheduler.getCurrentTime(); 108 scheduler.unPause(); 109 assertThat(transcript).containsExactly("one", "two"); 110 assertWithMessage("time").that(scheduler.getCurrentTime()).isEqualTo(time); 111 } 112 113 @Test 114 @SuppressWarnings("deprecation") idleConstantlyTrue_shouldRunAllTasks()115 public void idleConstantlyTrue_shouldRunAllTasks() { 116 scheduler.postDelayed(new AddToTranscript("one"), 0); 117 scheduler.postDelayed(new AddToTranscript("two"), 0); 118 scheduler.postDelayed(new AddToTranscript("three"), 1000); 119 assertThat(transcript).isEmpty(); 120 final long time = scheduler.getCurrentTime(); 121 scheduler.idleConstantly(true); 122 assertThat(transcript).containsExactly("one", "two", "three"); 123 assertWithMessage("time").that(scheduler.getCurrentTime()).isEqualTo(time + 1000); 124 } 125 126 @Test advanceTo_shouldAdvanceTimeEvenIfThereIsNoWork()127 public void advanceTo_shouldAdvanceTimeEvenIfThereIsNoWork() throws Exception { 128 scheduler.advanceTo(1000); 129 assertThat(scheduler.getCurrentTime()).isEqualTo(1000); 130 } 131 132 @Test advanceBy_returnsTrueIffSomeJobWasRun()133 public void advanceBy_returnsTrueIffSomeJobWasRun() throws Exception { 134 scheduler.postDelayed(new AddToTranscript("one"), 0); 135 scheduler.postDelayed(new AddToTranscript("two"), 0); 136 scheduler.postDelayed(new AddToTranscript("three"), 1000); 137 138 assertThat(scheduler.advanceBy(0)).isTrue(); 139 assertThat(transcript).containsExactly("one", "two"); 140 transcript.clear(); 141 142 assertThat(scheduler.advanceBy(0)).isFalse(); 143 assertThat(transcript).isEmpty(); 144 145 assertThat(scheduler.advanceBy(1000)).isTrue(); 146 assertThat(transcript).containsExactly("three"); 147 } 148 149 @Test postDelayed_addsAJobToBeRunInTheFuture()150 public void postDelayed_addsAJobToBeRunInTheFuture() throws Exception { 151 scheduler.postDelayed(new AddToTranscript("one"), 1000); 152 scheduler.postDelayed(new AddToTranscript("two"), 2000); 153 scheduler.postDelayed(new AddToTranscript("three"), 3000); 154 155 scheduler.advanceBy(1000); 156 assertThat(transcript).containsExactly("one"); 157 transcript.clear(); 158 159 scheduler.advanceBy(500); 160 assertThat(transcript).isEmpty(); 161 162 scheduler.advanceBy(501); 163 assertThat(transcript).containsExactly("two"); 164 transcript.clear(); 165 166 scheduler.advanceBy(999); 167 assertThat(transcript).containsExactly("three"); 168 } 169 170 @Test postDelayed_whileIdlingConstantly_executesImmediately()171 public void postDelayed_whileIdlingConstantly_executesImmediately() { 172 scheduler.setIdleState(CONSTANT_IDLE); 173 scheduler.postDelayed(new AddToTranscript("one"), 1000); 174 175 assertThat(transcript).containsExactly("one"); 176 } 177 178 @Test postDelayed_whileIdlingConstantly_advancesTime()179 public void postDelayed_whileIdlingConstantly_advancesTime() { 180 scheduler.setIdleState(CONSTANT_IDLE); 181 scheduler.postDelayed(new AddToTranscript("one"), 1000); 182 183 assertThat(scheduler.getCurrentTime()).isEqualTo(1000 + startTime); 184 } 185 186 @Test postAtFrontOfQueue_addsJobAtFrontOfQueue()187 public void postAtFrontOfQueue_addsJobAtFrontOfQueue() throws Exception { 188 scheduler.post(new AddToTranscript("one")); 189 scheduler.post(new AddToTranscript("two")); 190 scheduler.postAtFrontOfQueue(new AddToTranscript("three")); 191 192 scheduler.runOneTask(); 193 assertThat(transcript).containsExactly("three"); 194 transcript.clear(); 195 196 scheduler.runOneTask(); 197 assertThat(transcript).containsExactly("one"); 198 transcript.clear(); 199 200 scheduler.runOneTask(); 201 assertThat(transcript).containsExactly("two"); 202 } 203 204 @Test postAtFrontOfQueue_whenUnpaused_runsJobs()205 public void postAtFrontOfQueue_whenUnpaused_runsJobs() throws Exception { 206 scheduler.unPause(); 207 scheduler.postAtFrontOfQueue(new AddToTranscript("three")); 208 assertThat(transcript).containsExactly("three"); 209 } 210 211 @Test postDelayed_whenMoreItemsAreAdded_runsJobs()212 public void postDelayed_whenMoreItemsAreAdded_runsJobs() throws Exception { 213 scheduler.postDelayed(new Runnable() { 214 @Override 215 public void run() { 216 transcript.add("one"); 217 scheduler.postDelayed(new Runnable() { 218 @Override 219 public void run() { 220 transcript.add("two"); 221 scheduler.postDelayed(new AddToTranscript("three"), 1000); 222 } 223 }, 1000); 224 } 225 }, 1000); 226 227 scheduler.advanceBy(1000); 228 assertThat(transcript).containsExactly("one"); 229 transcript.clear(); 230 231 scheduler.advanceBy(500); 232 assertThat(transcript).isEmpty(); 233 234 scheduler.advanceBy(501); 235 assertThat(transcript).containsExactly("two"); 236 transcript.clear(); 237 238 scheduler.advanceBy(999); 239 assertThat(transcript).containsExactly("three"); 240 } 241 242 @Test remove_ShouldRemoveAllInstancesOfRunnableFromQueue()243 public void remove_ShouldRemoveAllInstancesOfRunnableFromQueue() throws Exception { 244 scheduler.post(new TestRunnable()); 245 TestRunnable runnable = new TestRunnable(); 246 scheduler.post(runnable); 247 scheduler.post(runnable); 248 assertThat(scheduler.size()).isEqualTo(3); 249 scheduler.remove(runnable); 250 assertThat(scheduler.size()).isEqualTo(1); 251 scheduler.advanceToLastPostedRunnable(); 252 assertThat(runnable.wasRun).isFalse(); 253 } 254 255 @Test reset_shouldUnPause()256 public void reset_shouldUnPause() throws Exception { 257 scheduler.pause(); 258 259 TestRunnable runnable = new TestRunnable(); 260 scheduler.post(runnable); 261 262 assertThat(runnable.wasRun).isFalse(); 263 264 scheduler.reset(); 265 scheduler.post(runnable); 266 assertThat(runnable.wasRun).isTrue(); 267 } 268 269 @Test reset_shouldClearPendingRunnables()270 public void reset_shouldClearPendingRunnables() throws Exception { 271 scheduler.pause(); 272 273 TestRunnable runnable1 = new TestRunnable(); 274 scheduler.post(runnable1); 275 276 assertThat(runnable1.wasRun).isFalse(); 277 278 scheduler.reset(); 279 280 TestRunnable runnable2 = new TestRunnable(); 281 scheduler.post(runnable2); 282 283 assertThat(runnable1.wasRun).isFalse(); 284 assertThat(runnable2.wasRun).isTrue(); 285 } 286 287 @Test nestedPost_whilePaused_doesntAutomaticallyExecute()288 public void nestedPost_whilePaused_doesntAutomaticallyExecute() { 289 final List<Integer> order = new ArrayList<>(); 290 scheduler.postDelayed(new Runnable() { 291 @Override 292 public void run() { 293 order.add(1); 294 scheduler.post(new Runnable() { 295 @Override 296 public void run() { 297 order.add(4); 298 } 299 }); 300 order.add(2); 301 } 302 }, 0); 303 scheduler.postDelayed(new Runnable() { 304 @Override 305 public void run() { 306 order.add(3); 307 } 308 }, 0); 309 scheduler.runOneTask(); 310 311 assertWithMessage("order:first run").that(order).containsExactly(1, 2); 312 assertWithMessage("size:first run").that(scheduler.size()).isEqualTo(2); 313 scheduler.runOneTask(); 314 assertWithMessage("order:second run").that(order).containsExactly(1, 2, 3); 315 assertWithMessage("size:second run").that(scheduler.size()).isEqualTo(1); 316 scheduler.runOneTask(); 317 assertWithMessage("order:third run").that(order).containsExactly(1, 2, 3, 4); 318 assertWithMessage("size:second run").that(scheduler.size()).isEqualTo(0); 319 } 320 321 @Test nestedPost_whileUnpaused_automaticallyExecutes3After()322 public void nestedPost_whileUnpaused_automaticallyExecutes3After() { 323 final List<Integer> order = new ArrayList<>(); 324 scheduler.unPause(); 325 scheduler.postDelayed(new Runnable() { 326 @Override 327 public void run() { 328 order.add(1); 329 scheduler.post(new Runnable() { 330 @Override 331 public void run() { 332 order.add(3); 333 } 334 }); 335 order.add(2); 336 } 337 }, 0); 338 339 assertWithMessage("order").that(order).containsExactly(1, 2, 3); 340 assertWithMessage("size").that(scheduler.size()).isEqualTo(0); 341 } 342 343 @Test nestedPostAtFront_whilePaused_runsBeforeSubsequentPost()344 public void nestedPostAtFront_whilePaused_runsBeforeSubsequentPost() { 345 final List<Integer> order = new ArrayList<>(); 346 scheduler.postDelayed(new Runnable() { 347 @Override 348 public void run() { 349 order.add(1); 350 scheduler.postAtFrontOfQueue(new Runnable() { 351 @Override 352 public void run() { 353 order.add(3); 354 } 355 }); 356 order.add(2); 357 } 358 }, 0); 359 scheduler.postDelayed(new Runnable() { 360 @Override 361 public void run() { 362 order.add(4); 363 } 364 }, 0); 365 scheduler.advanceToLastPostedRunnable(); 366 assertWithMessage("order").that(order).containsExactly(1, 2, 3, 4); 367 assertWithMessage("size").that(scheduler.size()).isEqualTo(0); 368 } 369 370 @Test nestedPostAtFront_whileUnpaused_runsAfter()371 public void nestedPostAtFront_whileUnpaused_runsAfter() { 372 final List<Integer> order = new ArrayList<>(); 373 scheduler.unPause(); 374 scheduler.postDelayed(new Runnable() { 375 @Override 376 public void run() { 377 order.add(1); 378 scheduler.postAtFrontOfQueue(new Runnable() { 379 @Override 380 public void run() { 381 order.add(3); 382 } 383 }); 384 order.add(2); 385 } 386 }, 0); 387 assertWithMessage("order").that(order).containsExactly(1, 2, 3); 388 assertWithMessage("size").that(scheduler.size()).isEqualTo(0); 389 } 390 391 @Test nestedPostDelayed_whileUnpaused_doesntAutomaticallyExecute3()392 public void nestedPostDelayed_whileUnpaused_doesntAutomaticallyExecute3() { 393 final List<Integer> order = new ArrayList<>(); 394 scheduler.unPause(); 395 scheduler.postDelayed(new Runnable() { 396 @Override 397 public void run() { 398 order.add(1); 399 scheduler.postDelayed(new Runnable() { 400 @Override 401 public void run() { 402 order.add(3); 403 } 404 }, 1); 405 order.add(2); 406 } 407 }, 0); 408 409 assertWithMessage("order:before").that(order).containsExactly(1, 2); 410 assertWithMessage("size:before").that(scheduler.size()).isEqualTo(1); 411 scheduler.advanceToLastPostedRunnable(); 412 assertWithMessage("order:after").that(order).containsExactly(1, 2, 3); 413 assertWithMessage("size:after").that(scheduler.size()).isEqualTo(0); 414 assertWithMessage("time:after").that(scheduler.getCurrentTime()).isEqualTo(1 + startTime); 415 } 416 417 @Test nestedPostDelayed_whenIdlingConstantly_automaticallyExecutes3After()418 public void nestedPostDelayed_whenIdlingConstantly_automaticallyExecutes3After() { 419 final List<Integer> order = new ArrayList<>(); 420 scheduler.setIdleState(CONSTANT_IDLE); 421 scheduler.postDelayed(new Runnable() { 422 @Override 423 public void run() { 424 order.add(1); 425 scheduler.postDelayed(new Runnable() { 426 @Override 427 public void run() { 428 order.add(3); 429 } 430 }, 1); 431 order.add(2); 432 } 433 }, 0); 434 435 assertWithMessage("order").that(order).containsExactly(1, 2, 3); 436 assertWithMessage("size").that(scheduler.size()).isEqualTo(0); 437 assertWithMessage("time").that(scheduler.getCurrentTime()).isEqualTo(1 + startTime); 438 } 439 440 @Test post_whenTheRunnableThrows_executesSubsequentRunnables()441 public void post_whenTheRunnableThrows_executesSubsequentRunnables() throws Exception { 442 final List<Integer> runnablesThatWereRun = new ArrayList<>(); 443 scheduler.post(new Runnable() { 444 @Override 445 public void run() { 446 runnablesThatWereRun.add(1); 447 throw new RuntimeException("foo"); 448 } 449 }); 450 451 try { 452 scheduler.unPause(); 453 } catch (RuntimeException ignored) { } 454 455 scheduler.post(new Runnable() { 456 @Override 457 public void run() { 458 runnablesThatWereRun.add(2); 459 } 460 }); 461 462 assertThat(runnablesThatWereRun).containsExactly(1, 2); 463 } 464 465 @Test testTimeNotChangedByNegativeDelay()466 public void testTimeNotChangedByNegativeDelay() throws Exception { 467 long currentTime = scheduler.getCurrentTime(); 468 long[] observedTime = new long[1]; 469 scheduler.postDelayed( 470 new Runnable() { 471 @Override 472 public void run() { 473 observedTime[0] = scheduler.getCurrentTime(); 474 } 475 }, 476 -1000); 477 scheduler.advanceToLastPostedRunnable(); 478 assertThat(observedTime[0]).isEqualTo(currentTime); 479 assertThat(scheduler.getCurrentTime()).isEqualTo(currentTime); 480 } 481 482 /** Tests for quadractic or exponential behavior in the scheduler, and stable sorting */ 483 @Test(timeout = 1000) schedulerWithManyRunnables()484 public void schedulerWithManyRunnables() { 485 Random random = new Random(0); 486 Map<Integer, List<Integer>> orderCheck = new TreeMap<>(); 487 List<Integer> actualOrder = new ArrayList<>(); 488 for (int i = 0; i < 20_000; i++) { 489 int delay = random.nextInt(10); 490 List<Integer> list = orderCheck.get(delay); 491 if (list == null) { 492 list = new ArrayList<>(); 493 orderCheck.put(delay, list); 494 } 495 list.add(i); 496 final int localI = i; 497 scheduler.postDelayed( 498 new Runnable() { 499 @Override 500 public void run() { 501 actualOrder.add(localI); 502 } 503 }, 504 delay); 505 } 506 assertThat(actualOrder).isEmpty(); 507 scheduler.advanceToLastPostedRunnable(); 508 assertThat(actualOrder).isEqualTo(ImmutableList.copyOf(Iterables.concat(orderCheck.values()))); 509 } 510 511 @Test(timeout=1000) schedulerAllowsConcurrentTimeRead_whileLockIsHeld()512 public void schedulerAllowsConcurrentTimeRead_whileLockIsHeld() throws InterruptedException { 513 final AtomicLong l = new AtomicLong(); 514 Thread t = new Thread("schedulerAllowsConcurrentTimeRead") { 515 @Override 516 public void run() { 517 l.set(scheduler.getCurrentTime()); 518 } 519 }; 520 // Grab the lock and then start a thread that tries to get the current time. The other thread 521 // should not deadlock. 522 synchronized (scheduler) { 523 t.start(); 524 t.join(); 525 } 526 } 527 528 @Test(timeout = 1000) schedulerAllowsConcurrentStateRead_whileLockIsHeld()529 public void schedulerAllowsConcurrentStateRead_whileLockIsHeld() throws InterruptedException { 530 Thread t = new Thread("schedulerAllowsConcurrentStateRead") { 531 @Override 532 public void run() { 533 scheduler.getIdleState(); 534 } 535 }; 536 // Grab the lock and then start a thread that tries to get the idle state. The other thread 537 // should not deadlock. 538 synchronized (scheduler) { 539 t.start(); 540 t.join(); 541 } 542 } 543 544 @Test(timeout = 1000) schedulerAllowsConcurrentIsPaused_whileLockIsHeld()545 public void schedulerAllowsConcurrentIsPaused_whileLockIsHeld() throws InterruptedException { 546 Thread t = new Thread("schedulerAllowsConcurrentIsPaused") { 547 @Override 548 public void run() { 549 scheduler.isPaused(); 550 } 551 }; 552 // Grab the lock and then start a thread that tries to get the paused state. The other thread 553 // should not deadlock. 554 synchronized (scheduler) { 555 t.start(); 556 t.join(); 557 } 558 } 559 560 private class AddToTranscript implements Runnable { 561 private String event; 562 AddToTranscript(String event)563 public AddToTranscript(String event) { 564 this.event = event; 565 } 566 567 @Override run()568 public void run() { 569 transcript.add(event); 570 } 571 } 572 } 573