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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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)
}
}
}