You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/test/java/org/mozilla/fenix/perf/StartupFrameworkStartMeasur...

177 lines
7.3 KiB
Kotlin

/* 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.Called
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.service.glean.private.BooleanMetricType
import mozilla.components.service.glean.private.QuantityMetricType
import mozilla.components.service.glean.private.TimespanMetricType
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.GleanMetrics.StartupTimeline as Telemetry
private const val PRIMARY_TICKS = 100L
private const val SECONDARY_TICKS = 50L
class StartupFrameworkStartMeasurementTest {
private lateinit var metrics: StartupFrameworkStartMeasurement
private lateinit var stat: Stat
// We'd prefer to use the Glean test methods over these mocks but they require us to add
// Robolectric and it's not worth the impact on test duration.
@MockK private lateinit var telemetry: Telemetry
@MockK(relaxed = true) private lateinit var frameworkPrimary: TimespanMetricType
@MockK(relaxed = true) private lateinit var frameworkSecondary: TimespanMetricType
@MockK(relaxed = true) private lateinit var frameworkStartError: BooleanMetricType
@MockK(relaxed = true) private lateinit var clockTicksPerSecondV2: QuantityMetricType
private var clockTicksPerSecondValue = -1L
private var elapsedRealtimeNanos = -1L
private var processStartTimeTicks = -1L
@Before
fun setUp() {
MockKAnnotations.init(this)
elapsedRealtimeNanos = -1
processStartTimeTicks = -1
// This value is hard-coded in the OS so we default to it as it's the expected value.
clockTicksPerSecondValue = PRIMARY_TICKS
stat = spyk(object : Stat() {
override val clockTicksPerSecond: Long get() = clockTicksPerSecondValue
})
every { stat.getProcessStartTimeTicks(any()) } answers { processStartTimeTicks }
val getElapsedRealtimeNanos = { elapsedRealtimeNanos }
every { telemetry.frameworkPrimary } returns frameworkPrimary
every { telemetry.frameworkSecondary } returns frameworkSecondary
every { telemetry.frameworkStartError } returns frameworkStartError
every { telemetry.clockTicksPerSecondV2 } returns clockTicksPerSecondV2
metrics = StartupFrameworkStartMeasurement(stat, telemetry, getElapsedRealtimeNanos)
}
@Test
fun `GIVEN app init is invalid WHEN metrics are set THEN frameworkStartError is set to true`() {
setProcessAppInitAndMetrics(processStart = 10, appInit = -1)
verifyFrameworkStartError()
}
@Test
fun `GIVEN app init is not called WHEN metrics are set THEN frameworkStartError is set to true`() {
metrics.setExpensiveMetric()
verifyFrameworkStartError()
}
@Test
fun `GIVEN app init is set to valid values and clock ticks per second is 100 WHEN metrics are set THEN frameworkPrimary is set with the correct value`() {
clockTicksPerSecondValue = PRIMARY_TICKS
setProcessAppInitAndMetrics(processStart = 166_636_813, appInit = 1_845_312_345_673_925)
verifyFrameworkStartSuccess(178_944_220_000_000, isPrimary = true) // calculated by hand.
}
@Test
fun `GIVEN app init is set to valid values and clock ticks per second is not 100 WHEN metrics are set THEN frameworkSecondary is set with the correct value`() {
clockTicksPerSecondValue = SECONDARY_TICKS
setProcessAppInitAndMetrics(processStart = 166_636_813, appInit = 1_845_312_345_673_925)
verifyFrameworkStartSuccess(178_944_220_000_000, isPrimary = false) // calculated by hand.
}
@Test // this overlaps with the success case test.
fun `GIVEN app init has valid values and clock ticks per second is 100 WHEN onAppInit is called twice and metrics are set THEN frameworkPrimary uses the first app init value`() {
clockTicksPerSecondValue = PRIMARY_TICKS
testAppInitCalledTwice(isPrimary = true)
}
@Test // this overlaps with the success case test.
fun `GIVEN app init has valid values and clock ticks per second is not 100 WHEN onAppInit is called twice and metrics are set THEN frameworkSecondary uses the first app init value`() {
clockTicksPerSecondValue = SECONDARY_TICKS
testAppInitCalledTwice(isPrimary = false)
}
private fun testAppInitCalledTwice(isPrimary: Boolean) {
processStartTimeTicks = 166_636_813
elapsedRealtimeNanos = 1_845_312_345_673_925
metrics.onApplicationInit()
elapsedRealtimeNanos = 1_945_312_345_673_925
metrics.onApplicationInit()
metrics.setExpensiveMetric()
verifyFrameworkStartSuccess(178_944_220_000_000, isPrimary) // calculated by hand.
}
@Test
fun `GIVEN app init have valid values and clock ticks per second is 100 WHEN metrics are set twice THEN frameworkPrimary is only set once`() {
clockTicksPerSecondValue = PRIMARY_TICKS
testMetricsSetTwice(isPrimary = true)
}
@Test
fun `GIVEN app init have valid values and clock ticks per second is not 100 WHEN metrics are set twice THEN frameworkSecondary is only set once`() {
clockTicksPerSecondValue = SECONDARY_TICKS
testMetricsSetTwice(isPrimary = false)
}
private fun testMetricsSetTwice(isPrimary: Boolean) {
setProcessAppInitAndMetrics(10, 100)
metrics.setExpensiveMetric()
val (setMetric, unsetMetric) = getSetAndUnsetMetric(isPrimary)
verify(exactly = 1) { setMetric.setRawNanos(any()) }
verify { unsetMetric wasNot Called }
verify(exactly = 1) { clockTicksPerSecondV2.set(any()) }
verify { frameworkStartError wasNot Called }
}
private fun setProcessAppInitAndMetrics(processStart: Long, appInit: Long) {
processStartTimeTicks = processStart
elapsedRealtimeNanos = appInit
metrics.onApplicationInit()
metrics.setExpensiveMetric()
}
private fun verifyFrameworkStartSuccess(nanos: Long, isPrimary: Boolean) {
val (setMetric, unsetMetric) = getSetAndUnsetMetric(isPrimary)
verify { setMetric.setRawNanos(nanos) }
verify { unsetMetric wasNot Called }
val expectedClockTicksPerSecond = getExpectedClockTicksPerSecond(isPrimary)
verify { clockTicksPerSecondV2.set(expectedClockTicksPerSecond) }
verify { frameworkStartError wasNot Called }
}
private fun verifyFrameworkStartError() {
verify { frameworkStartError.set(true) }
verify { frameworkPrimary wasNot Called }
verify { frameworkSecondary wasNot Called }
verify { clockTicksPerSecondV2 wasNot Called }
}
private fun getSetAndUnsetMetric(isPrimary: Boolean): Pair<TimespanMetricType, TimespanMetricType> {
return if (isPrimary) {
Pair(frameworkPrimary, frameworkSecondary)
} else {
Pair(frameworkSecondary, frameworkPrimary)
}
}
// This hard-codes some data that's passed into the test but I don't want to spend more time
// so I don't bother cleaning it up now.
private fun getExpectedClockTicksPerSecond(isPrimary: Boolean): Long =
if (isPrimary) PRIMARY_TICKS else SECONDARY_TICKS
}