diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt index 1688e45d1..0aa3846ad 100644 --- a/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt +++ b/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt @@ -10,6 +10,7 @@ import androidx.annotation.VisibleForTesting.PRIVATE import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -67,14 +68,19 @@ class StartupTypeTelemetry( @VisibleForTesting(otherwise = NONE) fun getTestCallbacks() = StartupTypeLifecycleObserver() + /** + * Record startup telemetry based on the available [startupStateProvider] and [startupPathProvider]. + * + * @param dispatcher used to control the thread on which telemetry will be recorded. Defaults to [Dispatchers.IO]. + */ @VisibleForTesting(otherwise = PRIVATE) - fun record() { + fun record(dispatcher: CoroutineDispatcher = Dispatchers.IO) { val startupState = startupStateProvider.getStartupStateForStartedActivity(activityClass) val startupPath = startupPathProvider.startupPathForActivity val label = getTelemetryLabel(startupState, startupPath) @OptIn(DelicateCoroutinesApi::class) - GlobalScope.launch(Dispatchers.IO) { + GlobalScope.launch(dispatcher) { PerfStartup.startupType[label].add(1) logger.info("Recorded start up: $label") } diff --git a/app/src/test/java/org/mozilla/fenix/perf/StartupTypeTelemetryTest.kt b/app/src/test/java/org/mozilla/fenix/perf/StartupTypeTelemetryTest.kt index 8671e3eba..1eef69a87 100644 --- a/app/src/test/java/org/mozilla/fenix/perf/StartupTypeTelemetryTest.kt +++ b/app/src/test/java/org/mozilla/fenix/perf/StartupTypeTelemetryTest.kt @@ -12,8 +12,11 @@ import io.mockk.impl.annotations.MockK import io.mockk.mockk import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.test.advanceUntilIdle import mozilla.components.support.ktx.kotlin.crossProduct import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain import mozilla.telemetry.glean.testing.GleanTestRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -38,6 +41,9 @@ private val activityClass = HomeActivity::class.java @RunWith(AndroidJUnit4::class) class StartupTypeTelemetryTest { + @get:Rule + val coroutinesTestRule = MainCoroutineRule() + @get:Rule val gleanTestRule = GleanTestRule(testContext) @@ -62,7 +68,7 @@ class StartupTypeTelemetryTest { } @Test - fun `GIVEN all possible path and state combinations WHEN record telemetry THEN the labels are incremented the appropriate number of times`() { + fun `GIVEN all possible path and state combinations WHEN record telemetry THEN the labels are incremented the appropriate number of times`() = runTestOnMain { val allPossibleInputArgs = StartupState.values().toList().crossProduct( StartupPath.values().toList() ) { state, path -> @@ -73,7 +79,8 @@ class StartupTypeTelemetryTest { every { stateProvider.getStartupStateForStartedActivity(activityClass) } returns state every { pathProvider.startupPathForActivity } returns path - telemetry.record() + telemetry.record(coroutinesTestRule.testDispatcher) + advanceUntilIdle() } validTelemetryLabels.forEach { label -> @@ -87,11 +94,12 @@ class StartupTypeTelemetryTest { } @Test - fun `WHEN record is called THEN telemetry is recorded with the appropriate label`() { + fun `WHEN record is called THEN telemetry is recorded with the appropriate label`() = runTestOnMain { every { stateProvider.getStartupStateForStartedActivity(activityClass) } returns StartupState.COLD every { pathProvider.startupPathForActivity } returns StartupPath.MAIN - telemetry.record() + telemetry.record(coroutinesTestRule.testDispatcher) + advanceUntilIdle() assertEquals(1, PerfStartup.startupType["cold_main"].testGetValue()) } @@ -99,33 +107,33 @@ class StartupTypeTelemetryTest { @Test fun `GIVEN the activity is launched WHEN onResume is called THEN we record the telemetry`() { launchApp() - verify(exactly = 1) { telemetry.record() } + verify(exactly = 1) { telemetry.record(any()) } } @Test fun `GIVEN the activity is launched WHEN the activity is paused and resumed THEN record is not called`() { // This part of the test duplicates another test but it's needed to initialize the state of this test. launchApp() - verify(exactly = 1) { telemetry.record() } + verify(exactly = 1) { telemetry.record(any()) } callbacks.onPause(mockk()) callbacks.onResume(mockk()) - verify(exactly = 1) { telemetry.record() } // i.e. this shouldn't be called again. + verify(exactly = 1) { telemetry.record(any()) } // i.e. this shouldn't be called again. } @Test fun `GIVEN the activity is launched WHEN the activity is stopped and resumed THEN record is called again`() { // This part of the test duplicates another test but it's needed to initialize the state of this test. launchApp() - verify(exactly = 1) { telemetry.record() } + verify(exactly = 1) { telemetry.record(any()) } callbacks.onPause(mockk()) callbacks.onStop(mockk()) callbacks.onStart(mockk()) callbacks.onResume(mockk()) - verify(exactly = 2) { telemetry.record() } // i.e. this should be called again. + verify(exactly = 2) { telemetry.record(any()) } // i.e. this should be called again. } private fun launchApp() {