Skip to content

Commit 3b1d8fd

Browse files
43jayclaude
andcommitted
test(profiling): Add unit test for PerfettoContinuousProfiler rate limiting
Verify that onRateLimitChanged stops the profiler, resets profiler/chunk IDs, and logs the expected warning. Run with: ./gradlew :sentry-android-core:testDebugUnitTest --tests "io.sentry.android.core.PerfettoContinuousProfilerTest" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 232ca4c commit 3b1d8fd

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package io.sentry.android.core
2+
3+
import android.content.Context
4+
import android.os.Build
5+
import androidx.test.core.app.ApplicationProvider
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import io.sentry.DataCategory
8+
import io.sentry.ILogger
9+
import io.sentry.IScopes
10+
import io.sentry.ProfileLifecycle
11+
import io.sentry.Sentry
12+
import io.sentry.SentryLevel
13+
import io.sentry.TracesSampler
14+
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector
15+
import io.sentry.protocol.SentryId
16+
import io.sentry.test.DeferredExecutorService
17+
import io.sentry.test.injectForField
18+
import io.sentry.transport.RateLimiter
19+
import kotlin.test.AfterTest
20+
import kotlin.test.BeforeTest
21+
import kotlin.test.Test
22+
import kotlin.test.assertEquals
23+
import kotlin.test.assertFalse
24+
import kotlin.test.assertTrue
25+
import org.junit.runner.RunWith
26+
import org.mockito.Mockito.mockStatic
27+
import org.mockito.kotlin.any
28+
import org.mockito.kotlin.eq
29+
import org.mockito.kotlin.mock
30+
import org.mockito.kotlin.spy
31+
import org.mockito.kotlin.verify
32+
import org.mockito.kotlin.whenever
33+
34+
@RunWith(AndroidJUnit4::class)
35+
class PerfettoContinuousProfilerTest {
36+
private lateinit var context: Context
37+
private val fixture = Fixture()
38+
39+
private class Fixture {
40+
private val mockDsn = "http://key@localhost/proj"
41+
val buildInfo =
42+
mock<BuildInfoProvider> {
43+
whenever(it.sdkInfoVersion).thenReturn(Build.VERSION_CODES.VANILLA_ICE_CREAM)
44+
}
45+
val executor = DeferredExecutorService()
46+
val mockedSentry = mockStatic(Sentry::class.java)
47+
val mockLogger = mock<ILogger>()
48+
val mockTracesSampler = mock<TracesSampler>()
49+
val mockPerfettoProfiler = mock<PerfettoProfiler>()
50+
51+
val scopes: IScopes = mock()
52+
val frameMetricsCollector: SentryFrameMetricsCollector = mock()
53+
54+
val options =
55+
spy(SentryAndroidOptions()).apply {
56+
dsn = mockDsn
57+
profilesSampleRate = 1.0
58+
isDebug = true
59+
setLogger(mockLogger)
60+
}
61+
62+
init {
63+
whenever(mockTracesSampler.sampleSessionProfile(any())).thenReturn(true)
64+
whenever(mockPerfettoProfiler.start(any())).thenReturn(
65+
AndroidProfiler.ProfileStartData(
66+
System.nanoTime(),
67+
0L,
68+
io.sentry.DateUtils.getCurrentDateTime(),
69+
),
70+
)
71+
}
72+
73+
fun getSut(): PerfettoContinuousProfiler {
74+
options.executorService = executor
75+
whenever(scopes.options).thenReturn(options)
76+
val profiler =
77+
PerfettoContinuousProfiler(
78+
ApplicationProvider.getApplicationContext(),
79+
buildInfo,
80+
frameMetricsCollector,
81+
mockLogger,
82+
{ options.executorService },
83+
)
84+
// Inject mock PerfettoProfiler to avoid hitting real ProfilingManager API
85+
profiler.injectForField("perfettoProfiler", mockPerfettoProfiler)
86+
profiler.injectForField("isInitialized", true)
87+
return profiler
88+
}
89+
}
90+
91+
@BeforeTest
92+
fun `set up`() {
93+
context = ApplicationProvider.getApplicationContext()
94+
Sentry.setCurrentScopes(fixture.scopes)
95+
fixture.mockedSentry.`when`<Any> { Sentry.getCurrentScopes() }.thenReturn(fixture.scopes)
96+
}
97+
98+
@AfterTest
99+
fun clear() {
100+
fixture.mockedSentry.close()
101+
}
102+
103+
@Test
104+
fun `profiler stops when rate limited`() {
105+
val profiler = fixture.getSut()
106+
val rateLimiter = mock<RateLimiter>()
107+
whenever(rateLimiter.isActiveForCategory(DataCategory.ProfileChunkUi)).thenReturn(true)
108+
109+
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
110+
assertTrue(profiler.isRunning)
111+
112+
profiler.onRateLimitChanged(rateLimiter)
113+
assertFalse(profiler.isRunning)
114+
assertEquals(SentryId.EMPTY_ID, profiler.profilerId)
115+
assertEquals(SentryId.EMPTY_ID, profiler.chunkId)
116+
verify(fixture.mockLogger)
117+
.log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler."))
118+
}
119+
}

0 commit comments

Comments
 (0)