11package io.sentry.android.core
22
3+ import android.app.Application
4+ import android.content.ContentProvider
35import android.content.Context
46import androidx.test.core.app.ApplicationProvider
57import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -17,6 +19,8 @@ import io.sentry.SentryExceptionFactory
1719import io.sentry.SentryItemType
1820import io.sentry.SentryOptions
1921import io.sentry.Session
22+ import io.sentry.android.core.performance.ActivityLifecycleTimeSpan
23+ import io.sentry.android.core.performance.AppStartMetrics
2024import io.sentry.exception.ExceptionMechanismException
2125import io.sentry.protocol.App
2226import io.sentry.protocol.Contexts
@@ -101,6 +105,81 @@ class InternalSentrySdkTest {
101105
102106 InternalSentrySdk .captureEnvelope(data)
103107 }
108+
109+ fun mockFinishedAppStart () {
110+ val metrics = AppStartMetrics .getInstance()
111+
112+ metrics.appStartType = AppStartMetrics .AppStartType .WARM
113+
114+ metrics.appStartTimeSpan.setStartedAt(20 ) // Can't be 0, as that's the default value if not set
115+ metrics.appStartTimeSpan.setStartUnixTimeMs(20 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
116+ metrics.appStartTimeSpan.setStoppedAt(200 )
117+ metrics.classLoadedUptimeMs = 100
118+
119+ AppStartMetrics .onApplicationCreate(mock<Application >())
120+ metrics.applicationOnCreateTimeSpan.description = " Application created"
121+ metrics.applicationOnCreateTimeSpan.setStartedAt(30 ) // Can't be 0, as that's the default value if not set
122+ metrics.applicationOnCreateTimeSpan.setStartUnixTimeMs(30 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
123+ metrics.applicationOnCreateTimeSpan.setStoppedAt(40 )
124+
125+ val activityLifecycleSpan = ActivityLifecycleTimeSpan ()
126+ activityLifecycleSpan.onCreate.description = " Test Activity Lifecycle onCreate"
127+ activityLifecycleSpan.onCreate.setStartedAt(50 ) // Can't be 0, as that's the default value if not set
128+ activityLifecycleSpan.onCreate.setStartUnixTimeMs(50 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
129+ activityLifecycleSpan.onCreate.setStoppedAt(60 )
130+
131+ activityLifecycleSpan.onStart.description = " Test Activity Lifecycle onStart"
132+ activityLifecycleSpan.onStart.setStartedAt(70 ) // Can't be 0, as that's the default value if not set
133+ activityLifecycleSpan.onStart.setStartUnixTimeMs(70 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
134+ activityLifecycleSpan.onStart.setStoppedAt(80 )
135+ metrics.addActivityLifecycleTimeSpans(activityLifecycleSpan)
136+
137+ AppStartMetrics .onContentProviderCreate(mock<ContentProvider >())
138+ metrics.contentProviderOnCreateTimeSpans[0 ].description = " Test Content Provider created"
139+ metrics.contentProviderOnCreateTimeSpans[0 ].setStartedAt(90 )
140+ metrics.contentProviderOnCreateTimeSpans[0 ].setStartUnixTimeMs(90 )
141+ metrics.contentProviderOnCreateTimeSpans[0 ].setStoppedAt(100 )
142+
143+ metrics.appStartProfiler = mock()
144+ metrics.appStartSamplingDecision = mock()
145+ }
146+
147+ fun mockMinimumFinishedAppStart () {
148+ val metrics = AppStartMetrics .getInstance()
149+
150+ metrics.appStartType = AppStartMetrics .AppStartType .WARM
151+
152+ metrics.appStartTimeSpan.setStartedAt(20 ) // Can't be 0, as that's the default value if not set
153+ metrics.appStartTimeSpan.setStartUnixTimeMs(20 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
154+ metrics.appStartTimeSpan.setStoppedAt(200 )
155+ metrics.classLoadedUptimeMs = 100
156+
157+ AppStartMetrics .onApplicationCreate(mock<Application >())
158+ metrics.applicationOnCreateTimeSpan.description = " Application created"
159+ metrics.applicationOnCreateTimeSpan.setStartedAt(30 ) // Can't be 0, as that's the default value if not set
160+ metrics.applicationOnCreateTimeSpan.setStartUnixTimeMs(30 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
161+ metrics.applicationOnCreateTimeSpan.setStoppedAt(40 )
162+ }
163+
164+ fun mockUnfinishedAppStart () {
165+ val metrics = AppStartMetrics .getInstance()
166+
167+ metrics.appStartType = AppStartMetrics .AppStartType .WARM
168+
169+ metrics.appStartTimeSpan.setStartedAt(20 ) // Can't be 0, as that's the default value if not set
170+ metrics.appStartTimeSpan.setStartUnixTimeMs(20 ) // The order matters, unix time must be set after started at in tests to avoid overwrite
171+ metrics.appStartTimeSpan.setStoppedAt(200 )
172+ metrics.classLoadedUptimeMs = 100
173+
174+ AppStartMetrics .onApplicationCreate(mock<Application >())
175+ metrics.applicationOnCreateTimeSpan.description = " Application created"
176+ metrics.applicationOnCreateTimeSpan.setStartedAt(30 ) // Can't be 0, as that's the default value if not set
177+
178+ val activityLifecycleSpan = ActivityLifecycleTimeSpan () // Expect the created spans are not started nor stopped
179+ activityLifecycleSpan.onCreate.description = " Test Activity Lifecycle onCreate"
180+ activityLifecycleSpan.onStart.description = " Test Activity Lifecycle onStart"
181+ metrics.addActivityLifecycleTimeSpans(activityLifecycleSpan)
182+ }
104183 }
105184
106185 @BeforeTest
@@ -302,4 +381,83 @@ class InternalSentrySdkTest {
302381 }
303382 assertEquals(Session .State .Crashed , scopeRef.get().session!! .status)
304383 }
384+
385+ @Test
386+ fun `getAppStartMeasurement returns correct serialized data from the app start instance` () {
387+ Fixture ().mockFinishedAppStart()
388+
389+ val serializedAppStart = InternalSentrySdk .getAppStartMeasurement()
390+
391+ assertEquals(" warm" , serializedAppStart[" type" ])
392+ assertEquals(20 .toLong(), serializedAppStart[" app_start_timestamp_ms" ])
393+
394+ val actualSpans = serializedAppStart[" spans" ] as List <* >
395+ assertEquals(5 , actualSpans.size)
396+
397+ val actualProcessSpan = actualSpans[0 ] as Map <* , * >
398+ assertEquals(" Process Initialization" , actualProcessSpan[" description" ])
399+ assertEquals(20 .toLong(), actualProcessSpan[" start_timestamp_ms" ])
400+ assertEquals(100 .toLong(), actualProcessSpan[" end_timestamp_ms" ])
401+
402+ val actualAppSpan = actualSpans[1 ] as Map <* , * >
403+ assertEquals(" Application created" , actualAppSpan[" description" ])
404+ assertEquals(30 .toLong(), actualAppSpan[" start_timestamp_ms" ])
405+ assertEquals(40 .toLong(), actualAppSpan[" end_timestamp_ms" ])
406+
407+ val actualContentProviderSpan = actualSpans[2 ] as Map <* , * >
408+ assertEquals(" Test Content Provider created" , actualContentProviderSpan[" description" ])
409+ assertEquals(90 .toLong(), actualContentProviderSpan[" start_timestamp_ms" ])
410+ assertEquals(100 .toLong(), actualContentProviderSpan[" end_timestamp_ms" ])
411+
412+ val actualActivityOnCreateSpan = actualSpans[3 ] as Map <* , * >
413+ assertEquals(" Test Activity Lifecycle onCreate" , actualActivityOnCreateSpan[" description" ])
414+ assertEquals(50 .toLong(), actualActivityOnCreateSpan[" start_timestamp_ms" ])
415+ assertEquals(60 .toLong(), actualActivityOnCreateSpan[" end_timestamp_ms" ])
416+
417+ val actualActivityOnStartSpan = actualSpans[4 ] as Map <* , * >
418+ assertEquals(" Test Activity Lifecycle onStart" , actualActivityOnStartSpan[" description" ])
419+ assertEquals(70 .toLong(), actualActivityOnStartSpan[" start_timestamp_ms" ])
420+ assertEquals(80 .toLong(), actualActivityOnStartSpan[" end_timestamp_ms" ])
421+ }
422+
423+ @Test
424+ fun `getAppStartMeasurement returns correct serialized data from the minimum app start instance` () {
425+ Fixture ().mockMinimumFinishedAppStart()
426+
427+ val serializedAppStart = InternalSentrySdk .getAppStartMeasurement()
428+
429+ assertEquals(" warm" , serializedAppStart[" type" ])
430+ assertEquals(20 .toLong(), serializedAppStart[" app_start_timestamp_ms" ])
431+
432+ val actualSpans = serializedAppStart[" spans" ] as List <* >
433+ assertEquals(2 , actualSpans.size)
434+
435+ val actualProcessSpan = actualSpans[0 ] as Map <* , * >
436+ assertEquals(" Process Initialization" , actualProcessSpan[" description" ])
437+ assertEquals(20 .toLong(), actualProcessSpan[" start_timestamp_ms" ])
438+ assertEquals(100 .toLong(), actualProcessSpan[" end_timestamp_ms" ])
439+
440+ val actualAppSpan = actualSpans[1 ] as Map <* , * >
441+ assertEquals(" Application created" , actualAppSpan[" description" ])
442+ assertEquals(30 .toLong(), actualAppSpan[" start_timestamp_ms" ])
443+ assertEquals(40 .toLong(), actualAppSpan[" end_timestamp_ms" ])
444+ }
445+
446+ @Test
447+ fun `getAppStartMeasurement returns only stopped spans in serialized data` () {
448+ Fixture ().mockUnfinishedAppStart()
449+
450+ val serializedAppStart = InternalSentrySdk .getAppStartMeasurement()
451+
452+ assertEquals(" warm" , serializedAppStart[" type" ])
453+ assertEquals(20 .toLong(), serializedAppStart[" app_start_timestamp_ms" ])
454+
455+ val actualSpans = serializedAppStart[" spans" ] as List <* >
456+ assertEquals(1 , actualSpans.size)
457+
458+ val actualProcessSpan = actualSpans[0 ] as Map <* , * >
459+ assertEquals(" Process Initialization" , actualProcessSpan[" description" ])
460+ assertEquals(20 .toLong(), actualProcessSpan[" start_timestamp_ms" ])
461+ assertEquals(100 .toLong(), actualProcessSpan[" end_timestamp_ms" ])
462+ }
305463}
0 commit comments