Revision control
Copy as Markdown
/* 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
package org.mozilla.focus.telemetry
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mozilla.focus.activity.IntentReceiverActivity
import org.mozilla.focus.activity.MainActivity
import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider
import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider.StartReason
import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog
import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog.LogEntry
import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider
import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider.StartupState
class StartupStateProviderTest {
private lateinit var provider: StartupStateProvider
private var startupActivityLog: StartupActivityLog = mock()
private var startReasonProvider: AppStartReasonProvider = mock()
private lateinit var logEntries: MutableList<LogEntry>
private val mainActivityClass = MainActivity::class.java
private val irActivityClass = IntentReceiverActivity::class.java
@Before
fun setUp() {
provider = StartupStateProvider(startupActivityLog, startReasonProvider)
logEntries = mutableListOf()
Mockito.doReturn(logEntries).`when`(startupActivityLog).log
Mockito.doReturn(logEntries).`when`(startupActivityLog).log
Mockito.doReturn(StartReason.ACTIVITY).`when`(startReasonProvider).reason
}
@Test
fun `GIVEN the app started for an activity WHEN is cold start THEN cold start is true`() {
forEachColdStartEntries { index ->
assertTrue("$index", provider.isColdStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN warm start THEN cold start is false`() {
forEachWarmStartEntries { index ->
assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN hot start THEN cold start is false`() {
forEachHotStartEntries { index ->
assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is cold start THEN warm start is false`() {
forEachColdStartEntries { index ->
assertFalse("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is warm start THEN warm start is true`() {
forEachWarmStartEntries { index ->
assertTrue("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is hot start THEN warm start is false`() {
forEachHotStartEntries { index ->
assertFalse("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is cold start THEN hot start is false`() {
forEachColdStartEntries { index ->
assertFalse("$index", provider.isHotStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is warm start THEN hot start is false`() {
forEachWarmStartEntries { index ->
assertFalse("$index", provider.isHotStartForStartedActivity(mainActivityClass))
}
}
@Test
fun `GIVEN the app started for an activity WHEN is hot start THEN hot start is true`() {
forEachHotStartEntries { index ->
assertTrue("$index", provider.isHotStartForStartedActivity(mainActivityClass))
}
}
@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(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.ActivityStopped(irActivityClass),
),
)
assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
}
@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(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityStarted(irActivityClass),
LogEntry.AppStarted,
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.ActivityStopped(irActivityClass),
),
)
assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
}
@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(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityStarted(irActivityClass),
LogEntry.AppStarted,
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.ActivityStopped(irActivityClass),
),
)
assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
}
@Test
fun `GIVEN the app started for an activity WHEN two MainActivities 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 MainActivity.
logEntries.addAll(
listOf(
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.ActivityStopped(mainActivityClass),
),
)
assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
}
@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(mainActivityClass))
}
@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(mainActivityClass),
),
)
assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
}
@Test
fun `GIVEN the app did not start for an activity WHEN is cold is checked THEN it returns false`() {
Mockito.doReturn(StartReason.NON_ACTIVITY).`when`(startReasonProvider).reason
assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
forEachColdStartEntries { index ->
assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
}
}
@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(mainActivityClass))
}
@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(mainActivityClass))
}
@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(mainActivityClass),
),
)
assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
}
@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(mainActivityClass))
}
@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(mainActivityClass))
}
@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(mainActivityClass),
),
)
assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
}
@Test
fun `GIVEN the app started for an activity WHEN it is a cold start THEN get startup state is cold`() {
forEachColdStartEntries { index ->
assertEquals("$index", StartupState.COLD, provider.getStartupStateForStartedActivity(mainActivityClass))
}
}
@Test
fun `WHEN it is a warm start THEN get startup state is warm`() {
forEachWarmStartEntries { index ->
assertEquals("$index", StartupState.WARM, provider.getStartupStateForStartedActivity(mainActivityClass))
}
}
@Test
fun `WHEN it is a hot start THEN get startup state is hot`() {
forEachHotStartEntries { index ->
assertEquals("$index", StartupState.HOT, provider.getStartupStateForStartedActivity(mainActivityClass))
}
}
@Test
fun `WHEN two activities are started THEN get startup state is unknown`() {
logEntries.addAll(
listOf(
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityStarted(irActivityClass),
LogEntry.AppStarted,
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.ActivityStopped(irActivityClass),
),
)
assertEquals(StartupState.UNKNOWN, provider.getStartupStateForStartedActivity(mainActivityClass))
}
private fun forEachColdStartEntries(block: (index: Int) -> Unit) {
// These entries mimic observed behavior.
//
// MAIN: open HomeActivity directly.
val coldStartEntries = listOf(
listOf(
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
),
listOf(
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
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(mainActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// untruncated MAIN: open MainActivity directly.
),
listOf(
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// truncated VIEW: open non-drawing IntentReceiverActivity, then MainActivity.
),
listOf(
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// untruncated VIEW: open non-drawing IntentReceiverActivity, then MainActivity.
),
listOf(
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
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(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// untruncated MAIN: open HomeActivity directly.
),
listOf(
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// truncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
),
listOf(
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
// untruncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
),
listOf(
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityCreated(mainActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
LogEntry.AppStopped,
LogEntry.ActivityStopped(mainActivityClass),
LogEntry.ActivityCreated(irActivityClass),
LogEntry.ActivityStarted(mainActivityClass),
LogEntry.AppStarted,
),
)
forEachStartEntry(hotStartEntries, block)
}
private fun forEachStartEntry(entries: List<List<LogEntry>>, block: (index: Int) -> Unit) {
entries.forEachIndexed { index, startEntry ->
logEntries.clear()
logEntries.addAll(startEntry)
block(index)
}
}
}