/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.fenix.perf import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.perf.AppStartReasonProvider.StartReason import org.mozilla.fenix.perf.StartupActivityLog.LogEntry class StartupStateProviderTest { private lateinit var provider: StartupStateProvider @MockK private lateinit var startupActivityLog: StartupActivityLog @MockK private lateinit var startReasonProvider: AppStartReasonProvider private lateinit var logEntries: MutableList private val homeActivityClass = HomeActivity::class.java private val irActivityClass = IntentReceiverActivity::class.java @Before fun setUp() { MockKAnnotations.init(this) provider = StartupStateProvider(startupActivityLog, startReasonProvider) logEntries = mutableListOf() every { startupActivityLog.log } returns logEntries every { startReasonProvider.reason } returns StartReason.ACTIVITY // default to minimize repetition. } @Test fun `GIVEN the app started for an activity WHEN is cold start THEN cold start is true`() { forEachColdStartEntries { index -> assertTrue("$index", provider.isColdStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN warm start THEN cold start is false`() { forEachWarmStartEntries { index -> assertFalse("$index", provider.isColdStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN hot start THEN cold start is false` () { forEachHotStartEntries { index -> assertFalse("$index", provider.isColdStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is cold start THEN warm start is false`() { forEachColdStartEntries { index -> assertFalse("$index", provider.isWarmStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is warm start THEN warm start is true`() { forEachWarmStartEntries { index -> assertTrue("$index", provider.isWarmStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is hot start THEN warm start is false` () { forEachHotStartEntries { index -> assertFalse("$index", provider.isWarmStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is cold start THEN hot start is false`() { forEachColdStartEntries { index -> assertFalse("$index", provider.isHotStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is warm start THEN hot start is false`() { forEachWarmStartEntries { index -> assertFalse("$index", provider.isHotStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN is hot start THEN hot start is true` () { forEachHotStartEntries { index -> assertTrue("$index", provider.isHotStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not cold`() { // These entries mimic observed behavior for local code changes. logEntries.addAll(listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.AppStarted, LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.ActivityStopped(irActivityClass) )) assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not warm`() { // These entries mimic observed behavior for local code changes. logEntries.addAll(listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.AppStarted, LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.ActivityStopped(irActivityClass) )) assertFalse(provider.isWarmStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not hot`() { // These entries mimic observed behavior for local code changes. logEntries.addAll(listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.AppStarted, LogEntry.ActivityStarted(homeActivityClass), LogEntry.ActivityStopped(irActivityClass) )) assertFalse(provider.isHotStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app started for an activity WHEN two HomeActivities are created THEN start up is not cold`() { // We're making an assumption about how this would work based on previous observed patterns. // AIUI, we should never have more than one HomeActivity. logEntries.addAll(listOf( LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted, LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.ActivityStopped(homeActivityClass) )) assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app started for an activity WHEN an activity hasn't been created yet THEN start up is not cold`() { assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app started for an activity WHEN an activity hasn't started yet THEN start up is not cold`() { logEntries.addAll(listOf( LogEntry.ActivityCreated(homeActivityClass) )) assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app did not start for an activity WHEN is cold is checked THEN it returns false`() { every { startReasonProvider.reason } returns StartReason.NON_ACTIVITY assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) forEachColdStartEntries { index -> assertFalse("$index", provider.isColdStartForStartedActivity(homeActivityClass)) } } @Test fun `GIVEN the app has been stopped WHEN is cold short circuit is called THEN it returns true`() { logEntries.add(LogEntry.AppStopped) assertTrue(provider.shouldShortCircuitColdStart()) } @Test fun `GIVEN the app has not been stopped WHEN is cold short circuit is called THEN it returns false`() { assertFalse(provider.shouldShortCircuitColdStart()) } @Test fun `GIVEN the app has not been stopped WHEN an activity has not been created THEN it's not a warm start`() { assertFalse(provider.isWarmStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app has been stopped WHEN an activity has not been created THEN it's not a warm start`() { logEntries.add(LogEntry.AppStopped) assertFalse(provider.isWarmStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app has been stopped WHEN an activity has not been started THEN it's not a warm start`() { logEntries.addAll(listOf( LogEntry.AppStopped, LogEntry.ActivityCreated(homeActivityClass) )) assertFalse(provider.isWarmStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app has not been stopped WHEN an activity has not been created THEN it's not a hot start`() { assertFalse(provider.isHotStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app has been stopped WHEN an activity has not been created THEN it's not a hot start`() { logEntries.add(LogEntry.AppStopped) assertFalse(provider.isHotStartForStartedActivity(homeActivityClass)) } @Test fun `GIVEN the app has been stopped WHEN an activity has not been started THEN it's not a hot start`() { logEntries.addAll(listOf( LogEntry.AppStopped, LogEntry.ActivityCreated(homeActivityClass) )) assertFalse(provider.isHotStartForStartedActivity(homeActivityClass)) } private fun forEachColdStartEntries(block: (index: Int) -> Unit) { // These entries mimic observed behavior. // // MAIN: open HomeActivity directly. val coldStartEntries = listOf(listOf( LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // VIEW: open non-drawing IntentReceiverActivity, then HomeActivity. ), listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted )) forEachStartEntry(coldStartEntries, block) } private fun forEachWarmStartEntries(block: (index: Int) -> Unit) { // These entries mimic observed behavior. We test both truncated (i.e. the current behavior // with the optimization to prevent an infinite log) and untruncated (the behavior without // such an optimization). // // truncated MAIN: open HomeActivity directly. val warmStartEntries = listOf(listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // untruncated MAIN: open HomeActivity directly. ), listOf( LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted, LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // truncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity. ), listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // untruncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity. ), listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted, LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted )) forEachStartEntry(warmStartEntries, block) } private fun forEachHotStartEntries(block: (index: Int) -> Unit) { // These entries mimic observed behavior. We test both truncated (i.e. the current behavior // with the optimization to prevent an infinite log) and untruncated (the behavior without // such an optimization). // // truncated MAIN: open HomeActivity directly. val hotStartEntries = listOf(listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // untruncated MAIN: open HomeActivity directly. ), listOf( LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted, LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // truncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity. ), listOf( LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted // untruncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity. ), listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted, LogEntry.AppStopped, LogEntry.ActivityStopped(homeActivityClass), LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted )) forEachStartEntry(hotStartEntries, block) } private fun forEachStartEntry(entries: List>, block: (index: Int) -> Unit) { entries.forEachIndexed { index, startEntry -> logEntries.clear() logEntries.addAll(startEntry) block(index) } } }