2020import static junit .framework .Assert .assertTrue ;
2121import static org .junit .Assert .assertEquals ;
2222import static org .junit .Assert .assertFalse ;
23+ import static org .junit .Assert .assertThrows ;
2324import static org .junit .Assert .fail ;
2425
2526import android .os .Handler ;
2627import android .os .Looper ;
2728import android .os .Message ;
29+ import android .os .SystemClock ;
2830import android .util .Log ;
31+ import androidx .test .espresso .AppNotIdleException ;
32+ import androidx .test .espresso .IdlingPolicies ;
33+ import androidx .test .espresso .IdlingPolicy ;
2934import androidx .test .espresso .IdlingResourceTimeoutException ;
3035import androidx .test .espresso .base .IdlingResourceRegistry .IdleNotificationCallback ;
3136import androidx .test .ext .junit .runners .AndroidJUnit4 ;
@@ -95,6 +100,10 @@ public Handler getHandler() {
95100
96101 @ Before
97102 public void setUp () throws Exception {
103+ // Reset the master policy timeout to the default value.
104+ IdlingPolicies .setMasterPolicyTimeout (2 , TimeUnit .SECONDS );
105+ IdlingPolicies .setIdlingResourceTimeout (1 , TimeUnit .SECONDS );
106+
98107 testThread = new LooperThread ();
99108 testThread .setUncaughtExceptionHandler (
100109 new Thread .UncaughtExceptionHandler () {
@@ -127,6 +136,10 @@ public IdleNotifier<IdleNotificationCallback> get() {
127136
128137 @ After
129138 public void tearDown () throws Exception {
139+ // Reset the master policy timeout to the default value.
140+ IdlingPolicies .setMasterPolicyTimeout (60 , TimeUnit .SECONDS );
141+ IdlingPolicies .setIdlingResourceTimeout (26 , TimeUnit .SECONDS );
142+
130143 testThread .quitLooper ();
131144 asyncPool .shutdown ();
132145 }
@@ -319,10 +332,10 @@ public void run() {
319332 }
320333 }));
321334 assertFalse (
322- "Should not have stopped looping the main thread yet!" , latch .await (2 , TimeUnit .SECONDS ));
335+ "Should not have stopped looping the main thread yet!" , latch .await (1 , TimeUnit .SECONDS ));
323336 assertEquals ("Not all main thread tasks have checked in" , 1L , latch .getCount ());
324337 asyncTaskShouldComplete .countDown ();
325- assertTrue ("App should be idle." , latch .await (5 , TimeUnit .SECONDS ));
338+ assertTrue ("App should be idle." , latch .await (1 , TimeUnit .SECONDS ));
326339 }
327340
328341 @ Test
@@ -366,9 +379,63 @@ public void run() {
366379 }
367380 }));
368381 assertFalse (
369- "Should not have stopped looping the main thread yet!" , latch .await (2 , TimeUnit .SECONDS ));
382+ "Should not have stopped looping the main thread yet!" ,
383+ latch .await (100 , TimeUnit .MILLISECONDS ));
370384 fakeResource .forceIdleNow ();
371- assertTrue ("App should be idle." , latch .await (5 , TimeUnit .SECONDS ));
385+ assertTrue ("App should be idle." , latch .await (1 , TimeUnit .SECONDS ));
386+ }
387+
388+ @ Test
389+ public void loopMainThreadUntilIdle_racyIdleResource () throws InterruptedException {
390+ IdlingPolicies .setMasterPolicyTimeout (100 , TimeUnit .MILLISECONDS );
391+ OnDemandIdlingResource fakeResource = new OnDemandIdlingResource ("FakeResource" );
392+ idlingResourceRegistry .registerResources (singletonList (fakeResource ));
393+ final CountDownLatch startedLatch = new CountDownLatch (1 );
394+ final CountDownLatch interruptedLatch = new CountDownLatch (1 );
395+ assertTrue (
396+ testThread
397+ .getHandler ()
398+ .post (
399+ () -> {
400+ // This is the sequence of events we want:
401+ // - Resource starts busy.
402+ // - Resource becomes idle, event posted to queue to update idle registry.
403+ // - Timeout occurs, no more events will be processed.
404+ // - Espresso detects race condition while checking if the resource is idle.
405+ IdlingPolicy masterIdlingPolicy = IdlingPolicies .getMasterIdlingPolicy ();
406+ long expectedTimeout =
407+ SystemClock .uptimeMillis ()
408+ + masterIdlingPolicy
409+ .getIdleTimeoutUnit ()
410+ .toMillis (masterIdlingPolicy .getIdleTimeout ());
411+ testThread
412+ .getHandler ()
413+ .post (
414+ () -> {
415+ Log .i (TAG , "Forcing resource to be idle." );
416+ fakeResource .forceIdleNow ();
417+ // Busy wait until after the timeout to ensure race buster cannot run.
418+ try {
419+ Thread .sleep (200 );
420+ } catch (InterruptedException e ) {
421+ throw new RuntimeException (e );
422+ }
423+ assertTrue (
424+ "Sleep should bring us past the timeout." ,
425+ SystemClock .uptimeMillis () > expectedTimeout );
426+ });
427+
428+ Log .i (TAG , "Hijacking thread and looping it." );
429+ startedLatch .countDown ();
430+ assertThrows (
431+ "Expected for loopMainThreadUntilIdle to be interrupted" ,
432+ AppNotIdleException .class ,
433+ () -> uiController .get ().loopMainThreadUntilIdle ());
434+ interruptedLatch .countDown ();
435+ }));
436+
437+ assertTrue ("looper has started." , startedLatch .await (2 , TimeUnit .SECONDS ));
438+ assertTrue ("App should be interrupted." , interruptedLatch .await (5 , TimeUnit .SECONDS ));
372439 }
373440
374441 @ Test
@@ -392,18 +459,22 @@ public void run() {
392459 }
393460 }));
394461 assertFalse (
395- "Should not have stopped looping the main thread yet!" , latch .await (1 , TimeUnit .SECONDS ));
462+ "Should not have stopped looping the main thread yet!" ,
463+ latch .await (100 , TimeUnit .MILLISECONDS ));
396464 fakeResource1 .forceIdleNow ();
397465 assertFalse (
398- "Should not have stopped looping the main thread yet!" , latch .await (1 , TimeUnit .SECONDS ));
466+ "Should not have stopped looping the main thread yet!" ,
467+ latch .await (100 , TimeUnit .MILLISECONDS ));
399468 idlingResourceRegistry .registerResources (singletonList (fakeResource3 ));
400469 assertFalse (
401- "Should not have stopped looping the main thread yet!" , latch .await (1 , TimeUnit .SECONDS ));
470+ "Should not have stopped looping the main thread yet!" ,
471+ latch .await (100 , TimeUnit .MILLISECONDS ));
402472 fakeResource2 .forceIdleNow ();
403473 assertFalse (
404- "Should not have stopped looping the main thread yet!" , latch .await (1 , TimeUnit .SECONDS ));
474+ "Should not have stopped looping the main thread yet!" ,
475+ latch .await (100 , TimeUnit .MILLISECONDS ));
405476 fakeResource3 .forceIdleNow ();
406- assertTrue ("App should be idle." , latch .await (5 , TimeUnit .SECONDS ));
477+ assertTrue ("App should be idle." , latch .await (1 , TimeUnit .SECONDS ));
407478 }
408479
409480 @ Test
@@ -430,13 +501,15 @@ public void run() {
430501 }
431502 }));
432503 assertFalse (
433- "Should not have stopped looping the main thread yet!" , latch .await (4 , TimeUnit .SECONDS ));
504+ "Should not have stopped looping the main thread yet!" ,
505+ latch .await (100 , TimeUnit .MILLISECONDS ));
434506 goodResource .forceIdleNow ();
435507 assertFalse (
436- "Should not have stopped looping the main thread yet!" , latch .await (12 , TimeUnit .SECONDS ));
508+ "Should not have stopped looping the main thread yet!" ,
509+ latch .await (100 , TimeUnit .MILLISECONDS ));
437510 kindaCrappyResource .forceIdleNow ();
438511 assertTrue (
439- "Should have caught IdlingResourceTimeoutException" , latch .await (11 , TimeUnit .SECONDS ));
512+ "Should have caught IdlingResourceTimeoutException" , latch .await (900 , TimeUnit .SECONDS ));
440513 }
441514
442515 @ Test
0 commit comments