diff --git a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt index e330008a7a..4db433909d 100644 --- a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt +++ b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt @@ -8,6 +8,7 @@ package com.datadog.android.rum.integration import com.datadog.android.api.feature.Feature import com.datadog.android.core.stub.StubSDKCore +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.Rum import com.datadog.android.rum.RumActionType @@ -23,6 +24,7 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -30,6 +32,7 @@ import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.extension.ExtendWith @@ -38,6 +41,7 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.quality.Strictness import java.net.URL +import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -395,6 +399,217 @@ class ManualTrackingRumTest { // endregion + // region AddViewLoading time + + @OptIn(ExperimentalRumApi::class) + @RepeatedTest(16) + fun `M attach view loading time W addViewLoadingTime { active view available }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + + // When + val endTime = System.nanoTime() + val expectedViewLoadingTime = endTime - startTime + rumMonitor.addViewLoadingTime(overwrite) + + // Then + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + } + .hasRumEvent(index = 1) { + // view updated with loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + @OptIn(ExperimentalRumApi::class) + @RepeatedTest(16) + fun `M not attach view loading time W addViewLoadingTime { view was stopped }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + rumMonitor.startView(key, name, emptyMap()) + rumMonitor.stopView(key, emptyMap()) + + // When + rumMonitor.addViewLoadingTime(overwrite) + + // Then + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + } + .hasRumEvent(index = 1) { + // view stopped + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + doesNotHaveViewLoadingTime() + } + } + + @OptIn(ExperimentalRumApi::class) + @RepeatedTest(16) + fun `M renew view loading time W addViewLoadingTime { loading time was already added, overwrite is true }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + val intermediateTime = System.nanoTime() + rumMonitor.addViewLoadingTime(overwrite) + + // When + Thread.sleep(100) + val endTime = System.nanoTime() + rumMonitor.addViewLoadingTime(true) + + // Then + val expectedFirstViewLoadingTime = intermediateTime - startTime + val expectedSecondViewLoadingTime = endTime - startTime + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(3) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + } + .hasRumEvent(index = 1) { + // first view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedFirstViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + .hasRumEvent(index = 2) { + // second view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedSecondViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + @OptIn(ExperimentalRumApi::class) + @RepeatedTest(16) + fun `M not renew view loading time W addViewLoadingTime { loading time was already added, overwrite is false }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + val intermediateTime = System.nanoTime() + rumMonitor.addViewLoadingTime(overwrite) + + // When + Thread.sleep(100) + rumMonitor.addViewLoadingTime(false) + + // Then + val expectedViewLoadingTime = intermediateTime - startTime + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + } + .hasRumEvent(index = 1) { + // first view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + // endregion + companion object { private val mainLooper = MainLooperTestConfiguration() diff --git a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt index c4b72fe574..07594317af 100644 --- a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt +++ b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt @@ -8,6 +8,7 @@ package com.datadog.android.rum.integration.tests.assertj import com.datadog.tools.unit.assertj.JsonObjectAssert import com.google.gson.JsonObject +import org.assertj.core.data.Offset class RumEventAssert(actual: JsonObject) : JsonObjectAssert(actual, true) { @@ -123,6 +124,20 @@ class RumEventAssert(actual: JsonObject) : // endregion + // region view loading time + + fun hasViewLoadingTime(time: Long, offset: Offset): RumEventAssert { + hasField("view.loading_time", time, offset = offset) + return this + } + + fun doesNotHaveViewLoadingTime(): RumEventAssert { + doesNotHaveField("view.loading_time") + return this + } + + // endregion + companion object { fun assertThat(actual: JsonObject): RumEventAssert { return RumEventAssert(actual)