/* 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 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 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 multiple activities are started but not stopped (maybe impossible) THEN start up is not cold`() { fun assertIsNotCold() { assertFalse(provider.isColdStartForStartedActivity(homeActivityClass)) } // Since we've never observed this, there are multiple ways the events could // theoretically be ordered: we try a few. logEntries.addAll(listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.AppStarted, LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass) )) assertIsNotCold() logEntries.clear() logEntries.addAll(listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted )) assertIsNotCold() logEntries.clear() logEntries.addAll(listOf( LogEntry.ActivityCreated(irActivityClass), LogEntry.ActivityCreated(homeActivityClass), LogEntry.ActivityStarted(irActivityClass), LogEntry.ActivityStarted(homeActivityClass), LogEntry.AppStarted )) assertIsNotCold() } @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()) } 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 )) // TODO: add VIEW. 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 )) // TODO: add VIEW. forEachStartEntry(hotStartEntries, block) } private fun forEachStartEntry(entries: List>, block: (index: Int) -> Unit) { entries.forEachIndexed { index, startEntry -> logEntries.clear() logEntries.addAll(startEntry) block(index) } } }