@@ -4,10 +4,11 @@ import android.app.ActivityManager
44import android.app.ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING
55import android.app.ActivityManager.ProcessErrorStateInfo.NO_ERROR
66import android.content.Context
7- import io.sentry.transport.ICurrentDateProvider
7+ import io.sentry.time.TestTicker
88import java.util.concurrent.CountDownLatch
99import java.util.concurrent.Executors
1010import java.util.concurrent.TimeUnit
11+ import java.util.concurrent.TimeUnit.MILLISECONDS
1112import kotlin.test.Test
1213import kotlin.test.assertEquals
1314import kotlin.test.assertNotNull
@@ -20,12 +21,12 @@ import org.mockito.kotlin.mock
2021import org.mockito.kotlin.whenever
2122
2223class ANRWatchDogTest {
23- private var currentTimeMs = 0L
24- private val timeProvider = ICurrentDateProvider { currentTimeMs }
24+ private lateinit var clock: TestTicker
2525
2626 @Before
2727 fun `setup` () {
28- currentTimeMs = 12341234
28+ // an arbitrary, non-zero origin: no caller may assume a tick counts from zero
29+ clock = TestTicker (MILLISECONDS .toNanos(12341234 ))
2930 }
3031
3132 @Test
@@ -42,8 +43,7 @@ class ANRWatchDogTest {
4243 whenever(handler.thread).thenReturn(thread)
4344 val interval = 10L
4445
45- val sut =
46- ANRWatchDog (timeProvider, interval, 1L , true , { a -> anr = a }, mock(), handler, mock())
46+ val sut = ANRWatchDog (clock, interval, 1L , true , { a -> anr = a }, mock(), handler, mock())
4747 val es = Executors .newSingleThreadExecutor()
4848 try {
4949 es.submit { sut.run () }
@@ -53,7 +53,7 @@ class ANRWatchDogTest {
5353 ) // Wait until worker posts the job for the "UI thread"
5454 var waitCount = 0
5555 do {
56- currentTimeMs + = 100L
56+ clock.advance( 100 , MILLISECONDS )
5757 Thread .sleep(100 ) // Let worker realize this is ANR
5858 } while (anr == null && waitCount++ < 100 )
5959
@@ -79,15 +79,14 @@ class ANRWatchDogTest {
7979 whenever(handler.thread).thenReturn(thread)
8080 val interval = 10L
8181
82- val sut =
83- ANRWatchDog (timeProvider, interval, 1L , true , { a -> anr = a }, mock(), handler, mock())
82+ val sut = ANRWatchDog (clock, interval, 1L , true , { a -> anr = a }, mock(), handler, mock())
8483 val es = Executors .newSingleThreadExecutor()
8584 try {
8685 es.submit { sut.run () }
8786
8887 var waitCount = 0
8988 do {
90- currentTimeMs + = 100L
89+ clock.advance( 100 , MILLISECONDS )
9190 Thread .sleep(100 ) // Let worker realize his runner always runs
9291 } while (! invoked && waitCount++ < 100 )
9392
@@ -121,8 +120,7 @@ class ANRWatchDogTest {
121120 val anrs = listOf (stateInfo)
122121 whenever(am.processesInErrorState).thenReturn(anrs)
123122
124- val sut =
125- ANRWatchDog (timeProvider, interval, 1L , true , { a -> anr = a }, mock(), handler, context)
123+ val sut = ANRWatchDog (clock, interval, 1L , true , { a -> anr = a }, mock(), handler, context)
126124 val es = Executors .newSingleThreadExecutor()
127125 try {
128126 es.submit { sut.run () }
@@ -132,7 +130,7 @@ class ANRWatchDogTest {
132130 ) // Wait until worker posts the job for the "UI thread"
133131 var waitCount = 0
134132 do {
135- currentTimeMs + = 100L
133+ clock.advance( 100 , MILLISECONDS )
136134 Thread .sleep(100 ) // Let worker realize this is ANR
137135 } while (anr == null && waitCount++ < 100 )
138136
@@ -167,8 +165,7 @@ class ANRWatchDogTest {
167165 val anrs = listOf (stateInfo)
168166 whenever(am.processesInErrorState).thenReturn(anrs)
169167
170- val sut =
171- ANRWatchDog (timeProvider, interval, 1L , true , { a -> anr = a }, mock(), handler, context)
168+ val sut = ANRWatchDog (clock, interval, 1L , true , { a -> anr = a }, mock(), handler, context)
172169 val es = Executors .newSingleThreadExecutor()
173170 try {
174171 es.submit { sut.run () }
@@ -178,7 +175,7 @@ class ANRWatchDogTest {
178175 ) // Wait until worker posts the job for the "UI thread"
179176 var waitCount = 0
180177 do {
181- currentTimeMs + = 100L
178+ clock.advance( 100 , MILLISECONDS )
182179 Thread .sleep(100L ) // Let worker realize this is ANR
183180 } while (anr == null && waitCount++ < 100 )
184181 assertNull(anr) // callback never ran
@@ -187,4 +184,31 @@ class ANRWatchDogTest {
187184 es.shutdown()
188185 }
189186 }
187+
188+ @Test
189+ fun `a device suspend does not trip the ANR threshold` () {
190+ // The uptime clock stands still while the device is suspended, so a suspend cannot be mistaken
191+ // for a blocked main thread. On an elapsed-real-time clock the suspended interval would count
192+ // against the main thread and fabricate an ANR.
193+ var anr: ApplicationNotResponding ? = null
194+ val handler = mock<MainLooperHandler >()
195+ val latch = CountDownLatch (1 )
196+ whenever(handler.post(any())).then { latch.countDown() }
197+ whenever(handler.thread).thenReturn(mock<Thread >())
198+
199+ // context is a mock, so ActivityManager is absent and any passed deadline reports an ANR
200+ val sut = ANRWatchDog (clock, 10L , 1L , true , { a -> anr = a }, mock(), handler, mock())
201+ val es = Executors .newSingleThreadExecutor()
202+ try {
203+ es.submit { sut.run () }
204+
205+ assertTrue(latch.await(10L , TimeUnit .SECONDS )) // wait until the watchdog is polling
206+ Thread .sleep(200 ) // hundreds of polls of wall time, none of it uptime
207+
208+ assertNull(anr)
209+ } finally {
210+ sut.interrupt()
211+ es.shutdown()
212+ }
213+ }
190214}
0 commit comments