/ * 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
}