@@ -6,20 +6,18 @@ import android.os.Build
66import androidx.test.ext.junit.runners.AndroidJUnit4
77import io.sentry.IScopes
88import io.sentry.ISentryExecutorService
9- import io.sentry.ISpan
10- import io.sentry.ITransaction
11- import io.sentry.TransactionContext
9+ import io.sentry.protocol.SentryTransaction
1210import java.util.concurrent.Callable
1311import java.util.function.Consumer
1412import kotlin.test.Test
1513import kotlin.test.assertEquals
1614import kotlin.test.assertNotNull
15+ import kotlin.test.assertTrue
1716import org.junit.Before
1817import org.junit.runner.RunWith
1918import org.mockito.kotlin.any
2019import org.mockito.kotlin.anyOrNull
2120import org.mockito.kotlin.argumentCaptor
22- import org.mockito.kotlin.eq
2321import org.mockito.kotlin.mock
2422import org.mockito.kotlin.never
2523import org.mockito.kotlin.verify
@@ -50,7 +48,12 @@ class ApplicationStartInfoIntegrationTest {
5048 options.isEnableApplicationStartInfo = true
5149 options.executorService = executor
5250 options.setLogger(mock< io.sentry.ILogger > ())
53- options.dateProvider = mock< io.sentry.SentryDateProvider > ()
51+
52+ val mockDateProvider = mock< io.sentry.SentryDateProvider > ()
53+ val mockDate = mock< io.sentry.SentryDate > ()
54+ whenever(mockDate.nanoTimestamp()).thenReturn(System .currentTimeMillis() * 1_000_000L )
55+ whenever(mockDateProvider.now()).thenReturn(mockDate)
56+ options.dateProvider = mockDateProvider
5457
5558 // Mock BuildInfoProvider to return API 35+
5659 whenever(buildInfoProvider.sdkInfoVersion).thenReturn(Build .VERSION_CODES .VANILLA_ICE_CREAM )
@@ -92,168 +95,117 @@ class ApplicationStartInfoIntegrationTest {
9295 @Test
9396 fun `transaction includes correct tags from ApplicationStartInfo` () {
9497 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
98+ val transactionCaptor = argumentCaptor<SentryTransaction >()
9599 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
96100 integration.register(scopes, options)
97101
98102 verify(activityManager)
99103 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
100104
101- val mockTransaction = mock<ITransaction >()
102- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
103- .thenReturn(mockTransaction)
104-
105105 val startInfo = createMockApplicationStartInfo()
106106 listenerCaptor.firstValue.accept(startInfo)
107107
108- verify(mockTransaction).setTag(eq(" start.reason" ), any())
108+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
109+ val transaction = transactionCaptor.firstValue
110+ assertNotNull(transaction.tags)
111+ assertTrue(transaction.tags!! .containsKey(" start.reason" ))
112+ assertTrue(transaction.tags!! .containsKey(" start.type" ))
113+ assertTrue(transaction.tags!! .containsKey(" start.launch_mode" ))
109114 }
110115
111116 @Test
112117 fun `transaction includes start type from ApplicationStartInfo` () {
113118 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
119+ val transactionCaptor = argumentCaptor<SentryTransaction >()
114120 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
115121 integration.register(scopes, options)
116122
117123 verify(activityManager)
118124 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
119125
120- val mockTransaction = mock<ITransaction >()
121- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
122- .thenReturn(mockTransaction)
123-
124- val startInfo = createMockApplicationStartInfo()
125- whenever(startInfo.startType)
126- .thenReturn(
127- if (Build .VERSION .SDK_INT >= 35 ) android.app.ApplicationStartInfo .START_TYPE_COLD else 0
128- )
126+ val startInfo =
127+ createMockApplicationStartInfo(startType = android.app.ApplicationStartInfo .START_TYPE_COLD )
129128 listenerCaptor.firstValue.accept(startInfo)
130129
131- verify(mockTransaction).setTag(" start.type" , " cold" )
130+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
131+ assertEquals(" cold" , transactionCaptor.firstValue.tags!! [" start.type" ])
132132 }
133133
134134 @Test
135135 fun `transaction includes launch mode from ApplicationStartInfo` () {
136136 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
137+ val transactionCaptor = argumentCaptor<SentryTransaction >()
137138 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
138139 integration.register(scopes, options)
139140
140141 verify(activityManager)
141142 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
142143
143- val mockTransaction = mock<ITransaction >()
144- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
145- .thenReturn(mockTransaction)
146-
147- val startInfo = createMockApplicationStartInfo()
148- whenever(startInfo.launchMode)
149- .thenReturn(
150- if (Build .VERSION .SDK_INT >= 35 ) android.app.ApplicationStartInfo .LAUNCH_MODE_STANDARD
151- else 0
144+ val startInfo =
145+ createMockApplicationStartInfo(
146+ launchMode = android.app.ApplicationStartInfo .LAUNCH_MODE_STANDARD
152147 )
153148 listenerCaptor.firstValue.accept(startInfo)
154149
155- verify(mockTransaction).setTag(" start.launch_mode" , " standard" )
150+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
151+ assertEquals(" standard" , transactionCaptor.firstValue.tags!! [" start.launch_mode" ])
156152 }
157153
158154 @Test
159155 fun `creates bind_application span when timestamp available` () {
160156 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
157+ val transactionCaptor = argumentCaptor<SentryTransaction >()
161158 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
162159 integration.register(scopes, options)
163160
164161 verify(activityManager)
165162 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
166163
167- val mockTransaction = mock<ITransaction >()
168- val mockSpan = mock<ISpan >()
169- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
170- .thenReturn(mockTransaction)
171- whenever(
172- mockTransaction.startChild(eq(" app.start.bind_application" ), anyOrNull(), any(), any())
173- )
174- .thenReturn(mockSpan)
175-
176164 val startInfo =
177165 createMockApplicationStartInfo(forkTime = 1000000000L , bindApplicationTime = 1100000000L )
178166 listenerCaptor.firstValue.accept(startInfo)
179167
180- verify(mockTransaction).startChild(eq(" app.start.bind_application" ), anyOrNull(), any(), any())
181- verify(mockSpan).finish(any(), any())
182- }
183-
184- @Test
185- fun `creates application_oncreate span when timestamp available` () {
186- val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
187- val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
188- integration.register(scopes, options)
189-
190- verify(activityManager)
191- .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
192-
193- val mockTransaction = mock<ITransaction >()
194- val mockSpan = mock<ISpan >()
195- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
196- .thenReturn(mockTransaction)
197- whenever(
198- mockTransaction.startChild(eq(" app.start.application_oncreate" ), anyOrNull(), any(), any())
199- )
200- .thenReturn(mockSpan)
201-
202- val startInfo =
203- createMockApplicationStartInfo(forkTime = 1000000000L , applicationOnCreateTime = 1200000000L )
204- listenerCaptor.firstValue.accept(startInfo)
205-
206- verify(mockTransaction)
207- .startChild(eq(" app.start.application_oncreate" ), anyOrNull(), any(), any())
208- verify(mockSpan).finish(any(), any())
168+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
169+ val spans = transactionCaptor.firstValue.spans
170+ assertTrue(spans.any { it.op == " bind_application" })
209171 }
210172
211173 @Test
212174 fun `creates ttid span when timestamp available` () {
213175 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
176+ val transactionCaptor = argumentCaptor<SentryTransaction >()
214177 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
215178 integration.register(scopes, options)
216179
217180 verify(activityManager)
218181 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
219182
220- val mockTransaction = mock<ITransaction >()
221- val mockSpan = mock<ISpan >()
222- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
223- .thenReturn(mockTransaction)
224- whenever(mockTransaction.startChild(eq(" app.start.ttid" ), anyOrNull(), any(), any()))
225- .thenReturn(mockSpan)
226-
227183 val startInfo =
228184 createMockApplicationStartInfo(forkTime = 1000000000L , firstFrameTime = 1500000000L )
229185 listenerCaptor.firstValue.accept(startInfo)
230186
231- verify(mockTransaction).startChild(eq(" app.start.ttid" ), anyOrNull(), any(), any())
232- verify(mockSpan).finish(any(), any())
187+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
188+ val spans = transactionCaptor.firstValue.spans
189+ assertTrue(spans.any { it.op == " ttid" })
233190 }
234191
235192 @Test
236193 fun `creates ttfd span when timestamp available` () {
237194 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
195+ val transactionCaptor = argumentCaptor<SentryTransaction >()
238196 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
239197 integration.register(scopes, options)
240198
241199 verify(activityManager)
242200 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
243201
244- val mockTransaction = mock<ITransaction >()
245- val mockSpan = mock<ISpan >()
246- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ()))
247- .thenReturn(mockTransaction)
248- whenever(mockTransaction.startChild(eq(" app.start.ttfd" ), anyOrNull(), any(), any()))
249- .thenReturn(mockSpan)
250-
251202 val startInfo =
252203 createMockApplicationStartInfo(forkTime = 1000000000L , fullyDrawnTime = 2000000000L )
253204 listenerCaptor.firstValue.accept(startInfo)
254205
255- verify(mockTransaction).startChild(eq(" app.start.ttfd" ), anyOrNull(), any(), any())
256- verify(mockSpan).finish(any(), any())
206+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
207+ val spans = transactionCaptor.firstValue.spans
208+ assertTrue(spans.any { it.op == " ttfd" })
257209 }
258210
259211 @Test
@@ -266,63 +218,60 @@ class ApplicationStartInfoIntegrationTest {
266218 }
267219
268220 @Test
269- fun `transaction name includes reason label ` () {
221+ fun `transaction name is app_start ` () {
270222 val listenerCaptor = argumentCaptor<Consumer <android.app.ApplicationStartInfo >>()
223+ val transactionCaptor = argumentCaptor<SentryTransaction >()
271224 val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
272225 integration.register(scopes, options)
273226
274227 verify(activityManager)
275228 .addApplicationStartInfoCompletionListener(any(), listenerCaptor.capture())
276229
277- var capturedContext: TransactionContext ? = null
278- whenever(scopes.startTransaction(any(), any< io.sentry.TransactionOptions > ())).thenAnswer {
279- capturedContext = it.arguments[0 ] as TransactionContext
280- mock()
281- }
282-
283230 val startInfo = createMockApplicationStartInfo()
284- whenever(startInfo.reason)
285- .thenReturn(
286- if (Build .VERSION .SDK_INT >= 35 ) android.app.ApplicationStartInfo .START_REASON_LAUNCHER
287- else 0
288- )
289231 listenerCaptor.firstValue.accept(startInfo)
290232
291- assertNotNull(capturedContext)
292- assertEquals(" app.start.launcher" , capturedContext!! .name)
233+ verify(scopes).captureTransaction(transactionCaptor.capture(), anyOrNull(), anyOrNull())
234+ assertEquals(" app.start" , transactionCaptor.firstValue.transaction)
235+ }
236+
237+ @Test
238+ fun `does not register on API lower than 35` () {
239+ whenever(buildInfoProvider.sdkInfoVersion).thenReturn(34 )
240+ val integration = ApplicationStartInfoIntegration (context, buildInfoProvider)
241+
242+ integration.register(scopes, options)
243+
244+ verify(activityManager, never()).addApplicationStartInfoCompletionListener(any(), any())
293245 }
294246
295247 // Helper methods
296248 private fun createMockApplicationStartInfo (
297249 forkTime : Long = 1000000000L, // nanoseconds
298250 bindApplicationTime : Long = 0L,
299- applicationOnCreateTime : Long = 0L,
300251 firstFrameTime : Long = 0L,
301252 fullyDrawnTime : Long = 0L,
253+ reason : Int = android.app.ApplicationStartInfo .START_REASON_LAUNCHER ,
254+ startType : Int = android.app.ApplicationStartInfo .START_TYPE_COLD ,
255+ launchMode : Int = android.app.ApplicationStartInfo .LAUNCH_MODE_STANDARD ,
302256 ): android.app.ApplicationStartInfo {
303257 val startInfo = mock< android.app.ApplicationStartInfo > ()
304258
305259 val timestamps = mutableMapOf<Int , Long >()
306- if (Build .VERSION .SDK_INT >= 35 ) {
307- timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FORK ] = forkTime
308- if (bindApplicationTime > 0 ) {
309- timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_BIND_APPLICATION ] =
310- bindApplicationTime
311- }
312- if (applicationOnCreateTime > 0 ) {
313- timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_APPLICATION_ONCREATE ] =
314- applicationOnCreateTime
315- }
316- if (firstFrameTime > 0 ) {
317- timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FIRST_FRAME ] = firstFrameTime
318- }
319- if (fullyDrawnTime > 0 ) {
320- timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FULLY_DRAWN ] = fullyDrawnTime
321- }
322-
323- whenever(startInfo.reason).thenReturn(android.app.ApplicationStartInfo .START_REASON_LAUNCHER )
260+ timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FORK ] = forkTime
261+ if (bindApplicationTime > 0 ) {
262+ timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_BIND_APPLICATION ] =
263+ bindApplicationTime
264+ }
265+ if (firstFrameTime > 0 ) {
266+ timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FIRST_FRAME ] = firstFrameTime
267+ }
268+ if (fullyDrawnTime > 0 ) {
269+ timestamps[android.app.ApplicationStartInfo .START_TIMESTAMP_FULLY_DRAWN ] = fullyDrawnTime
324270 }
325271
272+ whenever(startInfo.reason).thenReturn(reason)
273+ whenever(startInfo.startType).thenReturn(startType)
274+ whenever(startInfo.launchMode).thenReturn(launchMode)
326275 whenever(startInfo.startupTimestamps).thenReturn(timestamps)
327276
328277 return startInfo
0 commit comments