@@ -29,11 +29,13 @@ import org.junit.runner.RunWith
2929import org.mockito.kotlin.any
3030import org.mockito.kotlin.anyOrNull
3131import org.mockito.kotlin.argThat
32+ import org.mockito.kotlin.argumentCaptor
3233import org.mockito.kotlin.atMost
3334import org.mockito.kotlin.check
3435import org.mockito.kotlin.inOrder
3536import org.mockito.kotlin.mock
3637import org.mockito.kotlin.never
38+ import org.mockito.kotlin.times
3739import org.mockito.kotlin.verify
3840import org.mockito.kotlin.whenever
3941import org.robolectric.annotation.Config
@@ -269,6 +271,72 @@ class MemoryLimiterIntegrationTest {
269271 )
270272 }
271273
274+ @Test
275+ fun `maps ApplicationExitInfo importance to process visibility` () {
276+ // (importance, expected visibility) covering every branch of getProcessVisibility, including
277+ // the default fallback for an unknown/future importance value.
278+ val cases =
279+ listOf (
280+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_FOREGROUND to
281+ MemoryLimiterIntegration .PROCESS_VISIBILITY_VISIBLE ,
282+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_VISIBLE to
283+ MemoryLimiterIntegration .PROCESS_VISIBILITY_VISIBLE ,
284+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_TOP_SLEEPING to
285+ MemoryLimiterIntegration .PROCESS_VISIBILITY_VISIBLE ,
286+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_FOREGROUND_SERVICE to
287+ MemoryLimiterIntegration .PROCESS_VISIBILITY_NOT_VISIBLE ,
288+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_PERCEPTIBLE to
289+ MemoryLimiterIntegration .PROCESS_VISIBILITY_NOT_VISIBLE ,
290+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_SERVICE to
291+ MemoryLimiterIntegration .PROCESS_VISIBILITY_NOT_VISIBLE ,
292+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_CANT_SAVE_STATE to
293+ MemoryLimiterIntegration .PROCESS_VISIBILITY_NOT_VISIBLE ,
294+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_GONE to
295+ MemoryLimiterIntegration .PROCESS_VISIBILITY_NOT_VISIBLE ,
296+ ActivityManager .RunningAppProcessInfo .IMPORTANCE_CACHED to
297+ MemoryLimiterIntegration .PROCESS_VISIBILITY_CACHED ,
298+ // An importance value outside the known bands must fall back to the least specific bucket.
299+ Int .MAX_VALUE to MemoryLimiterIntegration .PROCESS_VISIBILITY_CACHED ,
300+ )
301+
302+ val baseTimestamp = System .currentTimeMillis() - 60_000
303+ val integration =
304+ fixture.getSut(
305+ memoryLimiterEnabled = true ,
306+ reportHistoricalMemoryLimiterExits = true ,
307+ dir = tmpDir,
308+ sdkVersion = 37 ,
309+ lastReportedTimestamp = baseTimestamp - 10_000 ,
310+ )
311+ // The captured events are never really flushed by the mocked scopes, so don't block on the
312+ // per-event flush latch (its default timeout would make this test take minutes).
313+ fixture.options.flushTimeoutMillis = 0
314+
315+ // Give each importance a distinct timestamp so captured events can be matched back to it.
316+ cases.forEachIndexed { index, (importance, _) ->
317+ fixture.addAppExitInfo(timestamp = baseTimestamp + index * 1_000L , importance = importance)
318+ }
319+
320+ integration.register(fixture.scopes, fixture.options)
321+
322+ val eventCaptor = argumentCaptor<SentryEvent >()
323+ verify(fixture.scopes, times(cases.size)).captureEvent(eventCaptor.capture(), anyOrNull<Hint >())
324+ val visibilityByTimestamp =
325+ eventCaptor.allValues.associate { event ->
326+ event.timestamp!! .time to
327+ event.exceptions!! .single().mechanism!! .data!! [
328+ MemoryLimiterIntegration .PROCESS_VISIBILITY_DATA_KEY ]
329+ }
330+
331+ cases.forEachIndexed { index, (importance, expectedVisibility) ->
332+ assertEquals(
333+ expectedVisibility,
334+ visibilityByTimestamp[baseTimestamp + index * 1_000L ],
335+ " importance $importance should map to $expectedVisibility " ,
336+ )
337+ }
338+ }
339+
272340 @Test
273341 fun `ignores exit when reason does not match exits produced by MemoryLimiter` () {
274342 val integration = fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp)
0 commit comments