Skip to content

Commit f88a7b3

Browse files
authored
Add integration tests for background ANR monitoring stop (#2269)
1 parent 1a20e81 commit f88a7b3

3 files changed

Lines changed: 113 additions & 4 deletions

File tree

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/EmbraceAnrService.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateServ
1414
import io.embrace.android.embracesdk.internal.worker.BackgroundWorker
1515
import java.util.concurrent.Callable
1616
import java.util.concurrent.CopyOnWriteArrayList
17+
import java.util.concurrent.ScheduledFuture
1718
import java.util.concurrent.TimeUnit
1819

1920
/**
@@ -36,6 +37,7 @@ internal class EmbraceAnrService(
3637
) : AnrService, MemoryCleanerListener, ProcessStateListener, BlockedThreadListener {
3738

3839
private val listeners: CopyOnWriteArrayList<BlockedThreadListener> = CopyOnWriteArrayList<BlockedThreadListener>()
40+
private var delayedBackgroundCheckTask: ScheduledFuture<*>? = null
3941

4042
init {
4143
if (processStateService.isInBackground) {
@@ -111,6 +113,8 @@ internal class EmbraceAnrService(
111113
*/
112114
override fun onForeground(coldStart: Boolean, timestamp: Long) {
113115
this.anrMonitorWorker.submit {
116+
// Cancel any pending delayed background check since we're now in foreground
117+
cancelDelayedBackgroundCheck()
114118
state.resetState()
115119
livenessCheckScheduler.startMonitoringThread()
116120
}
@@ -144,7 +148,15 @@ internal class EmbraceAnrService(
144148
* This handles slow app startup scenarios where the app takes time to transition to foreground.
145149
*/
146150
private fun scheduleDelayedBackgroundCheck() {
147-
anrMonitorWorker.schedule<Unit>(::stopMonitoringIfStillInBackground, 10, TimeUnit.SECONDS)
151+
delayedBackgroundCheckTask = anrMonitorWorker.schedule<Unit>(::stopMonitoringIfStillInBackground, 10, TimeUnit.SECONDS)
152+
}
153+
154+
/**
155+
* Cancels the delayed background check task if it exists.
156+
*/
157+
private fun cancelDelayedBackgroundCheck() {
158+
delayedBackgroundCheckTask?.cancel(false)
159+
delayedBackgroundCheckTask = null
148160
}
149161

150162
/**
@@ -155,6 +167,7 @@ internal class EmbraceAnrService(
155167
if (processStateService.isInBackground) {
156168
livenessCheckScheduler.stopMonitoringThread()
157169
}
170+
delayedBackgroundCheckTask = null
158171
}
159172

160173
private companion object {

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/EmbraceAnrServiceRule.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,19 @@ internal class EmbraceAnrServiceRule<T : ScheduledExecutorService>(
4040
lateinit var targetThreadHandler: TargetThreadHandler
4141
lateinit var anrMonitorThread: AtomicReference<Thread>
4242
lateinit var stacktraceSampler: AnrStacktraceSampler
43+
lateinit var looper: Looper
44+
lateinit var worker: BackgroundWorker
4345

4446
override fun before() {
4547
clock.setCurrentTime(0)
46-
val looper: Looper = mockk(relaxed = true)
48+
looper = mockk(relaxed = true)
4749
anrBehavior = FakeAnrBehavior()
4850
anrMonitorThread = AtomicReference(Thread.currentThread())
4951
fakeConfigService = FakeConfigService(anrBehavior = anrBehavior)
5052
fakeProcessStateService = FakeProcessStateService(false)
5153
anrExecutorService = scheduledExecutorSupplier.invoke()
5254
state = ThreadMonitoringState(clock)
53-
val worker =
54-
BackgroundWorker(anrExecutorService)
55+
worker = BackgroundWorker(anrExecutorService)
5556
targetThreadHandler = TargetThreadHandler(
5657
looper = looper,
5758
anrMonitorWorker = worker,
@@ -90,4 +91,21 @@ internal class EmbraceAnrServiceRule<T : ScheduledExecutorService>(
9091
processStateService = fakeProcessStateService
9192
)
9293
}
94+
95+
/**
96+
* Recreates the ANR service. Useful for tests where we need to update the fakes that we pass to the EmbraceAnrService.
97+
*/
98+
fun recreateService() {
99+
anrService = EmbraceAnrService(
100+
configService = fakeConfigService,
101+
looper = looper,
102+
logger = logger,
103+
livenessCheckScheduler = livenessCheckScheduler,
104+
anrMonitorWorker = worker,
105+
state = state,
106+
clock = clock,
107+
stacktraceSampler = stacktraceSampler,
108+
processStateService = fakeProcessStateService
109+
)
110+
}
93111
}

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/EmbraceAnrServiceTimingTest.kt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.junit.Before
99
import org.junit.Rule
1010
import org.junit.Test
1111
import java.lang.Thread.currentThread
12+
import java.util.concurrent.TimeUnit
1213
import java.util.concurrent.atomic.AtomicReference
1314

1415
/**
@@ -67,4 +68,81 @@ internal class EmbraceAnrServiceTimingTest {
6768
assertEquals(1, anrExecutorService.scheduledTasksCount())
6869
}
6970
}
71+
72+
@Test
73+
fun `test delayed background check stops monitoring when app remains in background`() {
74+
with(rule) {
75+
// Set background state and recreate service
76+
fakeProcessStateService.isInBackground = true
77+
recreateService()
78+
79+
// Start ANR capture - this should trigger scheduleDelayedBackgroundCheck
80+
anrService.startAnrCapture()
81+
anrExecutorService.runCurrentlyBlocked()
82+
83+
// Monitoring is initially started (even in background)
84+
assertTrue(state.started.get())
85+
86+
// Advance time by 9 seconds - monitoring should still be active
87+
anrExecutorService.moveForwardAndRunBlocked(TimeUnit.SECONDS.toMillis(9))
88+
assertTrue(state.started.get())
89+
90+
// Advance time by 2 more second to trigger the 10-second delayed check
91+
anrExecutorService.moveForwardAndRunBlocked(TimeUnit.SECONDS.toMillis(1))
92+
93+
// Run any pending tasks to ensure the delayed check executes
94+
anrExecutorService.runCurrentlyBlocked()
95+
96+
// Verify that monitoring is now stopped because app is still in background
97+
assertFalse(state.started.get())
98+
}
99+
}
100+
101+
@Test
102+
fun `test delayed background check does not stop monitoring when app transitions to foreground`() {
103+
with(rule) {
104+
// Set background state and recreate service
105+
fakeProcessStateService.isInBackground = true
106+
recreateService()
107+
108+
// Start ANR capture - this should trigger scheduleDelayedBackgroundCheck
109+
anrService.startAnrCapture()
110+
anrExecutorService.runCurrentlyBlocked()
111+
112+
// Monitoring is initially started (even in background)
113+
assertTrue(state.started.get())
114+
115+
// Advance time by 5 seconds
116+
anrExecutorService.moveForwardAndRunBlocked(TimeUnit.SECONDS.toMillis(5))
117+
118+
// Transition to foreground before the 10-second delay
119+
fakeProcessStateService.isInBackground = false
120+
anrService.onForeground(false, clock.now())
121+
anrExecutorService.runCurrentlyBlocked()
122+
123+
// Advance time by 5 more seconds to reach the 10-second mark
124+
anrExecutorService.moveForwardAndRunBlocked(TimeUnit.SECONDS.toMillis(5))
125+
126+
// Verify that monitoring is still active because app is now in foreground
127+
assertTrue(state.started.get())
128+
}
129+
}
130+
131+
@Test
132+
fun `test delayed background check is not scheduled when app starts in foreground`() {
133+
with(rule) {
134+
// Start ANR capture - this won't trigger scheduleDelayedBackgroundCheck as the default state is foreground
135+
anrService.startAnrCapture()
136+
anrExecutorService.runCurrentlyBlocked()
137+
138+
// Verify that monitoring is started
139+
assertTrue(state.started.get())
140+
141+
// Advance time by 15 seconds to ensure any delayed check would have triggered
142+
anrExecutorService.moveForwardAndRunBlocked(TimeUnit.SECONDS.toMillis(15))
143+
144+
// Verify that monitoring is still active (no delayed check was scheduled)
145+
assertTrue(state.started.get())
146+
}
147+
}
70148
}

0 commit comments

Comments
 (0)