From 39f500eb40db6e77fed6c53e23575453a5ba904d Mon Sep 17 00:00:00 2001 From: Francisco Veiga Date: Thu, 11 Dec 2025 16:36:22 +0000 Subject: [PATCH 1/5] RUM-10363: Fix conflicts --- dd-sdk-android-core/api/apiSurface | 3 +- .../api/dd-sdk-android-core.api | 3 +- .../android/api/feature/FeatureSdkCore.kt | 6 + .../android/core/internal/CoreFeature.kt | 14 +- .../android/core/internal/DatadogCore.kt | 18 +- .../core/internal/NoOpInternalSdkCore.kt | 14 +- .../android/core/internal/SdkFeature.kt | 6 +- .../data/upload/RotatingDnsResolver.kt | 17 +- .../core/internal/logger/SdkInternalLogger.kt | 4 +- .../metrics/BatchMetricsDispatcher.kt | 4 +- .../internal/metrics/MethodCalledTelemetry.kt | 9 +- .../file/advanced/ConsentAwareFileMigrator.kt | 13 +- .../file/advanced/FeatureFileOrchestrator.kt | 13 +- .../advanced/MoveDataMigrationOperation.kt | 6 +- .../advanced/WipeDataMigrationOperation.kt | 6 +- .../file/batch/BatchFileOrchestrator.kt | 18 +- .../thread/BackPressureExecutorService.kt | 6 +- .../thread/BackPressuredBlockingQueue.kt | 18 +- .../thread/ObservableLinkedBlockingQueue.kt | 24 +- .../internal/thread/ThreadPoolExecutorExt.kt | 11 +- .../time/DefaultAppStartTimeProvider.kt | 7 +- .../core/internal/time/KronosTimeProvider.kt | 7 +- .../android/core/internal/utils/MiscUtils.kt | 13 +- .../core/thread/FlushableExecutorService.kt | 5 +- .../error/internal/DatadogExceptionHandler.kt | 2 +- .../core/DatadogCoreInitializationTest.kt | 36 +-- .../datadog/android/core/DatadogCoreTest.kt | 30 +- .../android/core/internal/CoreFeatureTest.kt | 2 +- .../data/upload/RotatingDnsResolverTest.kt | 28 +- .../metrics/BatchMetricsDispatcherTest.kt | 20 +- .../metrics/MethodCalledTelemetryTest.kt | 22 +- .../advanced/ConsentAwareFileMigratorTest.kt | 8 +- .../advanced/FeatureFileOrchestratorTest.kt | 13 +- .../MoveDataMigrationOperationTest.kt | 36 ++- .../WipeDataMigrationOperationTest.kt | 29 +- .../file/batch/BatchFileOrchestratorTest.kt | 210 +++++++------- .../thread/AbstractExecutorServiceTest.kt | 10 +- .../thread/BackPressureExecutorServiceTest.kt | 7 +- ...ropOldestBackPressuredBlockingQueueTest.kt | 4 +- ...oreNewestBackPressuredBlockingQueueTest.kt | 4 +- .../LoggingScheduledThreadPoolExecutorTest.kt | 4 +- ...UnboundedBackPressuredBlockingQueueTest.kt | 4 +- ...t => ObservableLinkedBlockingQueueTest.kt} | 26 +- .../thread/ThreadPoolExecutorExtTest.kt | 57 +++- .../time/DefaultAppStartTimeProviderTest.kt | 130 +++++---- .../core/internal/utils/MiscUtilsTest.kt | 61 ++-- .../internal/DatadogExceptionHandlerTest.kt | 21 +- .../BatchClosedMetadataForgeryFactory.kt | 2 +- dd-sdk-android-internal/api/apiSurface | 3 +- .../api/dd-sdk-android-internal.api | 5 +- .../internal/profiler/DDExecutionTimer.kt | 8 +- .../internal/profiler/GlobalBenchmark.kt | 7 +- .../internal/time/DefaultTimeProvider.kt | 3 +- .../android/internal/time/TimeProvider.kt | 11 + .../internal/tests/stub/StubTimeProvider.kt | 37 +++ .../flags/internal/ExposureEventsProcessor.kt | 8 +- .../android/flags/internal/FlagsFeature.kt | 5 +- .../flags/internal/model/FlagsStateEntry.kt | 2 +- .../persistence/FlagsPersistenceManager.kt | 3 +- .../repository/DefaultFlagsRepository.kt | 1 + .../internal/ExposureEventsProcessorTest.kt | 30 +- .../flags/internal/FlagsFeatureTest.kt | 1 + .../persistence/FlagsStateSerializerTest.kt | 13 +- .../repository/DefaultFlagsRepositoryTest.kt | 8 +- .../log/internal/logger/DatadogLogHandler.kt | 4 +- .../internal/logger/DatadogLogHandlerTest.kt | 37 ++- .../com/datadog/android/ndk/NdkTests.kt | 1 - .../ndk/internal/NdkCrashReportsFeature.kt | 4 +- .../internal/NdkCrashReportsFeatureTest.kt | 3 +- .../rum/internal/DatadogLateCrashReporter.kt | 2 +- .../android/rum/internal/RumFeature.kt | 10 +- .../DefaultAccessibilityReader.kt | 4 +- .../internal/domain/scope/RumSessionScope.kt | 4 +- .../MainLooperLongTaskStrategy.kt | 7 +- .../metric/slowframes/SlowFramesListener.kt | 12 +- .../startup/RumFirstDrawTimeReporter.kt | 2 +- .../rum/resource/RumResourceInputStream.kt | 18 +- .../kotlin/com/datadog/android/rum/RumTest.kt | 1 + .../internal/DatadogLateCrashReporterTest.kt | 136 +++++---- .../android/rum/internal/RumFeatureTest.kt | 4 +- .../DefaultAccessibilityReaderTest.kt | 24 +- .../RumActionScopeAttributePropagationTest.kt | 15 +- .../domain/scope/RumActionScopeTest.kt | 214 ++++++++++---- ...pplicationScopeAttributePropagationTest.kt | 1 + .../domain/scope/RumApplicationScopeTest.kt | 1 + .../scope/RumContinuousActionScopeTest.kt | 270 ++++++++++++------ .../internal/domain/scope/RumRawEventExt.kt | 5 +- ...umResourceScopeAttributePropagationTest.kt | 20 +- .../domain/scope/RumResourceScopeTest.kt | 172 +++++------ ...RumSessionScopeAttributePropagationTest.kt | 1 + .../domain/scope/RumSessionScopeTest.kt | 124 +++++--- .../domain/scope/RumViewManagerScopeTest.kt | 29 +- .../MainLooperLongTaskStrategyTest.kt | 37 ++- .../DefaultSlowFramesListenerTest.kt | 7 +- .../internal/monitor/DatadogRumMonitorTest.kt | 3 +- .../internal/DefaultRecorderProvider.kt | 3 +- .../async/RecordedDataQueueHandler.kt | 11 +- .../internal/async/RecordedDataQueueItem.kt | 2 +- .../async/ResourceRecordedDataQueueItem.kt | 3 +- .../async/SnapshotRecordedDataQueueItem.kt | 5 +- .../async/TouchEventRecordedDataQueueItem.kt | 3 +- .../processor/RecordedDataProcessor.kt | 6 +- .../internal/recorder/Debouncer.kt | 12 +- .../recorder/SessionReplayRecorder.kt | 6 +- .../callback/RecorderWindowCallback.kt | 12 +- .../resources/ResourceDataStoreManager.kt | 4 +- ...urceRecordedDataQueueItemForgeryFactory.kt | 3 +- ...shotRecordedDataQueueItemForgeryFactory.kt | 3 +- ...ventRecordedDataQueueItemForgeryFactory.kt | 1 + .../internal/SessionReplayFeatureTest.kt | 1 + .../async/RecordedDataQueueHandlerTest.kt | 30 +- .../ResourceRecordedDataQueueItemTest.kt | 3 +- .../SnapshotRecordedDataQueueItemTest.kt | 5 +- .../TouchEventRecordedDataQueueItemTest.kt | 1 + .../processor/RecordedDataProcessorTest.kt | 49 ++-- .../internal/recorder/DebouncerTest.kt | 42 ++- .../recorder/WindowCallbackInterceptorTest.kt | 6 +- .../callback/RecorderWindowCallbackTest.kt | 25 +- .../resources/ResourceDataStoreManagerTest.kt | 29 +- .../trace/internal/DatadogSpanLogger.kt | 2 +- .../trace/internal/DatadogSpanLoggerTest.kt | 12 + .../webview/internal/rum/WebViewRumFeature.kt | 2 +- .../rum/domain/WebViewNativeRumViewsCache.kt | 4 +- .../android/webview/WebViewTrackingTest.kt | 1 + .../domain/WebViewNativeRumViewsCacheTest.kt | 74 +++-- .../datadog/android/core/stub/StubSDKCore.kt | 6 +- .../core/stub/StubScheduledExecutorService.kt | 21 +- .../datadog/benchmark/DatadogVitalsMeter.kt | 6 +- .../internal/reader/FpsVitalReader.kt | 7 +- tools/unit/build.gradle.kts | 1 + 130 files changed, 1752 insertions(+), 977 deletions(-) rename dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/{ObservableBlockingQueueTest.kt => ObservableLinkedBlockingQueueTest.kt} (92%) create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt diff --git a/dd-sdk-android-core/api/apiSurface b/dd-sdk-android-core/api/apiSurface index ad942a1b97..60ff595522 100644 --- a/dd-sdk-android-core/api/apiSurface +++ b/dd-sdk-android-core/api/apiSurface @@ -132,6 +132,7 @@ fun com.datadog.android.api.InternalLogger.measureMethodCallPerf(Class fun FeatureScope.getContextFuture(Set = emptySet()): java.util.concurrent.Future? interface com.datadog.android.api.feature.FeatureSdkCore : com.datadog.android.api.SdkCore val internalLogger: com.datadog.android.api.InternalLogger + val timeProvider: com.datadog.android.internal.time.TimeProvider fun registerFeature(Feature) fun getFeature(String): FeatureScope? fun updateFeatureContext(String, Boolean = true, (MutableMap) -> Unit) @@ -403,7 +404,7 @@ interface com.datadog.android.core.sampling.Sampler interface com.datadog.android.core.thread.FlushableExecutorService : java.util.concurrent.ExecutorService fun drainTo(MutableCollection) interface Factory - fun create(com.datadog.android.api.InternalLogger, String, com.datadog.android.core.configuration.BackPressureStrategy): FlushableExecutorService + fun create(com.datadog.android.api.InternalLogger, String, com.datadog.android.core.configuration.BackPressureStrategy, com.datadog.android.internal.time.TimeProvider): FlushableExecutorService interface com.datadog.android.event.EventMapper fun map(T): T? class com.datadog.android.event.MapperSerializer : com.datadog.android.core.persistence.Serializer diff --git a/dd-sdk-android-core/api/dd-sdk-android-core.api b/dd-sdk-android-core/api/dd-sdk-android-core.api index bc77d2df00..627b48e909 100644 --- a/dd-sdk-android-core/api/dd-sdk-android-core.api +++ b/dd-sdk-android-core/api/dd-sdk-android-core.api @@ -423,6 +423,7 @@ public abstract interface class com/datadog/android/api/feature/FeatureSdkCore : public abstract fun getFeature (Ljava/lang/String;)Lcom/datadog/android/api/feature/FeatureScope; public abstract fun getFeatureContext (Ljava/lang/String;Z)Ljava/util/Map; public abstract fun getInternalLogger ()Lcom/datadog/android/api/InternalLogger; + public abstract fun getTimeProvider ()Lcom/datadog/android/internal/time/TimeProvider; public abstract fun registerFeature (Lcom/datadog/android/api/feature/Feature;)V public abstract fun removeContextUpdateReceiver (Lcom/datadog/android/api/feature/FeatureContextUpdateReceiver;)V public abstract fun removeEventReceiver (Ljava/lang/String;)V @@ -1016,7 +1017,7 @@ public abstract interface class com/datadog/android/core/thread/FlushableExecuto } public abstract interface class com/datadog/android/core/thread/FlushableExecutorService$Factory { - public abstract fun create (Lcom/datadog/android/api/InternalLogger;Ljava/lang/String;Lcom/datadog/android/core/configuration/BackPressureStrategy;)Lcom/datadog/android/core/thread/FlushableExecutorService; + public abstract fun create (Lcom/datadog/android/api/InternalLogger;Ljava/lang/String;Lcom/datadog/android/core/configuration/BackPressureStrategy;Lcom/datadog/android/internal/time/TimeProvider;)Lcom/datadog/android/core/thread/FlushableExecutorService; } public abstract interface class com/datadog/android/event/EventMapper { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureSdkCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureSdkCore.kt index aee88240c5..e8e47b07f3 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureSdkCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureSdkCore.kt @@ -8,6 +8,7 @@ package com.datadog.android.api.feature import com.datadog.android.api.InternalLogger import com.datadog.android.api.SdkCore +import com.datadog.android.internal.time.TimeProvider import okhttp3.Call import okhttp3.OkHttpClient import java.util.UUID @@ -27,6 +28,11 @@ interface FeatureSdkCore : SdkCore { */ val internalLogger: InternalLogger + /** + * The [TimeProvider] used by this core instance for current timestamps. + */ + val timeProvider: TimeProvider + /** * Registers a feature to this instance of the Datadog SDK. * diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt index 2dedfc6d7d..4394b1c066 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt @@ -135,7 +135,7 @@ internal class CoreFeature( .writeTimeout(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS) .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)) .connectionSpecs(listOf(connectionSpec)) - .dns(RotatingDnsResolver()) // NPE cannot happen here + .dns(RotatingDnsResolver(timeProvider = timeProvider)) // NPE cannot happen here .build() } @@ -327,7 +327,7 @@ internal class CoreFeature( } fun createExecutorService(executorContext: String): ExecutorService { - return executorServiceFactory.create(internalLogger, executorContext, backpressureStrategy) + return executorServiceFactory.create(internalLogger, executorContext, backpressureStrategy, timeProvider) } fun createScheduledExecutorService(executorContext: String): ScheduledExecutorService { @@ -652,7 +652,8 @@ internal class CoreFeature( persistenceExecutorService = executorServiceFactory.create( internalLogger = internalLogger, executorContext = "storage", - backPressureStrategy = backpressureStrategy + backPressureStrategy = backpressureStrategy, + timeProvider = timeProvider ) val contextQueue = BackPressuredBlockingQueue( internalLogger, @@ -662,7 +663,8 @@ internal class CoreFeature( // just notify when reached onItemDropped = {}, onThresholdReached = {}, - backpressureMitigation = null + backpressureMitigation = null, + timeProvider = timeProvider ) @Suppress("UnsafeThirdPartyFunctionCall") // all parameters are safe contextExecutorService = ThreadPoolExecutor( @@ -753,8 +755,8 @@ internal class CoreFeature( " process of your application." internal val DEFAULT_FLUSHABLE_EXECUTOR_SERVICE_FACTORY = - FlushableExecutorService.Factory { logger, executorContext, backPressureStrategy -> - BackPressureExecutorService(logger, executorContext, backPressureStrategy) + FlushableExecutorService.Factory { logger, executorContext, backPressureStrategy, timeProvider -> + BackPressureExecutorService(logger, executorContext, backPressureStrategy, timeProvider) } internal val DEFAULT_SCHEDULED_EXECUTOR_SERVICE_FACTORY = diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index eb6c9a01b4..d65924a9cb 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -34,7 +34,6 @@ import com.datadog.android.core.internal.logger.SdkInternalLogger import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.core.internal.time.DefaultAppStartTimeProvider -import com.datadog.android.core.internal.time.composeTimeInfo import com.datadog.android.core.internal.utils.executeSafe import com.datadog.android.core.internal.utils.getSafe import com.datadog.android.core.internal.utils.scheduleSafe @@ -42,6 +41,7 @@ import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.core.thread.FlushableExecutorService import com.datadog.android.error.internal.CrashReportsFeature import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.privacy.TrackingConsent import com.google.gson.JsonObject import okhttp3.Call @@ -101,7 +101,17 @@ internal class DatadogCore( /** @inheritDoc */ override val time: TimeInfo get() { - return coreFeature.timeProvider.composeTimeInfo() + return with(coreFeature.timeProvider) { + val deviceTimeMs = getDeviceTimestamp() + val serverTimeMs = getServerTimestamp() + TimeInfo( + deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(deviceTimeMs), + serverTimeNs = TimeUnit.MILLISECONDS.toNanos(serverTimeMs), + serverTimeOffsetNs = TimeUnit.MILLISECONDS + .toNanos(serverTimeMs - deviceTimeMs), + serverTimeOffsetMs = serverTimeMs - deviceTimeMs + ) + } } /** @inheritDoc */ @@ -115,6 +125,10 @@ internal class DatadogCore( /** @inheritDoc */ override val internalLogger: InternalLogger = internalLoggerProvider(this) + /** @inheritDoc */ + override val timeProvider: TimeProvider + get() = coreFeature.timeProvider + /** @inheritDoc */ override var isDeveloperModeEnabled: Boolean = false internal set diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt index 39445c207c..18bb8b58b5 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt @@ -19,6 +19,8 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.logger.SdkInternalLogger import com.datadog.android.core.internal.net.DefaultFirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver +import com.datadog.android.internal.time.DefaultTimeProvider +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.privacy.TrackingConsent import com.google.gson.JsonObject import okhttp3.Call @@ -41,15 +43,16 @@ import java.util.concurrent.TimeUnit /** * A no-op implementation of [SdkCore]. */ -@Suppress("TooManyFunctions") internal object NoOpInternalSdkCore : InternalSdkCore { override val name: String = "no-op" - override val time: TimeInfo = with(System.currentTimeMillis()) { - TimeInfo.EMPTY.copy( + override val time: TimeInfo = with(timeProvider.getDeviceTimestamp()) { + TimeInfo( deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(this), - serverTimeNs = TimeUnit.MILLISECONDS.toNanos(this) + serverTimeNs = TimeUnit.MILLISECONDS.toNanos(this), + serverTimeOffsetNs = 0L, + serverTimeOffsetMs = 0L ) } @@ -59,6 +62,9 @@ internal object NoOpInternalSdkCore : InternalSdkCore { override val internalLogger: InternalLogger get() = SdkInternalLogger(this) + override val timeProvider: TimeProvider + get() = DefaultTimeProvider() + // region InternalSdkCore override val networkInfo: NetworkInfo diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt index 73f46f8d11..8bf85da834 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt @@ -366,7 +366,8 @@ internal class SdkFeature( executorService = coreFeature.persistenceExecutorService, filePersistenceConfig = filePersistenceConfig, internalLogger = internalLogger, - metricsDispatcher = metricsDispatcher + metricsDispatcher = metricsDispatcher, + timeProvider = coreFeature.timeProvider ) this.fileOrchestrator = fileOrchestrator @@ -398,7 +399,8 @@ internal class SdkFeature( sdkVersion = coreFeature.sdkVersion, androidInfoProvider = coreFeature.androidInfoProvider, executionTimer = GlobalBenchmark.createExecutionTimer( - track = wrappedFeature.name + track = wrappedFeature.name, + timeProvider = coreFeature.timeProvider ) ) } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt index a176a6bdb6..424fced90b 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt @@ -6,6 +6,7 @@ package com.datadog.android.core.internal.data.upload +import com.datadog.android.internal.time.TimeProvider import okhttp3.Dns import java.net.InetAddress import kotlin.time.Duration @@ -14,17 +15,17 @@ import kotlin.time.Duration.Companion.nanoseconds internal class RotatingDnsResolver( private val delegate: Dns = Dns.SYSTEM, - private val ttl: Duration = TTL_30_MIN + private val ttl: Duration = TTL_30_MIN, + private val timeProvider: TimeProvider ) : Dns { data class ResolvedHost( val hostname: String, - val addresses: MutableList + val addresses: MutableList, + private val resolutionTimestamp: Long ) { - private val resolutionTimestamp: Long = System.nanoTime() - - fun getAge(): Duration { - return (System.nanoTime() - resolutionTimestamp).nanoseconds + fun getAge(currentTime: Long): Duration { + return (currentTime - resolutionTimestamp).nanoseconds } fun rotate() { @@ -50,7 +51,7 @@ internal class RotatingDnsResolver( } else { @Suppress("UnsafeThirdPartyFunctionCall") // handled by caller val result = delegate.lookup(hostname) - knownHosts[hostname] = ResolvedHost(hostname, result.toMutableList()) + knownHosts[hostname] = ResolvedHost(hostname, result.toMutableList(), timeProvider.getDeviceElapsedTimeNs()) safeCopy(result) } } @@ -66,7 +67,7 @@ internal class RotatingDnsResolver( } private fun isValid(knownHost: ResolvedHost): Boolean { - return knownHost.getAge() < ttl && knownHost.addresses.isNotEmpty() + return knownHost.getAge(timeProvider.getDeviceElapsedTimeNs()) < ttl && knownHost.addresses.isNotEmpty() } // endregion diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt index 14f208fa26..2e55bc0887 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt @@ -20,6 +20,7 @@ import com.datadog.android.core.sampling.RateBasedSampler import com.datadog.android.internal.attributes.LocalAttribute import com.datadog.android.internal.attributes.enrichWithNonNullAttribute import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.internal.time.DefaultTimeProvider internal class SdkInternalLogger( private val sdkCore: FeatureSdkCore?, @@ -140,7 +141,8 @@ internal class SdkInternalLogger( internalLogger = this, operationName = operationName, callerClass = callerClass, - creationSampleRate = samplingRate + creationSampleRate = samplingRate, + timeProvider = sdkCore?.timeProvider ?: DefaultTimeProvider() ) } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt index 8b739bae42..925f1a483a 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt @@ -25,7 +25,7 @@ internal class BatchMetricsDispatcher( private val uploadConfiguration: DataUploadConfiguration?, private val filePersistenceConfig: FilePersistenceConfig, private val internalLogger: InternalLogger, - private val dateTimeProvider: TimeProvider + private val timeProvider: TimeProvider ) : MetricsDispatcher, ProcessLifecycleMonitor.Callback { @@ -90,7 +90,7 @@ internal class BatchMetricsDispatcher( numPendingBatches: Int ): Map? { val fileCreationTimestamp = file.nameAsTimestampSafe(internalLogger) ?: return null - val fileAgeInMillis = dateTimeProvider.getDeviceTimestamp() - fileCreationTimestamp + val fileAgeInMillis = timeProvider.getDeviceTimestamp() - fileCreationTimestamp if (fileAgeInMillis < 0) { // the device time was manually modified or the time zone changed // we are dropping this metric to not skew our charts diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt index 1bb0c6d646..b5392edd0c 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt @@ -10,6 +10,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.MethodCallSamplingRate import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.PerformanceMetric.Companion.METRIC_TYPE +import com.datadog.android.internal.time.TimeProvider /** * Performance metric to measure the execution time for a method. @@ -17,18 +18,20 @@ import com.datadog.android.core.metrics.PerformanceMetric.Companion.METRIC_TYPE * @param operationName the operation name * @param callerClass - the class calling the performance metric. * @param creationSampleRate - sampling frequency used to create the metric - * @param startTime - the time when the metric is instantiated, to be used as the start point for the measurement. + * @param timeProvider - the provider for time measurements. */ internal class MethodCalledTelemetry( internal val internalLogger: InternalLogger, internal val operationName: String, internal val callerClass: String, internal val creationSampleRate: Float, - internal val startTime: Long = System.nanoTime() + internal val timeProvider: TimeProvider ) : PerformanceMetric { + internal val startTime: Long = timeProvider.getDeviceElapsedTimeNs() + override fun stopAndSend(isSuccessful: Boolean) { - val executionTime = System.nanoTime() - startTime + val executionTime = timeProvider.getDeviceElapsedTimeNs() - startTime val additionalProperties: MutableMap = mutableMapOf() additionalProperties[EXECUTION_TIME] = executionTime diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigrator.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigrator.kt index a33dc71bee..fa35bbbe9f 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigrator.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigrator.kt @@ -10,11 +10,13 @@ import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover import com.datadog.android.core.internal.persistence.file.FileOrchestrator +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.privacy.TrackingConsent internal class ConsentAwareFileMigrator( private val fileMover: FileMover, - private val internalLogger: InternalLogger + private val internalLogger: InternalLogger, + private val timeProvider: TimeProvider ) : DataMigrator { @WorkerThread @@ -47,7 +49,8 @@ internal class ConsentAwareFileMigrator( WipeDataMigrationOperation( previousFileOrchestrator.getRootDir(), fileMover, - internalLogger + internalLogger, + timeProvider ) } @@ -56,7 +59,8 @@ internal class ConsentAwareFileMigrator( WipeDataMigrationOperation( newFileOrchestrator.getRootDir(), fileMover, - internalLogger + internalLogger, + timeProvider ) } @@ -65,7 +69,8 @@ internal class ConsentAwareFileMigrator( previousFileOrchestrator.getRootDir(), newFileOrchestrator.getRootDir(), fileMover, - internalLogger + internalLogger, + timeProvider ) } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestrator.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestrator.kt index c776b234c6..f28efad6b3 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestrator.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestrator.kt @@ -13,6 +13,7 @@ import com.datadog.android.core.internal.persistence.file.FileOrchestrator import com.datadog.android.core.internal.persistence.file.FilePersistenceConfig import com.datadog.android.core.internal.persistence.file.batch.BatchFileOrchestrator import com.datadog.android.core.internal.privacy.ConsentProvider +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.privacy.TrackingConsent import java.io.File import java.util.Locale @@ -41,24 +42,28 @@ internal class FeatureFileOrchestrator( executorService: ExecutorService, filePersistenceConfig: FilePersistenceConfig, internalLogger: InternalLogger, - metricsDispatcher: MetricsDispatcher + metricsDispatcher: MetricsDispatcher, + timeProvider: TimeProvider ) : this( consentProvider, BatchFileOrchestrator( File(storageDir, PENDING_DIR.format(Locale.US, featureName)), filePersistenceConfig, internalLogger, - metricsDispatcher + metricsDispatcher, + timeProvider ), BatchFileOrchestrator( File(storageDir, GRANTED_DIR.format(Locale.US, featureName)), filePersistenceConfig, internalLogger, - metricsDispatcher + metricsDispatcher, + timeProvider ), ConsentAwareFileMigrator( FileMover(internalLogger), - internalLogger + internalLogger, + timeProvider ), executorService, internalLogger diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperation.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperation.kt index e043c35586..db740fb1e8 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperation.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperation.kt @@ -10,6 +10,7 @@ import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover import com.datadog.android.core.internal.utils.retryWithDelay +import com.datadog.android.internal.time.TimeProvider import java.io.File import java.util.concurrent.TimeUnit @@ -21,7 +22,8 @@ internal class MoveDataMigrationOperation( internal val fromDir: File?, internal val toDir: File?, internal val fileMover: FileMover, - internal val internalLogger: InternalLogger + internal val internalLogger: InternalLogger, + internal val timeProvider: TimeProvider ) : DataMigrationOperation { @WorkerThread @@ -39,7 +41,7 @@ internal class MoveDataMigrationOperation( { WARN_NULL_DEST_DIR } ) } else { - retryWithDelay(MAX_RETRY, RETRY_DELAY_NS, internalLogger) { + retryWithDelay(MAX_RETRY, RETRY_DELAY_NS, internalLogger, timeProvider) { fileMover.moveFiles(fromDir, toDir) } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperation.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperation.kt index cf120e0e14..83c6333b6c 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperation.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperation.kt @@ -10,6 +10,7 @@ import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover import com.datadog.android.core.internal.utils.retryWithDelay +import com.datadog.android.internal.time.TimeProvider import java.io.File import java.util.concurrent.TimeUnit @@ -19,7 +20,8 @@ import java.util.concurrent.TimeUnit internal class WipeDataMigrationOperation( internal val targetDir: File?, internal val fileMover: FileMover, - internal val internalLogger: InternalLogger + internal val internalLogger: InternalLogger, + internal val timeProvider: TimeProvider ) : DataMigrationOperation { @WorkerThread @@ -31,7 +33,7 @@ internal class WipeDataMigrationOperation( { WARN_NULL_DIR } ) } else { - retryWithDelay(MAX_RETRY, RETRY_DELAY_NS, internalLogger) { + retryWithDelay(MAX_RETRY, RETRY_DELAY_NS, internalLogger, timeProvider) { fileMover.delete(targetDir) } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt index dc689cdd38..01a740f6b9 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt @@ -19,6 +19,7 @@ import com.datadog.android.core.internal.persistence.file.existsSafe import com.datadog.android.core.internal.persistence.file.lengthSafe import com.datadog.android.core.internal.persistence.file.listFilesSafe import com.datadog.android.core.internal.persistence.file.mkdirsSafe +import com.datadog.android.internal.time.TimeProvider import java.io.File import java.io.FileFilter import java.util.Locale @@ -33,6 +34,7 @@ internal class BatchFileOrchestrator( internal val config: FilePersistenceConfig, private val internalLogger: InternalLogger, private val metricsDispatcher: MetricsDispatcher, + private val timeProvider: TimeProvider, private val pendingFiles: AtomicInteger = AtomicInteger(0) ) : FileOrchestrator { @@ -64,7 +66,7 @@ internal class BatchFileOrchestrator( var files = listBatchFiles() files = deleteObsoleteFiles(files) freeSpaceIfNeeded(files) - lastCleanupTimestamp = System.currentTimeMillis() + lastCleanupTimestamp = timeProvider.getDeviceTimestamp() } return getReusableWritableFile() ?: createNewFile() @@ -79,7 +81,7 @@ internal class BatchFileOrchestrator( val files = listSortedBatchFiles().let { deleteObsoleteFiles(it) } - lastCleanupTimestamp = System.currentTimeMillis() + lastCleanupTimestamp = timeProvider.getDeviceTimestamp() pendingFiles.set(files.count()) return files.firstOrNull { @@ -202,7 +204,7 @@ internal class BatchFileOrchestrator( } private fun createNewFile(): File { - val newFileName = System.currentTimeMillis().toString() + val newFileName = timeProvider.getDeviceTimestamp().toString() val newFile = File(rootDir, newFileName) val closedFile = previousFile val closedFileLastAccessTimestamp = lastFileAccessTimestamp @@ -217,7 +219,7 @@ internal class BatchFileOrchestrator( } previousFile = newFile previousFileItemCount = 1 - lastFileAccessTimestamp = System.currentTimeMillis() + lastFileAccessTimestamp = timeProvider.getDeviceTimestamp() pendingFiles.incrementAndGet() return newFile } @@ -244,7 +246,7 @@ internal class BatchFileOrchestrator( return if (isRecentEnough && hasRoomForMore && hasSlotForMore) { previousFileItemCount = lastKnownFileItemCount + 1 - lastFileAccessTimestamp = System.currentTimeMillis() + lastFileAccessTimestamp = timeProvider.getDeviceTimestamp() lastFile } else { null @@ -252,13 +254,13 @@ internal class BatchFileOrchestrator( } private fun isFileRecent(file: File, delayMs: Long): Boolean { - val now = System.currentTimeMillis() + val now = timeProvider.getDeviceTimestamp() val fileTimestamp = file.name.toLongOrNull() ?: 0L return fileTimestamp >= (now - delayMs) } private fun deleteObsoleteFiles(files: List): List { - val threshold = System.currentTimeMillis() - config.oldFileThreshold + val threshold = timeProvider.getDeviceTimestamp() - config.oldFileThreshold return files .mapNotNull { val isOldFile = (it.name.toLongOrNull() ?: 0) < threshold @@ -328,7 +330,7 @@ internal class BatchFileOrchestrator( } private fun canDoCleanup(): Boolean { - return System.currentTimeMillis() - lastCleanupTimestamp > config.cleanupFrequencyThreshold + return timeProvider.getDeviceTimestamp() - lastCleanupTimestamp > config.cleanupFrequencyThreshold } private val File.metadata: File diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorService.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorService.kt index a00fcd59a1..729f4d6016 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorService.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorService.kt @@ -9,6 +9,7 @@ package com.datadog.android.core.internal.thread import com.datadog.android.api.InternalLogger import com.datadog.android.core.configuration.BackPressureStrategy import com.datadog.android.core.thread.FlushableExecutorService +import com.datadog.android.internal.time.TimeProvider import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit @@ -18,13 +19,14 @@ import java.util.concurrent.TimeUnit internal class BackPressureExecutorService( val logger: InternalLogger, executorContext: String, - backpressureStrategy: BackPressureStrategy + backpressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider ) : ThreadPoolExecutor( CORE_POOL_SIZE, CORE_POOL_SIZE, THREAD_POOL_MAX_KEEP_ALIVE_MS, TimeUnit.MILLISECONDS, - BackPressuredBlockingQueue(logger, executorContext, backpressureStrategy), + BackPressuredBlockingQueue(logger, executorContext, backpressureStrategy, timeProvider), DatadogThreadFactory(executorContext) ), FlushableExecutorService { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt index 73f8f464ce..683039802b 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt @@ -10,12 +10,13 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.core.configuration.BackPressureMitigation import com.datadog.android.core.configuration.BackPressureStrategy import com.datadog.android.internal.thread.NamedExecutionUnit +import com.datadog.android.internal.time.TimeProvider import java.util.concurrent.TimeUnit /** - * [LinkedBlockingQueue] that supports backpressure handling via the chosen backpressure mitigation strategy. + * [java.util.concurrent.LinkedBlockingQueue] that supports backpressure handling via the chosen backpressure mitigation strategy. * - * This queue may be either bounded or unbounded by specifying capacity. See docs of [LinkedBlockingQueue] for more + * This queue may be either bounded or unbounded by specifying capacity. See docs of [java.util.concurrent.LinkedBlockingQueue] for more * details. * * If queue is unbounded, there is still a possibility to be notified if certain size threshold is reached. @@ -29,11 +30,13 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue private val onThresholdReached: () -> Unit private val onItemDropped: (Any) -> Unit private val backpressureMitigation: BackPressureMitigation? + private val timeProvider: TimeProvider constructor( logger: InternalLogger, executorContext: String, - backPressureStrategy: BackPressureStrategy + backPressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider ) : this( logger, executorContext, @@ -41,7 +44,8 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue backPressureStrategy.capacity, backPressureStrategy.onThresholdReached, backPressureStrategy.onItemDropped, - backPressureStrategy.backpressureMitigation + backPressureStrategy.backpressureMitigation, + timeProvider ) constructor( @@ -51,7 +55,8 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue capacity: Int, onThresholdReached: () -> Unit, onItemDropped: (Any) -> Unit, - backpressureMitigation: BackPressureMitigation? + backpressureMitigation: BackPressureMitigation?, + timeProvider: TimeProvider ) : super(capacity) { this.logger = logger this.executorContext = executorContext @@ -60,6 +65,7 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue this.onThresholdReached = onThresholdReached this.onItemDropped = onItemDropped this.backpressureMitigation = backpressureMitigation + this.timeProvider = timeProvider } override fun offer(e: E): Boolean { @@ -116,7 +122,7 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue } private fun notifyThresholdReached() { - val dump = dumpQueue() + val dump = dumpQueue(timeProvider.getDeviceTimestamp()) val backPressureMap = buildMap { put("capacity", capacity) if (!dump.isNullOrEmpty()) { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueue.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueue.kt index 8402dc322e..b27057f4fd 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueue.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueue.kt @@ -11,29 +11,17 @@ import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong -internal open class ObservableLinkedBlockingQueue : LinkedBlockingQueue { - - private val currentTimeProvider: () -> Long - - constructor( - currentTimeProvider: () -> Long = { System.currentTimeMillis() } - ) : this(Int.MAX_VALUE, currentTimeProvider) - - constructor( - capacity: Int, - currentTimeProvider: () -> Long = { System.currentTimeMillis() } - ) : super(capacity) { - this.currentTimeProvider = currentTimeProvider - } +internal open class ObservableLinkedBlockingQueue( + capacity: Int = Int.MAX_VALUE +) : LinkedBlockingQueue(capacity) { private val lastDumpTimestamp: AtomicLong = AtomicLong(0) - fun dumpQueue(): Map? { - val currentTime = currentTimeProvider.invoke() + fun dumpQueue(currentTimestamp: Long): Map? { val last = lastDumpTimestamp.get() - val timeSinceLastDump = currentTime - last + val timeSinceLastDump = currentTimestamp - last return if (timeSinceLastDump > DUMPING_TIME_INTERVAL_IN_MS) { - if (lastDumpTimestamp.compareAndSet(last, currentTime)) { + if (lastDumpTimestamp.compareAndSet(last, currentTimestamp)) { buildDumpMap() } else { null diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt index 7def26113d..c7775b1938 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt @@ -7,13 +7,18 @@ package com.datadog.android.core.internal.thread import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.TimeProvider import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit internal const val MAX_SLEEP_DURATION_IN_MS = 10L -internal fun ThreadPoolExecutor.waitToIdle(timeoutInMs: Long, internalLogger: InternalLogger): Boolean { - val startTime = System.nanoTime() +internal fun ThreadPoolExecutor.waitToIdle( + timeoutInMs: Long, + internalLogger: InternalLogger, + timeProvider: TimeProvider +): Boolean { + val startTime = timeProvider.getDeviceElapsedTimeNs() val timeoutInNs = TimeUnit.MILLISECONDS.toNanos(timeoutInMs) val sleepDurationInMs = timeoutInMs.coerceIn(0, MAX_SLEEP_DURATION_IN_MS) var interrupted: Boolean @@ -22,7 +27,7 @@ internal fun ThreadPoolExecutor.waitToIdle(timeoutInMs: Long, internalLogger: In return true } interrupted = sleepSafe(sleepDurationInMs, internalLogger) - } while (((System.nanoTime() - startTime) < timeoutInNs) && !interrupted) + } while (((timeProvider.getDeviceElapsedTimeNs() - startTime) < timeoutInNs) && !interrupted) return isIdle() } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt index 879c20faf4..ad407dc1fd 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt @@ -11,11 +11,14 @@ import android.os.Build import android.os.Process import android.os.SystemClock import com.datadog.android.core.internal.system.BuildSdkVersionProvider +import com.datadog.android.internal.time.DefaultTimeProvider +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.DdRumContentProvider import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.seconds internal class DefaultAppStartTimeProvider( + private val timeProvider: TimeProvider = DefaultTimeProvider(), buildSdkVersionProvider: BuildSdkVersionProvider = BuildSdkVersionProvider.DEFAULT ) : AppStartTimeProvider { @@ -24,7 +27,7 @@ internal class DefaultAppStartTimeProvider( when { buildSdkVersionProvider.version >= Build.VERSION_CODES.N -> { val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val result = System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(diffMs) + val result = timeProvider.getDeviceElapsedTimeNs() - TimeUnit.MILLISECONDS.toNanos(diffMs) /** * Occasionally [Process.getStartElapsedRealtime] returns buggy values. We filter them and fall back @@ -41,7 +44,7 @@ internal class DefaultAppStartTimeProvider( } override val appUptimeNs: Long - get() = System.nanoTime() - appStartTimeNs + get() = timeProvider.getDeviceElapsedTimeNs() - appStartTimeNs companion object { internal val PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS = 10.seconds.inWholeNanoseconds diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt index 5a8a426fcd..85a798fd7f 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt @@ -11,6 +11,11 @@ import com.datadog.android.internal.time.TimeProvider import com.lyft.kronos.Clock import java.util.concurrent.TimeUnit +/** + * A [TimeProvider] implementation that uses Kronos NTP for server time synchronization. + * + * Device timestamp and elapsed time are inherited from [TimeProvider] default implementations. + */ internal class KronosTimeProvider( private val clock: Clock, private val internalLogger: InternalLogger @@ -24,7 +29,7 @@ internal class KronosTimeProvider( override fun getServerOffsetMillis(): Long { return clock.safeGetCurrentTimeMs() .map { server -> - val device = System.currentTimeMillis() + val device = getDeviceTimestamp() val delta = server - device delta } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt index 2663481ea3..878daf7b72 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt @@ -7,6 +7,7 @@ package com.datadog.android.core.internal.utils import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.internal.utils.NULL_MAP_VALUE import com.datadog.android.lint.InternalApi import com.google.gson.JsonArray @@ -23,9 +24,10 @@ internal fun retryWithDelay( times: Int, retryDelayNs: Long, internalLogger: InternalLogger, + timeProvider: TimeProvider, block: () -> Boolean ): Boolean { - return retryWithDelay(block, times, retryDelayNs, internalLogger) + return retryWithDelay(block, times, retryDelayNs, internalLogger, timeProvider) } @Suppress("TooGenericExceptionCaught") @@ -33,13 +35,14 @@ internal inline fun retryWithDelay( block: () -> Boolean, times: Int, loopsDelayInNanos: Long, - internalLogger: InternalLogger + internalLogger: InternalLogger, + timeProvider: TimeProvider ): Boolean { var retryCounter = 1 var wasSuccessful = false - var loopTimeOrigin = System.nanoTime() - loopsDelayInNanos + var loopTimeOrigin = timeProvider.getDeviceElapsedTimeNs() - loopsDelayInNanos while (retryCounter <= times && !wasSuccessful) { - if ((System.nanoTime() - loopTimeOrigin) >= loopsDelayInNanos) { + if ((timeProvider.getDeviceElapsedTimeNs() - loopTimeOrigin) >= loopsDelayInNanos) { wasSuccessful = try { block() } catch (e: Exception) { @@ -54,7 +57,7 @@ internal inline fun retryWithDelay( ) false } - loopTimeOrigin = System.nanoTime() + loopTimeOrigin = timeProvider.getDeviceElapsedTimeNs() retryCounter++ } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/thread/FlushableExecutorService.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/thread/FlushableExecutorService.kt index ce38ef0f05..572195b549 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/thread/FlushableExecutorService.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/thread/FlushableExecutorService.kt @@ -8,6 +8,7 @@ package com.datadog.android.core.thread import com.datadog.android.api.InternalLogger import com.datadog.android.core.configuration.BackPressureStrategy +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.lint.InternalApi import java.util.concurrent.ExecutorService @@ -36,12 +37,14 @@ interface FlushableExecutorService : ExecutorService { * @param internalLogger the internal logger * @param executorContext Context to be used for logging and naming threads running on this executor. * @param backPressureStrategy the strategy to handle back-pressure + * @param timeProvider - the provider for time measurements. * @return the instance */ fun create( internalLogger: InternalLogger, executorContext: String, - backPressureStrategy: BackPressureStrategy + backPressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider ): FlushableExecutorService } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt index 51eb001496..b12669a57c 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt @@ -57,7 +57,7 @@ internal class DatadogExceptionHandler( // give some time to the persistence executor service to finish its tasks if (sdkCore is InternalSdkCore) { val idled = (sdkCore.getPersistenceExecutorService() as? ThreadPoolExecutor) - ?.waitToIdle(MAX_WAIT_FOR_IDLE_TIME_IN_MS, sdkCore.internalLogger) ?: true + ?.waitToIdle(MAX_WAIT_FOR_IDLE_TIME_IN_MS, sdkCore.internalLogger, sdkCore.timeProvider) ?: true if (!idled) { sdkCore.internalLogger.log( InternalLogger.Level.WARN, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt index 3e922b5cfc..5bccc9ba1a 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt @@ -111,7 +111,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(fakeConfiguration.copy(crashReportsEnabled = crashReportsEnabled)) } @@ -187,7 +187,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(fakeConfiguration) } @@ -210,7 +210,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -238,7 +238,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -267,7 +267,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -295,7 +295,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -354,7 +354,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(configuration) } @@ -402,7 +402,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(fakeConfiguration.copy(additionalConfig = mapOf(Datadog.DD_SOURCE_TAG to source))) } @@ -420,7 +420,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy(additionalConfig = mapOf(Datadog.DD_SOURCE_TAG to source)) @@ -440,7 +440,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy(additionalConfig = mapOf(Datadog.DD_SOURCE_TAG to source)) @@ -460,7 +460,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(fakeConfiguration.copy(additionalConfig = customAttributes.nonNullData)) } @@ -478,7 +478,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -500,7 +500,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -522,7 +522,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -544,7 +544,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize(fakeConfiguration.copy(additionalConfig = customAttributes.nonNullData)) } @@ -562,7 +562,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -584,7 +584,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( @@ -608,7 +608,7 @@ internal class DatadogCoreInitializationTest { appContext.mockInstance, fakeInstanceId, fakeInstanceName, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService } + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService } ).apply { initialize( fakeConfiguration.copy( diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index 3d3e66789e..bfd5369756 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -28,7 +28,6 @@ import com.datadog.android.core.internal.privacy.ConsentProvider import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.core.internal.user.MutableUserInfoProvider import com.datadog.android.core.thread.FlushableExecutorService -import com.datadog.android.internal.time.DefaultTimeProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent @@ -40,6 +39,7 @@ import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes +import com.datadog.tools.unit.stub.StubTimeProvider import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.AdvancedForgery @@ -53,7 +53,6 @@ import fr.xgouchet.elmyr.annotation.StringForgeryType 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.AfterEach import org.junit.jupiter.api.AssertionFailureBuilder import org.junit.jupiter.api.BeforeEach @@ -151,7 +150,7 @@ internal class DatadogCoreTest { fakeInstanceId, fakeInstanceName, internalLoggerProvider = { mockInternalLogger }, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService }, + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService }, buildSdkVersionProvider = mockBuildSdkVersionProvider ).apply { initialize(fakeConfiguration) @@ -1015,22 +1014,28 @@ internal class DatadogCoreTest { } @Test - fun `M provide time info without correction W time() {NoOpTimeProvider}`() { + fun `M provide time info without correction W time() {NoOpTimeProvider}`( + @LongForgery(min = 0L) fakeDeviceTimestampMs: Long, + @LongForgery(min = 0L) fakeServerTimestampMs: Long + ) { // Given testedCore.coreFeature = mock() whenever(testedCore.coreFeature.initialized).thenReturn(AtomicBoolean()) - whenever(testedCore.coreFeature.timeProvider) doReturn DefaultTimeProvider() + val stubTimeProvider = StubTimeProvider( + deviceTimestampMs = fakeDeviceTimestampMs, + serverTimestampMs = fakeServerTimestampMs + ) + whenever(testedCore.coreFeature.timeProvider) doReturn stubTimeProvider // When val time = testedCore.time // Then - // We do keep a margin of 1ms delay as this test can sometimes be flaky. - // the DatadogCore.time implementation computes server and device time independently and it can sometimes - // happen that those computations land on successive ms, leading to a 1second offset - assertThat(time.deviceTimeNs).isCloseTo(time.serverTimeNs, Offset.offset(msToNs)) - assertThat(time.serverTimeOffsetMs).isLessThanOrEqualTo(1) - assertThat(time.serverTimeOffsetNs).isLessThanOrEqualTo(msToNs) + val expectedOffsetMs = fakeServerTimestampMs - fakeDeviceTimestampMs + assertThat(time.deviceTimeNs).isEqualTo(TimeUnit.MILLISECONDS.toNanos(fakeDeviceTimestampMs)) + assertThat(time.serverTimeNs).isEqualTo(TimeUnit.MILLISECONDS.toNanos(fakeServerTimestampMs)) + assertThat(time.serverTimeOffsetMs).isEqualTo(expectedOffsetMs) + assertThat(time.serverTimeOffsetNs).isEqualTo(TimeUnit.MILLISECONDS.toNanos(expectedOffsetMs)) } @Test @@ -1509,9 +1514,6 @@ internal class DatadogCoreTest { companion object { - val msToNs = TimeUnit.MILLISECONDS.toNanos(1) - val secondsToNs = TimeUnit.SECONDS.toNanos(1) - val appContext = ApplicationContextTestConfiguration(Application::class.java) @TestConfigurationsProvider diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt index 60d9626b9d..7cd00c575f 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt @@ -147,7 +147,7 @@ internal class CoreFeatureTest { testedFeature = CoreFeature( mockInternalLogger, mockAppStartTimeProvider, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService }, + executorServiceFactory = { _, _, _, _ -> mockPersistenceExecutorService }, scheduledExecutorServiceFactory = { _, _, _ -> mockScheduledExecutorService } ) whenever(appContext.mockInstance.getSystemService(Context.CONNECTIVITY_SERVICE)) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt index d25b9fe7b7..1f69761c8e 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt @@ -6,8 +6,10 @@ package com.datadog.android.core.internal.data.upload +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -23,6 +25,8 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.net.InetAddress @@ -41,16 +45,24 @@ internal class RotatingDnsResolverTest { @Mock lateinit var mockDelegate: Dns + @Mock + lateinit var mockTimeProvider: TimeProvider + @StringForgery lateinit var fakeHostname: String + @LongForgery(min = 0L) + var fakeElapsedTimeNs: Long = 0L + lateinit var fakeInetAddresses: List @BeforeEach fun `set up`(forge: Forge) { fakeInetAddresses = forge.aList { mock() } - testedDns = RotatingDnsResolver(mockDelegate, TEST_TTL_MS) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeElapsedTimeNs + + testedDns = RotatingDnsResolver(mockDelegate, TEST_TTL_MS, mockTimeProvider) } @Test @@ -72,12 +84,13 @@ internal class RotatingDnsResolverTest { val result = mutableListOf() // When - fakeInetAddresses.forEach { + fakeInetAddresses.forEach { _ -> result.add(testedDns.lookup(fakeHostname).first()) } // Then assertThat(result).containsExactlyElementsOf(fakeInetAddresses) + verify(mockDelegate).lookup(fakeHostname) } @Test @@ -90,12 +103,14 @@ internal class RotatingDnsResolverTest { // When val result = testedDns.lookup(fakeHostname) - Thread.sleep(TEST_TTL_MS.inWholeMilliseconds) + val fakeExpiredTime = fakeElapsedTimeNs + TEST_TTL_MS.inWholeNanoseconds + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime val result2 = testedDns.lookup(fakeHostname) // Then assertThat(result).containsExactlyElementsOf(fakeInetAddresses) assertThat(result2).containsExactlyElementsOf(fakeInetAddresses2) + verify(mockDelegate, times(2)).lookup(fakeHostname) } @Test @@ -119,14 +134,15 @@ internal class RotatingDnsResolverTest { // the real use case where we have a small number of addresses to rotate fakeInetAddresses = forge.aList(size = forge.anInt(min = 1, max = 3)) { mock() } whenever(mockDelegate.lookup(fakeHostname)) doReturn fakeInetAddresses - // just wait the TTL time to make sure all threads are concurrently accessing the lookup - Thread.sleep(TEST_TTL_MS.inWholeMilliseconds) + + val fakeExpiredTime = fakeElapsedTimeNs + TEST_TTL_MS.inWholeNanoseconds + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).doReturn(fakeElapsedTimeNs, fakeExpiredTime) + var exceptionThrown: Exception? = null // When List(100) { Thread { - Thread.sleep(forge.aLong(min = 0, max = 100)) try { testedDns.lookup(fakeHostname) } catch (e: Exception) { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt index 5e0a9a4b48..3ff98bd2b8 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt @@ -15,6 +15,7 @@ import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -55,9 +56,10 @@ internal class BatchMetricsDispatcherTest { lateinit var fakeUploadConfiguration: DataUploadConfiguration @Mock - lateinit var mockDateTimeProvider: TimeProvider + lateinit var mockTimeProvider: TimeProvider - private var currentTimeInMillis: Long = System.currentTimeMillis() + @LongForgery(min = 100001, max = Long.MAX_VALUE / 4) + var fakeTimestamp: Long = 0L @Mock lateinit var mockInternalLogger: InternalLogger @@ -79,13 +81,13 @@ internal class BatchMetricsDispatcherTest { Feature.SESSION_REPLAY_RESOURCES_FEATURE_NAME ) ) - whenever(mockDateTimeProvider.getDeviceTimestamp()).doReturn(currentTimeInMillis) + whenever(mockTimeProvider.getDeviceTimestamp()).doReturn(fakeTimestamp) testedBatchMetricsDispatcher = BatchMetricsDispatcher( fakeFeatureName, fakeUploadConfiguration, fakeFilePersistenceConfig, mockInternalLogger, - mockDateTimeProvider + mockTimeProvider ) } @@ -118,7 +120,7 @@ internal class BatchMetricsDispatcherTest { // Given val fakeReason = forge.forgeIncludeInMetricReason() val fakeFile: File = forge.forgeValidFile() - val newFileName = (currentTimeInMillis + forge.aLong(min = 100, max = 1000)).toString() + val newFileName = (fakeTimestamp + forge.aLong(min = 100, max = 1000)).toString() whenever(fakeFile.name).thenReturn(newFileName) // When @@ -298,7 +300,7 @@ internal class BatchMetricsDispatcherTest { fakeUploadConfiguration, fakeFilePersistenceConfig, mockInternalLogger, - mockDateTimeProvider + mockTimeProvider ) val fakeReason: RemovalReason.Flushed = forge.getForgery() val fakeFile: File = forge.forgeValidFile() @@ -538,7 +540,7 @@ internal class BatchMetricsDispatcherTest { fakeUploadConfiguration, fakeFilePersistenceConfig, mockInternalLogger, - mockDateTimeProvider + mockTimeProvider ) val fakeFile: File = forge.forgeValidClosedFile() @@ -553,7 +555,7 @@ internal class BatchMetricsDispatcherTest { return mutableMapOf( BatchMetricsDispatcher.TYPE_KEY to BatchMetricsDispatcher.BATCH_DELETED_TYPE_VALUE, BatchMetricsDispatcher.TRACK_KEY to resolveTrackName(fakeFeatureName), - BatchMetricsDispatcher.BATCH_AGE_KEY to max(0, (currentTimeInMillis - file.name.toLong())), + BatchMetricsDispatcher.BATCH_AGE_KEY to max(0, (fakeTimestamp - file.name.toLong())), BatchMetricsDispatcher.UPLOADER_WINDOW_KEY to fakeFilePersistenceConfig.recentDelayMs, BatchMetricsDispatcher.UPLOADER_DELAY_KEY to mapOf( @@ -589,7 +591,7 @@ internal class BatchMetricsDispatcherTest { } private fun Forge.forgeValidFile(): File { - val fileNameAsLong = currentTimeInMillis - aLong(min = 1000, max = 100000) + val fileNameAsLong = fakeTimestamp - aLong(min = 1000, max = 100000) val fileLength = aPositiveLong() val dirName = forgeAGrantedDirName() val parentDirectory: File = mock { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt index 2485f16e0d..b0ee3395f1 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt @@ -17,9 +17,11 @@ import com.datadog.android.core.internal.metrics.MethodCalledTelemetry.Companion import com.datadog.android.core.internal.metrics.MethodCalledTelemetry.Companion.METRIC_TYPE_VALUE import com.datadog.android.core.internal.metrics.MethodCalledTelemetry.Companion.OPERATION_NAME import com.datadog.android.core.metrics.PerformanceMetric.Companion.METRIC_TYPE +import com.datadog.android.internal.time.TimeProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -61,6 +63,9 @@ internal class MethodCalledTelemetryTest { @Mock lateinit var mockDeviceInfo: DeviceInfo + @Mock + lateinit var mockTimeProvider: TimeProvider + @StringForgery lateinit var fakeDeviceModel: String @@ -85,7 +90,11 @@ internal class MethodCalledTelemetryTest { @FloatForgery(min = 0.1f, max = 100f) private var fakeCreationSampleRate: Float = 0.1f - private var fakeStartTime: Long = 0 + @LongForgery(min = 0L) + private var fakeStartTimeNs: Long = 0L + + @LongForgery(min = 0L) + private var fakeElapsedTimeNs: Long = 0L private var fakeStatus: Boolean = false @@ -105,13 +114,16 @@ internal class MethodCalledTelemetryTest { whenever(mockDeviceInfo.osVersion).thenReturn(fakeOsVersion) whenever(mockDeviceInfo.deviceBuildId).thenReturn(fakeOsBuild) - fakeStartTime = System.nanoTime() + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeStartTimeNs) + .thenReturn(fakeStartTimeNs + fakeElapsedTimeNs) + testedMethodCalledTelemetry = MethodCalledTelemetry( internalLogger = mockInternalLogger, operationName = fakeOperationName, callerClass = fakeCallerClass, - startTime = fakeStartTime, - creationSampleRate = fakeCreationSampleRate + creationSampleRate = fakeCreationSampleRate, + timeProvider = mockTimeProvider ) } @@ -137,7 +149,7 @@ internal class MethodCalledTelemetryTest { verify(mockInternalLogger).logMetric(any(), mapCaptor.capture(), eq(100.0f), eq(fakeCreationSampleRate)) val executionTime = mapCaptor.firstValue[EXECUTION_TIME] as Long - assertThat(executionTime).isLessThan(System.nanoTime() - fakeStartTime) + assertThat(executionTime).isEqualTo(fakeElapsedTimeNs) } @Test diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigratorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigratorTest.kt index a2697700e7..7e569e9990 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigratorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/ConsentAwareFileMigratorTest.kt @@ -6,7 +6,6 @@ package com.datadog.android.core.internal.persistence.file.advanced -import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover import com.datadog.android.core.internal.persistence.file.FileOrchestrator import com.datadog.android.privacy.TrackingConsent @@ -23,6 +22,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -48,14 +48,12 @@ internal class ConsentAwareFileMigratorTest { @Mock lateinit var mockFileMover: FileMover - @Mock - lateinit var mockInternalLogger: InternalLogger - @BeforeEach fun `set up`() { testedMigrator = ConsentAwareFileMigrator( mockFileMover, - mockInternalLogger + mock(), + mock() ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestratorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestratorTest.kt index 27fcecabbf..651b1234ce 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestratorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/FeatureFileOrchestratorTest.kt @@ -11,6 +11,7 @@ import com.datadog.android.core.internal.metrics.MetricsDispatcher import com.datadog.android.core.internal.persistence.file.FilePersistenceConfig import com.datadog.android.core.internal.persistence.file.batch.BatchFileOrchestrator import com.datadog.android.core.internal.privacy.ConsentProvider +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.privacy.TrackingConsent import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery @@ -64,6 +65,9 @@ internal class FeatureFileOrchestratorTest { @Mock lateinit var mockMetricsDispatcher: MetricsDispatcher + @Mock + lateinit var mockTimeProvider: TimeProvider + @BeforeEach fun `set up`() { whenever(mockConsentProvider.getConsent()) doReturn fakeConsent @@ -81,7 +85,8 @@ internal class FeatureFileOrchestratorTest { mockExecutorService, fakeFilePersistenceConfig, mockInternalLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + mockTimeProvider ) // Then @@ -103,7 +108,8 @@ internal class FeatureFileOrchestratorTest { mockExecutorService, fakeFilePersistenceConfig, mockInternalLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + mockTimeProvider ) // Then @@ -125,7 +131,8 @@ internal class FeatureFileOrchestratorTest { mockExecutorService, fakeFilePersistenceConfig, mockInternalLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + mockTimeProvider ) // Then diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt index 4f93549804..d39904ee21 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt @@ -8,8 +8,10 @@ package com.datadog.android.core.internal.persistence.file.advanced import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -28,6 +30,8 @@ import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.io.File +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong import kotlin.system.measureTimeMillis @Extensions( @@ -51,13 +55,25 @@ internal class MoveDataMigrationOperationTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeElapsedTimeNs: Long = 0L + @BeforeEach fun `set up`() { + val currentTime = AtomicLong(fakeElapsedTimeNs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + currentTime.getAndAdd(RETRY_DELAY_NS) + } + testedOperation = MoveDataMigrationOperation( fakeFromDirectory, fakeToDirectory, mockFileMover, - mockInternalLogger + mockInternalLogger, + mockTimeProvider ) } @@ -68,7 +84,8 @@ internal class MoveDataMigrationOperationTest { null, fakeToDirectory, mockFileMover, - mockInternalLogger + mockInternalLogger, + mockTimeProvider ) // When @@ -90,7 +107,8 @@ internal class MoveDataMigrationOperationTest { fakeFromDirectory, null, mockFileMover, - mockInternalLogger + mockInternalLogger, + mockTimeProvider ) whenever(mockFileMover.delete(fakeFromDirectory)) doReturn true @@ -132,7 +150,7 @@ internal class MoveDataMigrationOperationTest { } @Test - fun `M retry with 500ms delay W run() {move fails once}`() { + fun `M not wait for real delay W run() {move fails once, time provider mocked}`() { // Given whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false, true) @@ -144,7 +162,7 @@ internal class MoveDataMigrationOperationTest { // Then verify(mockFileMover, times(2)).moveFiles(fakeFromDirectory, fakeToDirectory) - assertThat(duration).isBetween(500L, 550L) + assertThat(duration).isLessThan(100L) } @Test @@ -161,7 +179,7 @@ internal class MoveDataMigrationOperationTest { } @Test - fun `M retry with 500ms delay W run() {move always fails}`() { + fun `M not wait for real delay W run() {move always fails, time provider mocked}`() { // Given whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false) @@ -173,6 +191,10 @@ internal class MoveDataMigrationOperationTest { // Then verify(mockFileMover, times(3)).moveFiles(fakeFromDirectory, fakeToDirectory) - assertThat(duration).isBetween(1000L, 1100L) + assertThat(duration).isLessThan(100L) + } + + companion object { + private val RETRY_DELAY_NS = TimeUnit.MILLISECONDS.toNanos(500) } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt index f6a9dbab4b..65343f0ca4 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt @@ -8,8 +8,10 @@ package com.datadog.android.core.internal.persistence.file.advanced import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.persistence.file.FileMover +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -28,6 +30,8 @@ import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.io.File +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong import kotlin.system.measureTimeMillis @Extensions( @@ -49,12 +53,24 @@ internal class WipeDataMigrationOperationTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeElapsedTimeNs: Long = 0L + @BeforeEach fun `set up`() { + val currentTime = AtomicLong(fakeElapsedTimeNs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + currentTime.getAndAdd(RETRY_DELAY_NS) + } + testedOperation = WipeDataMigrationOperation( fakeTargetDirectory, mockFileMover, - mockInternalLogger + mockInternalLogger, + mockTimeProvider ) } @@ -64,7 +80,8 @@ internal class WipeDataMigrationOperationTest { testedOperation = WipeDataMigrationOperation( null, mockFileMover, - mockInternalLogger + mockInternalLogger, + mockTimeProvider ) // When @@ -117,7 +134,7 @@ internal class WipeDataMigrationOperationTest { } @Test - fun `M retry with 500ms delay W run() {move always fails}`() { + fun `M not wait for real delay W run() {move always fails, time provider mocked}`() { // Given whenever(mockFileMover.delete(fakeTargetDirectory)) .doReturn(false) @@ -129,6 +146,10 @@ internal class WipeDataMigrationOperationTest { // Then verify(mockFileMover, times(3)).delete(fakeTargetDirectory) - assertThat(duration).isBetween(1000L, 1100L) + assertThat(duration).isLessThan(100L) + } + + companion object { + private val RETRY_DELAY_NS = TimeUnit.MILLISECONDS.toNanos(500) } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt index 8d83ceeee4..e394540762 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt @@ -14,6 +14,7 @@ import com.datadog.android.core.internal.persistence.file.FileOrchestrator import com.datadog.android.core.internal.persistence.file.FilePersistenceConfig import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog +import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -35,7 +36,6 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions @@ -77,10 +77,16 @@ internal class BatchFileOrchestratorTest { @Mock lateinit var mockPendingFiles: AtomicInteger + @LongForgery(min = 0L) + var fakeTimestamp: Long = 0L + + lateinit var stubTimeProvider: StubTimeProvider + @BeforeEach fun `set up`() { whenever(mockPendingFiles.decrementAndGet()).thenReturn(fakePendingBatches) whenever(mockPendingFiles.incrementAndGet()).thenReturn(fakePendingBatches) + stubTimeProvider = StubTimeProvider(deviceTimestampMs = fakeTimestamp) fakeRootDir = File(tempDir, fakeRootDirName) fakeRootDir.mkdirs() testedOrchestrator = BatchFileOrchestrator( @@ -88,7 +94,8 @@ internal class BatchFileOrchestratorTest { config = TEST_PERSISTENCE_CONFIG, internalLogger = mockLogger, metricsDispatcher = mockMetricsDispatcher, - pendingFiles = mockPendingFiles + pendingFiles = mockPendingFiles, + timeProvider = stubTimeProvider ) } @@ -110,10 +117,9 @@ internal class BatchFileOrchestratorTest { @Test fun `M send batch_closed metric W getWritableFile()`() { // Given - val lowerTimestamp = System.currentTimeMillis() + val fileCreationTimestamp = stubTimeProvider.deviceTimestampMs val oldFile = testedOrchestrator.getWritableFile() - val upperTimestamp = System.currentTimeMillis() - Thread.sleep(RECENT_DELAY_MS + 1) + stubTimeProvider.deviceTimestampMs += RECENT_DELAY_MS + 1 // When testedOrchestrator.getWritableFile() @@ -129,8 +135,7 @@ internal class BatchFileOrchestratorTest { assertThat(fileArgumentCaptor.firstValue).isEqualTo(oldFile) metadataArgumentCaptor.firstValue.let { assertThat(it.eventsCount).isEqualTo(1L) - assertThat(it.lastTimeWasUsedInMs) - .isBetween(lowerTimestamp, upperTimestamp) + assertThat(it.lastTimeWasUsedInMs).isEqualTo(fileCreationTimestamp) } verifyNoMoreInteractions(mockMetricsDispatcher) } @@ -146,7 +151,8 @@ internal class BatchFileOrchestratorTest { notADir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -173,7 +179,8 @@ internal class BatchFileOrchestratorTest { corruptedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -201,7 +208,8 @@ internal class BatchFileOrchestratorTest { restrictedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -236,27 +244,25 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val oldTimestamp = System.currentTimeMillis() - oldFileAge + val currentTime = stubTimeProvider.deviceTimestampMs + val oldTimestamp = currentTime - oldFileAge val oldFile = File(fakeRootDir, oldTimestamp.toString()) oldFile.createNewFile() val oldFileMeta = File("${oldFile.path}_metadata") oldFileMeta.createNewFile() - val youngTimestamp = System.currentTimeMillis() - RECENT_DELAY_MS - 1 + val youngTimestamp = currentTime - RECENT_DELAY_MS - 1 val youngFile = File(fakeRootDir, youngTimestamp.toString()) youngFile.createNewFile() // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(currentTime) assertThat(oldFile).doesNotExist() assertThat(oldFileMeta).doesNotExist() assertThat(youngFile).exists() @@ -274,19 +280,18 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val oldTimestamp = System.currentTimeMillis() - oldFileAge + val currentTime = stubTimeProvider.deviceTimestampMs + val oldTimestamp = currentTime - oldFileAge val oldFile = File(fakeRootDir, oldTimestamp.toString()) oldFile.createNewFile() val oldFileMeta = File("${oldFile.path}_metadata") oldFileMeta.createNewFile() - val youngTimestamp = System.currentTimeMillis() - RECENT_DELAY_MS - 1 + val youngTimestamp = currentTime - RECENT_DELAY_MS - 1 val youngFile = File(fakeRootDir, youngTimestamp.toString()) youngFile.createNewFile() // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // let's add very old file after the previous cleanup call. If threshold is respected, // cleanup shouldn't be performed during the next getWritableFile call val evenOlderFile = File(fakeRootDir, (oldTimestamp - 1).toString()) @@ -298,8 +303,7 @@ internal class BatchFileOrchestratorTest { assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(currentTime) assertThat(oldFile).doesNotExist() assertThat(oldFileMeta).doesNotExist() assertThat(youngFile).exists() @@ -317,17 +321,16 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val oldTimestamp = System.currentTimeMillis() - oldFileAge + val currentTime = stubTimeProvider.deviceTimestampMs + val oldTimestamp = currentTime - oldFileAge val oldFile = File(fakeRootDir, oldTimestamp.toString()) oldFile.createNewFile() val oldFileMeta = File("${oldFile.path}_metadata") oldFileMeta.createNewFile() // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() - Thread.sleep(CLEANUP_FREQUENCY_THRESHOLD_MS + 1) + stubTimeProvider.deviceTimestampMs += CLEANUP_FREQUENCY_THRESHOLD_MS + 1 val evenOlderFile = File(fakeRootDir, (oldTimestamp - 1).toString()) evenOlderFile.createNewFile() testedOrchestrator.getWritableFile() @@ -337,8 +340,7 @@ internal class BatchFileOrchestratorTest { assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(currentTime) assertThat(oldFile).doesNotExist() assertThat(oldFileMeta).doesNotExist() assertThat(evenOlderFile).doesNotExist() @@ -352,14 +354,13 @@ internal class BatchFileOrchestratorTest { argThat { this is RemovalReason.Obsolete }, eq(fakePendingBatches) ) - argumentCaptor() { + argumentCaptor { verify(mockMetricsDispatcher).sendBatchClosedMetric( eq(result), capture() ) assertThat(firstValue.eventsCount).isEqualTo(1L) - assertThat(firstValue.lastTimeWasUsedInMs) - .isBetween(start, end) + assertThat(firstValue.lastTimeWasUsedInMs).isEqualTo(currentTime) } verifyNoMoreInteractions(mockMetricsDispatcher) } @@ -368,19 +369,17 @@ internal class BatchFileOrchestratorTest { fun `M return new File W getWritableFile() {no available file}`() { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) + val currentTime = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(currentTime) verifyNoInteractions(mockMetricsDispatcher) } @@ -393,7 +392,7 @@ internal class BatchFileOrchestratorTest { val previousFile = testedOrchestrator.getWritableFile() checkNotNull(previousFile) previousFile.writeText(previousData) - Thread.sleep(1) + stubTimeProvider.deviceTimestampMs += 1 // When val result = testedOrchestrator.getWritableFile() @@ -411,31 +410,27 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val beforeFileCreateTimestamp = System.currentTimeMillis() + val fileCreateTimestamp = stubTimeProvider.deviceTimestampMs val previousFile = testedOrchestrator.getWritableFile() - val afterFileCreateTimestamp = System.currentTimeMillis() checkNotNull(previousFile) previousFile.writeText(previousData) - Thread.sleep(RECENT_DELAY_MS + 1) + stubTimeProvider.deviceTimestampMs += RECENT_DELAY_MS + 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(newFileTimestamp) assertThat(previousFile.readText()).isEqualTo(previousData) - argumentCaptor() { + argumentCaptor { verify(mockMetricsDispatcher).sendBatchClosedMetric(eq(previousFile), capture()) - assertThat(firstValue.lastTimeWasUsedInMs) - .isBetween(beforeFileCreateTimestamp, afterFileCreateTimestamp) + assertThat(firstValue.lastTimeWasUsedInMs).isEqualTo(fileCreateTimestamp) assertThat(firstValue.eventsCount).isEqualTo(1L) } verifyNoMoreInteractions(mockMetricsDispatcher) @@ -447,22 +442,21 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val previousFile = File(fakeRootDir, System.currentTimeMillis().toString()) + val currentTime = stubTimeProvider.deviceTimestampMs + val previousFile = File(fakeRootDir, currentTime.toString()) previousFile.writeText(previousData) - Thread.sleep(1) + stubTimeProvider.deviceTimestampMs += 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(newFileTimestamp) assertThat(previousFile.readText()).isEqualTo(previousData) verifyNoInteractions(mockMetricsDispatcher) } @@ -471,31 +465,27 @@ internal class BatchFileOrchestratorTest { fun `M return new File W getWritableFile() {previous file is deleted}`() { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val beforeFileCreateTimestamp = System.currentTimeMillis() + val fileCreateTimestamp = stubTimeProvider.deviceTimestampMs val previousFile = testedOrchestrator.getWritableFile() - val afterFileCreateTimestamp = System.currentTimeMillis() checkNotNull(previousFile) previousFile.createNewFile() previousFile.delete() - Thread.sleep(1) + stubTimeProvider.deviceTimestampMs += 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(newFileTimestamp) assertThat(previousFile).doesNotExist() - argumentCaptor() { + argumentCaptor { verify(mockMetricsDispatcher).sendBatchClosedMetric(eq(previousFile), capture()) - assertThat(firstValue.lastTimeWasUsedInMs) - .isBetween(beforeFileCreateTimestamp, afterFileCreateTimestamp) + assertThat(firstValue.lastTimeWasUsedInMs).isEqualTo(fileCreateTimestamp) assertThat(firstValue.eventsCount).isEqualTo(1L) } verifyNoMoreInteractions(mockMetricsDispatcher) @@ -507,30 +497,26 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val beforeFileCreateTimestamp = System.currentTimeMillis() + val fileCreateTimestamp = stubTimeProvider.deviceTimestampMs val previousFile = testedOrchestrator.getWritableFile() - val afterFileCreateTimestamp = System.currentTimeMillis() checkNotNull(previousFile) previousFile.writeText(previousData) - Thread.sleep(1) + stubTimeProvider.deviceTimestampMs += 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(newFileTimestamp) assertThat(previousFile.readText()).isEqualTo(previousData) - argumentCaptor() { + argumentCaptor { verify(mockMetricsDispatcher).sendBatchClosedMetric(eq(previousFile), capture()) - assertThat(firstValue.lastTimeWasUsedInMs) - .isBetween(beforeFileCreateTimestamp, afterFileCreateTimestamp) + assertThat(firstValue.lastTimeWasUsedInMs).isEqualTo(fileCreateTimestamp) assertThat(firstValue.eventsCount).isEqualTo(1L) } verifyNoMoreInteractions(mockMetricsDispatcher) @@ -542,7 +528,7 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val beforeFileCreateTimestamp = System.currentTimeMillis() + val fileCreateTimestamp = stubTimeProvider.deviceTimestampMs var previousFile = testedOrchestrator.getWritableFile() repeat(4) { @@ -559,27 +545,26 @@ internal class BatchFileOrchestratorTest { assumeTrue(file == previousFile) file?.appendText(previousData[i]) } - val afterLastFileUsageTimestamp = System.currentTimeMillis() + val lastFileUsageTimestamp = stubTimeProvider.deviceTimestampMs // When - val start = System.currentTimeMillis() + stubTimeProvider.deviceTimestampMs += 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs val nextFile = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(nextFile) assertThat(nextFile) .doesNotExist() .hasParent(fakeRootDir) - assertThat(nextFile.name.toLong()) - .isBetween(start, end) + assertThat(nextFile.name.toLong()).isEqualTo(newFileTimestamp) assertThat(previousFile?.readText()) .isEqualTo(previousData.joinToString(separator = "")) - argumentCaptor() { + argumentCaptor { verify(mockMetricsDispatcher).sendBatchClosedMetric(eq(previousFile!!), capture()) assertThat(firstValue.lastTimeWasUsedInMs) - .isBetween(beforeFileCreateTimestamp, afterLastFileUsageTimestamp) + .isBetween(fileCreateTimestamp, lastFileUsageTimestamp) assertThat(firstValue.eventsCount).isEqualTo(MAX_ITEM_PER_BATCH.toLong()) } previousFile = nextFile @@ -598,23 +583,21 @@ internal class BatchFileOrchestratorTest { val file = testedOrchestrator.getWritableFile() checkNotNull(file) file.writeText(previousData) - Thread.sleep(1) + stubTimeProvider.deviceTimestampMs += 1 file } // When - Thread.sleep(CLEANUP_FREQUENCY_THRESHOLD_MS + 1) - val start = System.currentTimeMillis() + stubTimeProvider.deviceTimestampMs += CLEANUP_FREQUENCY_THRESHOLD_MS + 1 + val newFileTimestamp = stubTimeProvider.deviceTimestampMs val result = testedOrchestrator.getWritableFile() - val end = System.currentTimeMillis() // Then checkNotNull(result) assertThat(result) .doesNotExist() .hasParent(fakeRootDir) - assertThat(result.name.toLong()) - .isBetween(start, end) + assertThat(result.name.toLong()).isEqualTo(newFileTimestamp) assertThat(files.first()).doesNotExist() mockLogger.verifyLog( InternalLogger.Level.ERROR, @@ -643,7 +626,8 @@ internal class BatchFileOrchestratorTest { notADir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -669,7 +653,8 @@ internal class BatchFileOrchestratorTest { corruptedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -696,7 +681,8 @@ internal class BatchFileOrchestratorTest { restrictedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -717,12 +703,13 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val oldTimestamp = System.currentTimeMillis() - oldFileAge + val currentTime = stubTimeProvider.deviceTimestampMs + val oldTimestamp = currentTime - oldFileAge val oldFile = File(fakeRootDir, oldTimestamp.toString()) oldFile.createNewFile() val oldFileMeta = File("${oldFile.path}_metadata") oldFileMeta.createNewFile() - val youngTimestamp = System.currentTimeMillis() - RECENT_DELAY_MS - 1 + val youngTimestamp = currentTime - RECENT_DELAY_MS - 1 val youngFile = File(fakeRootDir, youngTimestamp.toString()) youngFile.createNewFile() @@ -764,7 +751,8 @@ internal class BatchFileOrchestratorTest { fun `M return file W getReadableFile() {existing old enough file}`() { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val timestamp = System.currentTimeMillis() - (RECENT_DELAY_MS * 2) + val currentTime = stubTimeProvider.deviceTimestampMs + val timestamp = currentTime - (RECENT_DELAY_MS * 2) val file = File(fakeRootDir, timestamp.toString()) file.createNewFile() @@ -782,7 +770,8 @@ internal class BatchFileOrchestratorTest { fun `M return null W getReadableFile() {file is too recent}`() { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val timestamp = System.currentTimeMillis() - (RECENT_DELAY_MS / 2) + val currentTime = stubTimeProvider.deviceTimestampMs + val timestamp = currentTime - (RECENT_DELAY_MS / 2) val file = File(fakeRootDir, timestamp.toString()) file.createNewFile() @@ -797,7 +786,8 @@ internal class BatchFileOrchestratorTest { fun `M return null W getReadableFile() {file is in exclude list}`() { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val timestamp = System.currentTimeMillis() - (RECENT_DELAY_MS * 2) + val currentTime = stubTimeProvider.deviceTimestampMs + val timestamp = currentTime - (RECENT_DELAY_MS * 2) val file = File(fakeRootDir, timestamp.toString()) file.createNewFile() @@ -823,7 +813,8 @@ internal class BatchFileOrchestratorTest { notADir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -849,7 +840,8 @@ internal class BatchFileOrchestratorTest { corruptedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -876,7 +868,8 @@ internal class BatchFileOrchestratorTest { restrictedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -922,8 +915,9 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val old = System.currentTimeMillis() - (RECENT_DELAY_MS * 2) - val new = System.currentTimeMillis() - (RECENT_DELAY_MS / 2) + val currentTime = stubTimeProvider.deviceTimestampMs + val old = currentTime - (RECENT_DELAY_MS * 2) + val new = currentTime - (RECENT_DELAY_MS / 2) val expectedFiles = mutableListOf() for (i in 1..count) { // create both non readable and non writable files @@ -968,8 +962,9 @@ internal class BatchFileOrchestratorTest { ) { // Given assumeTrue(fakeRootDir.listFiles().isNullOrEmpty()) - val old = System.currentTimeMillis() - (RECENT_DELAY_MS * 2) - val new = System.currentTimeMillis() - (RECENT_DELAY_MS / 2) + val currentTime = stubTimeProvider.deviceTimestampMs + val old = currentTime - (RECENT_DELAY_MS * 2) + val new = currentTime - (RECENT_DELAY_MS / 2) val expectedFiles = mutableListOf() for (i in 1..count) { // create both non readable and non writable files @@ -1015,7 +1010,8 @@ internal class BatchFileOrchestratorTest { notADir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -1041,7 +1037,8 @@ internal class BatchFileOrchestratorTest { corruptedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -1068,7 +1065,8 @@ internal class BatchFileOrchestratorTest { restrictedDir, TEST_PERSISTENCE_CONFIG, mockLogger, - mockMetricsDispatcher + mockMetricsDispatcher, + stubTimeProvider ) // When @@ -1144,7 +1142,7 @@ internal class BatchFileOrchestratorTest { @Test fun `M return metadata file W getMetadataFile()`() { // Given - val fakeFileName = System.currentTimeMillis().toString() + val fakeFileName = stubTimeProvider.deviceTimestampMs.toString() val fakeFile = File(fakeRootDir.path, fakeFileName) // When @@ -1160,7 +1158,7 @@ internal class BatchFileOrchestratorTest { @StringForgery fakeSuffix: String ) { // Given - val fakeFileName = System.currentTimeMillis().toString() + val fakeFileName = stubTimeProvider.deviceTimestampMs.toString() val fakeFile = File("${fakeRootDir.parent}$fakeSuffix", fakeFileName) // When diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/AbstractExecutorServiceTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/AbstractExecutorServiceTest.kt index 904015772c..2a8ba350c4 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/AbstractExecutorServiceTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/AbstractExecutorServiceTest.kt @@ -9,6 +9,7 @@ package com.datadog.android.core.internal.thread import com.datadog.android.api.InternalLogger import com.datadog.android.core.configuration.BackPressureMitigation import com.datadog.android.core.configuration.BackPressureStrategy +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.forge.aThrowable @@ -26,6 +27,7 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.mock import org.mockito.kotlin.verifyNoInteractions import org.mockito.quality.Strictness import java.util.concurrent.CancellationException @@ -66,7 +68,7 @@ internal abstract class AbstractExecutorServiceTest { mockOnItemDropped, fakeBackPressureMitigation ) - testedExecutor = createTestedExecutorService(forge, fakeBackpressureStrategy) + testedExecutor = createTestedExecutorService(forge, fakeBackpressureStrategy, mock()) } @AfterEach @@ -74,7 +76,11 @@ internal abstract class AbstractExecutorServiceTest { testedExecutor.shutdownNow() } - abstract fun createTestedExecutorService(forge: Forge, backPressureStrategy: BackPressureStrategy): T + abstract fun createTestedExecutorService( + forge: Forge, + backPressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider + ): T // region execute diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorServiceTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorServiceTest.kt index 93b01d2159..f08e004f0d 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorServiceTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/BackPressureExecutorServiceTest.kt @@ -7,6 +7,7 @@ package com.datadog.android.core.internal.thread import com.datadog.android.core.configuration.BackPressureStrategy +import com.datadog.android.internal.time.TimeProvider import fr.xgouchet.elmyr.Forge import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -16,12 +17,14 @@ internal class BackPressureExecutorServiceTest : override fun createTestedExecutorService( forge: Forge, - backPressureStrategy: BackPressureStrategy + backPressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider ): BackPressureExecutorService { return BackPressureExecutorService( mockInternalLogger, forge.anAlphabeticalString(), - backPressureStrategy + backPressureStrategy, + timeProvider ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/DropOldestBackPressuredBlockingQueueTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/DropOldestBackPressuredBlockingQueueTest.kt index 313662a56f..2d443f3924 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/DropOldestBackPressuredBlockingQueueTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/DropOldestBackPressuredBlockingQueueTest.kt @@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.quality.Strictness @@ -64,7 +65,8 @@ class DropOldestBackPressuredBlockingQueueTest { mockOnThresholdReached, mockOnItemsDropped, BackPressureMitigation.DROP_OLDEST - ) + ), + timeProvider = mock() ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/IgnoreNewestBackPressuredBlockingQueueTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/IgnoreNewestBackPressuredBlockingQueueTest.kt index bafa652a60..936b03b7a1 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/IgnoreNewestBackPressuredBlockingQueueTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/IgnoreNewestBackPressuredBlockingQueueTest.kt @@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.quality.Strictness @@ -64,7 +65,8 @@ class IgnoreNewestBackPressuredBlockingQueueTest { mockOnThresholdReached, mockOnItemsDropped, BackPressureMitigation.IGNORE_NEWEST - ) + ), + timeProvider = mock() ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/LoggingScheduledThreadPoolExecutorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/LoggingScheduledThreadPoolExecutorTest.kt index 003bb357bd..8431900ff9 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/LoggingScheduledThreadPoolExecutorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/LoggingScheduledThreadPoolExecutorTest.kt @@ -8,6 +8,7 @@ package com.datadog.android.core.internal.thread import com.datadog.android.api.InternalLogger import com.datadog.android.core.configuration.BackPressureStrategy +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.forge.aThrowable import fr.xgouchet.elmyr.Forge @@ -23,7 +24,8 @@ internal class LoggingScheduledThreadPoolExecutorTest : override fun createTestedExecutorService( forge: Forge, - backPressureStrategy: BackPressureStrategy + backPressureStrategy: BackPressureStrategy, + timeProvider: TimeProvider ): ScheduledThreadPoolExecutor { return LoggingScheduledThreadPoolExecutor( 1, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/NotifyOnlyUnboundedBackPressuredBlockingQueueTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/NotifyOnlyUnboundedBackPressuredBlockingQueueTest.kt index 8612705920..cf8a0dbc2e 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/NotifyOnlyUnboundedBackPressuredBlockingQueueTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/NotifyOnlyUnboundedBackPressuredBlockingQueueTest.kt @@ -22,6 +22,7 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.quality.Strictness @@ -61,7 +62,8 @@ class NotifyOnlyUnboundedBackPressuredBlockingQueueTest { capacity = Int.MAX_VALUE, onThresholdReached = mockOnThresholdReached, onItemDropped = mockOnItemsDropped, - backpressureMitigation = null + backpressureMitigation = null, + timeProvider = mock() ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableBlockingQueueTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueueTest.kt similarity index 92% rename from dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableBlockingQueueTest.kt rename to dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueueTest.kt index 73942b7de3..b7aef26808 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableBlockingQueueTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ObservableLinkedBlockingQueueTest.kt @@ -26,7 +26,7 @@ import org.mockito.quality.Strictness ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -class ObservableBlockingQueueTest { +class ObservableLinkedBlockingQueueTest { @Test fun `M return only once non-null map W try to dump multiple times in dump interval`( @@ -39,18 +39,17 @@ class ObservableBlockingQueueTest { val fakeThirdTimestamp = fakeSecondTimestamp + forge.aLong(max = 1000) val fakeTimestamps = listOf(fakeFirstTimestamp, fakeSecondTimestamp, fakeThirdTimestamp).iterator() - val fakeTimeProvider: () -> Long = { fakeTimestamps.next() } val testedObservableLinkedBlockingQueue = - ObservableLinkedBlockingQueue(fakeItemCount + 1, fakeTimeProvider) + ObservableLinkedBlockingQueue(fakeItemCount + 1) repeat(fakeItemCount) { val mockItem = mock() testedObservableLinkedBlockingQueue.offer(mockItem) } // When - val firstMap = testedObservableLinkedBlockingQueue.dumpQueue() - val secondMap = testedObservableLinkedBlockingQueue.dumpQueue() - val thirdMap = testedObservableLinkedBlockingQueue.dumpQueue() + val firstMap = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamps.next()) + val secondMap = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamps.next()) + val thirdMap = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamps.next()) // Then assertThat(firstMap).isNotNull @@ -67,17 +66,16 @@ class ObservableBlockingQueueTest { val fakeFirstTimestamp = forge.aLong(min = 10000) val fakeSecondTimestamp = fakeFirstTimestamp + forge.aLong(min = 5000) val fakeTimestamps = listOf(fakeFirstTimestamp, fakeSecondTimestamp).iterator() - val fakeTimeProvider: () -> Long = { fakeTimestamps.next() } val testedObservableLinkedBlockingQueue = - ObservableLinkedBlockingQueue(fakeItemCount + 1, fakeTimeProvider) + ObservableLinkedBlockingQueue(fakeItemCount + 1) repeat(fakeItemCount) { val mockItem = mock() testedObservableLinkedBlockingQueue.offer(mockItem) } // When - val firstMap = testedObservableLinkedBlockingQueue.dumpQueue() - val secondMap = testedObservableLinkedBlockingQueue.dumpQueue() + val firstMap = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamps.next()) + val secondMap = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamps.next()) // Then assertThat(firstMap).isNotNull @@ -90,10 +88,10 @@ class ObservableBlockingQueueTest { ) { // Given val expectedMap = mutableMapOf() - val fakeTimeProvider: () -> Long = { forge.aLong(min = 10000L) } + val fakeTimestamp = forge.aLong(min = 10000L) val fakeRunnableCount = forge.anInt(min = 5, max = 100) val testedObservableLinkedBlockingQueue = - ObservableLinkedBlockingQueue(fakeRunnableCount + 1, fakeTimeProvider) + ObservableLinkedBlockingQueue(fakeRunnableCount + 1) val fakeRunnableTypeCount = forge.anInt(min = 1, max = fakeRunnableCount) val fakeRunnableTypes = mutableListOf() repeat(fakeRunnableTypeCount) { @@ -101,13 +99,13 @@ class ObservableBlockingQueueTest { } repeat(fakeRunnableCount) { val fakeName = forge.anElementFrom(fakeRunnableTypes) - val fakeNamedRunnable = NamedRunnable(fakeName, mock()) + val fakeNamedRunnable = NamedRunnable(fakeName, mock()) testedObservableLinkedBlockingQueue.offer(fakeNamedRunnable) expectedMap[fakeName] = (expectedMap[fakeName] ?: 0) + 1 } // When - val map = testedObservableLinkedBlockingQueue.dumpQueue() + val map = testedObservableLinkedBlockingQueue.dumpQueue(fakeTimestamp) // Then assertThat(map).isEqualTo(expectedMap) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt index f25b6dfe1b..b724abd74b 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt @@ -7,24 +7,24 @@ package com.datadog.android.core.internal.thread import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.LongForgery 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.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.ThreadPoolExecutor -import kotlin.system.measureTimeMillis +import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -40,6 +40,17 @@ internal class ThreadPoolExecutorExtTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeElapsedTimeNs: Long = 0L + + @BeforeEach + fun `set up`() { + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeElapsedTimeNs) + } + @Test fun `M return false W waitToIdle { timeout reached }`( @LongForgery(min = 0, max = 500) fakeTimeout: Long, @@ -50,9 +61,13 @@ internal class ThreadPoolExecutorExtTest { val fakeCompletedCount = forge.aLong(min = 0, max = fakeTaskCount - 1) whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount).thenReturn(fakeCompletedCount) + val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeElapsedTimeNs) + .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) testedMockExecutor.isIdle() // THEN @@ -60,7 +75,7 @@ internal class ThreadPoolExecutorExtTest { } @Test - fun `M wait max timeout milliseconds W waitToIdle { executor not idled }`( + fun `M exit loop after timeout W waitToIdle { executor not idled }`( @LongForgery(min = 500, max = 1000) fakeTimeout: Long, forge: Forge ) { @@ -69,14 +84,16 @@ internal class ThreadPoolExecutorExtTest { val fakeCompletedCount = forge.aLong(min = 0, max = fakeTaskCount - 1) whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount).thenReturn(fakeCompletedCount) + val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeElapsedTimeNs) + .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) // WHEN - val duration = measureTimeMillis { - testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) - } + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN - assertThat(duration).isCloseTo(fakeTimeout, Offset.offset(130)) + assertThat(isIdled).isFalse() } @Test @@ -91,7 +108,7 @@ internal class ThreadPoolExecutorExtTest { .thenReturn(fakeTaskCount) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN assertThat(isIdled).isTrue() @@ -110,9 +127,13 @@ internal class ThreadPoolExecutorExtTest { whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount) .thenReturn(fakeTaskCount / 2).thenReturn(fakeTaskCount) + val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeElapsedTimeNs) + .thenReturn(fakeElapsedTimeNs + timeoutNs / 2) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, internalLogger = mock()) + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN assertThat(isIdled).isTrue() @@ -123,12 +144,14 @@ internal class ThreadPoolExecutorExtTest { @LongForgery(min = Long.MIN_VALUE, max = 0) fakeTimeout: Long, forge: Forge ) { - // WHEN + // GIVEN val fakeTaskCount = forge.aLong(min = 2, max = 10) val fakeCompletedCount = forge.aLong(min = 0, max = fakeTaskCount - 1) whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount).thenReturn(fakeCompletedCount) - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) + + // WHEN + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN assertThat(isIdled).isFalse() @@ -145,7 +168,7 @@ internal class ThreadPoolExecutorExtTest { .thenReturn(fakeTaskCount) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN assertThat(isIdled).isTrue() @@ -166,9 +189,13 @@ internal class ThreadPoolExecutorExtTest { whenever(testedMockExecutor.completedTaskCount) .thenReturn(fakeTaskCount / 2) .thenReturn(fakeTaskCount + 2) + val fakeTimeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeElapsedTimeNs) + .thenReturn(fakeElapsedTimeNs + fakeTimeoutNs / 2) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger) + val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) // THEN assertThat(isIdled).isTrue() diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt index 791c437a8e..efe04f08ad 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt @@ -10,130 +10,156 @@ import android.os.Build import android.os.Process import android.os.SystemClock import com.datadog.android.core.internal.system.BuildSdkVersionProvider +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.DdRumContentProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.data.Offset import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.whenever import java.util.concurrent.TimeUnit @Extensions( + ExtendWith(MockitoExtension::class), ExtendWith(ForgeExtension::class) ) class DefaultAppStartTimeProviderTest { + + @Mock + private lateinit var mockTimeProvider: TimeProvider + + @Mock + private lateinit var mockBuildSdkVersionProvider: BuildSdkVersionProvider + @Test fun `M return process start time W appStartTime { N+ }`( @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, + @LongForgery(min = 0L) fakeCurrentTimeNs: Long, forge: Forge ) { - // GIVEN - val mockBuildSdkVersionProvider: BuildSdkVersionProvider = mock() - whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion - + // Given + whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val startTimeNs = System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(diffMs) + val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) DdRumContentProvider.createTimeNs = startTimeNs + forge.aLong(min = 0, max = DefaultAppStartTimeProvider.PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS) - val testedProvider = DefaultAppStartTimeProvider(mockBuildSdkVersionProvider) + val testedProvider = DefaultAppStartTimeProvider( + mockTimeProvider, + mockBuildSdkVersionProvider + ) - // WHEN + // When val providedStartTime = testedProvider.appStartTimeNs - // THEN - assertThat(providedStartTime) - .isCloseTo(startTimeNs, Offset.offset(TimeUnit.MILLISECONDS.toNanos(100))) + // Then + assertThat(providedStartTime).isEqualTo(startTimeNs) } @Test fun `M fall back to DdRumContentProvider W appStartTime { N+ getStartElapsedRealtime returns buggy value }`( @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, + @LongForgery(min = 0L) fakeCurrentTimeNs: Long, forge: Forge ) { // GIVEN - val mockBuildSdkVersionProvider: BuildSdkVersionProvider = mock() - whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion + whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val startTimeNs = System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(diffMs) + val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) DdRumContentProvider.createTimeNs = startTimeNs + forge.aLong(min = DefaultAppStartTimeProvider.PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS) - val testedProvider = DefaultAppStartTimeProvider(mockBuildSdkVersionProvider) - // WHEN + val testedProvider = DefaultAppStartTimeProvider( + mockTimeProvider, + mockBuildSdkVersionProvider + ) + val providedStartTime = testedProvider.appStartTimeNs // THEN - assertThat(providedStartTime) - .isCloseTo(DdRumContentProvider.createTimeNs, Offset.offset(TimeUnit.MILLISECONDS.toNanos(100))) + assertThat(providedStartTime).isEqualTo(DdRumContentProvider.createTimeNs) } @Test fun `M return content provider load time W appStartTime { Legacy }`( @IntForgery(min = Build.VERSION_CODES.M, max = Build.VERSION_CODES.N) apiVersion: Int ) { - // GIVEN - val mockBuildSdkVersionProvider: BuildSdkVersionProvider = mock() - whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion + // Given + whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) val startTimeNs = DdRumContentProvider.createTimeNs - val testedProvider = DefaultAppStartTimeProvider(mockBuildSdkVersionProvider) + val testedProvider = DefaultAppStartTimeProvider( + mockTimeProvider, + mockBuildSdkVersionProvider + ) - // WHEN + // When val providedStartTime = testedProvider.appStartTimeNs - // THEN + // Then assertThat(providedStartTime).isEqualTo(startTimeNs) } @Test fun `M return app uptime W appUptimeNs`( - @IntForgery(min = Build.VERSION_CODES.M) apiVersion: Int + @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, + @LongForgery(min = 1000000L) fakeCurrentTimeNs: Long, + @LongForgery(min = 100L, max = 999999L) fakeUptimeNs: Long ) { - // GIVEN - val mockBuildSdkVersionProvider: BuildSdkVersionProvider = mock { - on { version } doReturn apiVersion - } - val testedProvider = DefaultAppStartTimeProvider(mockBuildSdkVersionProvider) - - // WHEN - val beforeNs = System.nanoTime() - val appStartTimeNs = testedProvider.appStartTimeNs + // Given + whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() + val fakeStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeCurrentTimeNs) + .thenReturn(fakeStartTimeNs + fakeUptimeNs) + + // When + val testedProvider = DefaultAppStartTimeProvider( + mockTimeProvider, + mockBuildSdkVersionProvider + ) + testedProvider.appStartTimeNs val uptime = testedProvider.appUptimeNs - val afterNs = System.nanoTime() - // THEN - val expectedUptime = beforeNs - appStartTimeNs - assertThat(uptime) - .isGreaterThan(0) - .isCloseTo(expectedUptime, Offset.offset(TimeUnit.MILLISECONDS.toNanos(100))) - .isLessThanOrEqualTo(afterNs - appStartTimeNs) + // Then + assertThat(uptime).isEqualTo(fakeUptimeNs) } @Test fun `M return increasing uptime W appUptimeNs called multiple times`( - @IntForgery(min = Build.VERSION_CODES.M) apiVersion: Int + @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, + @LongForgery(min = 1000000L) fakeCurrentTimeNs: Long ) { - // GIVEN - val mockBuildSdkVersionProvider: BuildSdkVersionProvider = mock() - whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion - val testedProvider = DefaultAppStartTimeProvider(mockBuildSdkVersionProvider) - - // WHEN + // Given + whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() + val fakeStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeCurrentTimeNs) + .thenReturn(fakeStartTimeNs + 100L) + .thenReturn(fakeStartTimeNs + 200L) + + // When + val testedProvider = DefaultAppStartTimeProvider( + mockTimeProvider, + mockBuildSdkVersionProvider + ) + testedProvider.appStartTimeNs val uptime1 = testedProvider.appUptimeNs - Thread.sleep(10) val uptime2 = testedProvider.appUptimeNs - // THEN + // Then assertThat(uptime2).isGreaterThan(uptime1) } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt index d00c1ee8ee..8d16387e45 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt @@ -9,6 +9,7 @@ package com.datadog.android.core.internal.utils import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.utils.JsonSerializer.ITEM_SERIALIZATION_ERROR import com.datadog.android.core.internal.utils.JsonSerializer.safeMapValuesToJson +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.internal.utils.NULL_MAP_VALUE import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog @@ -25,13 +26,13 @@ import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.MapForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType 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.json.JSONArray import org.json.JSONObject import org.junit.jupiter.api.Test @@ -49,7 +50,6 @@ import org.mockito.quality.Strictness import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit -import kotlin.system.measureNanoTime @Extensions( ExtendWith(MockitoExtension::class), @@ -62,62 +62,52 @@ internal class MiscUtilsTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeCurrentTimeNs = 0L + + @LongForgery(min = 0L, max = 2L) + var fakeDelaySeconds = 0L + @Test fun `M repeat max N times W retryWithDelay { success = false }`(forge: Forge) { // Given val fakeTimes = forge.anInt(min = 1, max = 10) - val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) val mockedBlock: () -> Boolean = mock() whenever(mockedBlock.invoke()).thenReturn(false) + stubTimeProviderWithDelay() // When - val wasSuccessful = retryWithDelay(mockedBlock, fakeTimes, fakeDelay, mockInternalLogger) + val wasSuccessful = retryWithDelay(mockedBlock, fakeTimes, fakeDelayNs, mockInternalLogger, mockTimeProvider) // Then assertThat(wasSuccessful).isFalse() verify(mockedBlock, times(fakeTimes)).invoke() } - @Test - fun `M execute the block in a delayed loop W retryWithDelay`(forge: Forge) { - // Given - val fakeTimes = forge.anInt(min = 1, max = 4) - val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) - val mockedBlock: () -> Boolean = mock() - whenever(mockedBlock.invoke()).thenReturn(false) - - // When - val executionTime = measureNanoTime { retryWithDelay(mockedBlock, fakeTimes, fakeDelay, mockInternalLogger) } - - // Then - assertThat(executionTime).isCloseTo( - fakeTimes * fakeDelay, - Offset.offset(TimeUnit.SECONDS.toNanos(1)) - ) - } - @Test fun `M do nothing W retryWithDelay { times less or equal than 0 }`(forge: Forge) { // Given - val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) val mockedBlock: () -> Boolean = mock() // When - retryWithDelay(mockedBlock, forge.anInt(Int.MIN_VALUE, 1), fakeDelay, mockInternalLogger) + retryWithDelay(mockedBlock, forge.anInt(Int.MIN_VALUE, 1), fakeDelayNs, mockInternalLogger, mockTimeProvider) // Then verifyNoInteractions(mockedBlock) } @Test - fun `M repeat until success W retryWithDelay { result false }`(forge: Forge) { + fun `M repeat until success W retryWithDelay { result false }`() { // Given - val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) val mockedBlock: () -> Boolean = mock() whenever(mockedBlock.invoke()).thenReturn(false).thenReturn(true) + stubTimeProviderWithDelay() // When - val wasSuccessful = retryWithDelay(mockedBlock, 3, fakeDelay, mockInternalLogger) + val wasSuccessful = retryWithDelay(mockedBlock, 3, fakeDelayNs, mockInternalLogger, mockTimeProvider) // Then assertThat(wasSuccessful).isTrue() @@ -126,16 +116,15 @@ internal class MiscUtilsTest { @Test fun `M repeat until success W retryWithDelay { exception }`( - @Forgery exception: Exception, - forge: Forge + @Forgery exception: Exception ) { // Given - val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) val mockedBlock: () -> Boolean = mock() whenever(mockedBlock.invoke()).thenThrow(exception).thenReturn(true) + stubTimeProviderWithDelay() // When - val wasSuccessful = retryWithDelay(mockedBlock, 3, fakeDelay, mockInternalLogger) + val wasSuccessful = retryWithDelay(mockedBlock, 3, fakeDelayNs, mockInternalLogger, mockTimeProvider) // Then assertThat(wasSuccessful).isTrue() @@ -315,5 +304,15 @@ internal class MiscUtilsTest { } } + private val fakeDelayNs: Long + get() = TimeUnit.SECONDS.toNanos(fakeDelaySeconds) + + private fun stubTimeProviderWithDelay() { + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + fakeCurrentTimeNs += fakeDelayNs + fakeCurrentTimeNs + } + } + // endregion } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt index 5450f205ca..c217fe83bc 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt @@ -21,6 +21,7 @@ import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.thread.waitToIdle import com.datadog.android.core.internal.utils.TAG_DATADOG_UPLOAD import com.datadog.android.core.internal.utils.UPLOAD_WORKER_NAME +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.internal.utils.loggableStackTrace import com.datadog.android.utils.config.ApplicationContextTestConfiguration import com.datadog.android.utils.forge.Configurator @@ -29,8 +30,10 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.setStaticValue +import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -81,6 +84,11 @@ internal class DatadogExceptionHandlerTest { @Mock lateinit var mockInternalLogger: InternalLogger + lateinit var stubTimeProvider: StubTimeProvider + + @Mock + lateinit var mockTimeProvider: TimeProvider + @Mock lateinit var mockLogsFeatureScope: FeatureScope @@ -96,12 +104,18 @@ internal class DatadogExceptionHandlerTest { @StringForgery lateinit var fakeInstanceName: String + @LongForgery(min = 0L) + var fakeElapsedTimeNs = 0L + @BeforeEach fun `set up`() { + stubTimeProvider = StubTimeProvider(elapsedTimeNs = fakeElapsedTimeNs) + whenever(mockSdkCore.getFeature(Feature.LOGS_FEATURE_NAME)) doReturn mockLogsFeatureScope whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.name) doReturn fakeInstanceName + whenever(mockSdkCore.timeProvider) doReturn stubTimeProvider CoreFeature.disableKronosBackgroundSync = true @@ -164,7 +178,7 @@ internal class DatadogExceptionHandlerTest { // Then verify(mockScheduledThreadExecutor) - .waitToIdle(DatadogExceptionHandler.MAX_WAIT_FOR_IDLE_TIME_IN_MS, mockInternalLogger) + .waitToIdle(DatadogExceptionHandler.MAX_WAIT_FOR_IDLE_TIME_IN_MS, mockInternalLogger, stubTimeProvider) mockInternalLogger.verifyLog( InternalLogger.Level.WARN, InternalLogger.Target.USER, @@ -181,6 +195,11 @@ internal class DatadogExceptionHandlerTest { whenever(it.completedTaskCount).thenReturn(0) } whenever(mockSdkCore.getPersistenceExecutorService()) doReturn mockScheduledThreadExecutor + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider + val timeoutNs = DatadogExceptionHandler.MAX_WAIT_FOR_IDLE_TIME_IN_MS * 1_000_000L + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeElapsedTimeNs) + .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) Thread.setDefaultUncaughtExceptionHandler(null) testedHandler.register() val currentThread = Thread.currentThread() diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/BatchClosedMetadataForgeryFactory.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/BatchClosedMetadataForgeryFactory.kt index 10803fc6ff..55b76aea9f 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/BatchClosedMetadataForgeryFactory.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/BatchClosedMetadataForgeryFactory.kt @@ -14,7 +14,7 @@ internal class BatchClosedMetadataForgeryFactory : ForgeryFactory?) @@ -123,6 +123,7 @@ class com.datadog.android.internal.time.DefaultTimeProvider : TimeProvider interface com.datadog.android.internal.time.TimeProvider fun getDeviceTimestamp(): Long fun getServerTimestamp(): Long + fun getDeviceElapsedTimeNs(): Long fun getServerOffsetNanos(): Long fun getServerOffsetMillis(): Long fun ByteArray.toHexString(): String diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index 3170f8842c..a9319aa49e 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -156,7 +156,7 @@ public abstract interface class com/datadog/android/internal/profiler/ExecutionT public final class com/datadog/android/internal/profiler/GlobalBenchmark { public static final field INSTANCE Lcom/datadog/android/internal/profiler/GlobalBenchmark; - public final fun createExecutionTimer (Ljava/lang/String;)Lcom/datadog/android/internal/profiler/ExecutionTimer; + public final fun createExecutionTimer (Ljava/lang/String;Lcom/datadog/android/internal/time/TimeProvider;)Lcom/datadog/android/internal/profiler/ExecutionTimer; public final fun getBenchmarkSdkUploads ()Lcom/datadog/android/internal/profiler/BenchmarkSdkUploads; public final fun getProfiler ()Lcom/datadog/android/internal/profiler/BenchmarkProfiler; public final fun register (Lcom/datadog/android/internal/profiler/BenchmarkProfiler;)V @@ -296,6 +296,7 @@ public final class com/datadog/android/internal/thread/NamedRunnable : com/datad public final class com/datadog/android/internal/time/DefaultTimeProvider : com/datadog/android/internal/time/TimeProvider { public fun ()V + public fun getDeviceElapsedTimeNs ()J public fun getDeviceTimestamp ()J public fun getServerOffsetMillis ()J public fun getServerOffsetNanos ()J @@ -303,6 +304,7 @@ public final class com/datadog/android/internal/time/DefaultTimeProvider : com/d } public abstract interface class com/datadog/android/internal/time/TimeProvider { + public abstract fun getDeviceElapsedTimeNs ()J public abstract fun getDeviceTimestamp ()J public abstract fun getServerOffsetMillis ()J public abstract fun getServerOffsetNanos ()J @@ -310,6 +312,7 @@ public abstract interface class com/datadog/android/internal/time/TimeProvider { } public final class com/datadog/android/internal/time/TimeProvider$DefaultImpls { + public static fun getDeviceElapsedTimeNs (Lcom/datadog/android/internal/time/TimeProvider;)J public static fun getDeviceTimestamp (Lcom/datadog/android/internal/time/TimeProvider;)J } diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt index a8f07bf9a2..b1366b2101 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt @@ -6,18 +6,22 @@ package com.datadog.android.internal.profiler +import com.datadog.android.internal.time.TimeProvider + internal class DDExecutionTimer( private val track: String, + private val timeProvider: TimeProvider, private val benchmarkSdkUploads: BenchmarkSdkUploads = GlobalBenchmark.getBenchmarkSdkUploads() ) : ExecutionTimer { + override fun measure(action: () -> T): T { if (track.isEmpty()) { return action() } - val requestStartTime = System.nanoTime() + val requestStartTime = timeProvider.getDeviceElapsedTimeNs() val result = action() - val latencyInSeconds = (System.nanoTime() - requestStartTime) / NANOSECONDS_IN_A_SECOND + val latencyInSeconds = (timeProvider.getDeviceElapsedTimeNs() - requestStartTime) / NANOSECONDS_IN_A_SECOND responseLatencyReport(latencyInSeconds, track) return result } diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/GlobalBenchmark.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/GlobalBenchmark.kt index f80a5f7d45..eba7a6230b 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/GlobalBenchmark.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/GlobalBenchmark.kt @@ -6,6 +6,8 @@ package com.datadog.android.internal.profiler +import com.datadog.android.internal.time.TimeProvider + /** * A global holder of [BenchmarkProfiler] * allowing registration and retrieval of [BenchmarkProfiler] and [BenchmarkMeter] implementations. @@ -48,13 +50,14 @@ object GlobalBenchmark { /** * Creates the appropriate [ExecutionTimer]. */ - fun createExecutionTimer(track: String): ExecutionTimer { + fun createExecutionTimer(track: String, timeProvider: TimeProvider): ExecutionTimer { if (benchmarkSdkUploads is NoOpBenchmarkSdkUploads) { return NoOpExecutionTimer() } return DDExecutionTimer( - track = track + track = track, + timeProvider = timeProvider ) } } diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt index 4bc9cab863..452b8ead5c 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt @@ -9,9 +9,10 @@ package com.datadog.android.internal.time /** * A [TimeProvider] implementation that provides the current device time as both device and server time. * The offsets are always 0. + * + * Device timestamp and elapsed time are inherited from [TimeProvider] default implementations. */ class DefaultTimeProvider : TimeProvider { - override fun getServerTimestamp(): Long = System.currentTimeMillis() override fun getServerOffsetNanos(): Long = 0L diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt index f6f47e909c..03c348c5ae 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt @@ -15,6 +15,9 @@ interface TimeProvider { /** * Returns the current device timestamp in milliseconds. + * + * Default implementation returns [System.currentTimeMillis]. + * This should not be overridden as device time always refers to the local system clock. */ fun getDeviceTimestamp(): Long = System.currentTimeMillis() @@ -23,6 +26,14 @@ interface TimeProvider { */ fun getServerTimestamp(): Long + /** + * Returns the current device elapsed time in nanoseconds. + * + * Default implementation returns [System.nanoTime]. + * This should not be overridden as device elapsed time always refers to the monotonic system clock. + */ + fun getDeviceElapsedTimeNs(): Long = System.nanoTime() + /** * Returns the offset between the device and server time references in nanoseconds. */ diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt new file mode 100644 index 0000000000..384eaf67f8 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt @@ -0,0 +1,37 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.stub + +import com.datadog.android.internal.time.TimeProvider + +/** + * A stub implementation of [TimeProvider] for testing purposes. + * + * @property elapsedTimeNs The monotonic elapsed time in nanoseconds. Defaults to `0`. + * @property deviceTimestampMs The device timestamp in milliseconds. Defaults to `0`. + * @property serverTimestampMs The server timestamp in milliseconds. Defaults to `0`. + * @property serverOffsetNs The server time offset in nanoseconds. Defaults to `0`. + * @property serverOffsetMs The server time offset in milliseconds. Defaults to `0`. + */ +class StubTimeProvider( + var elapsedTimeNs: Long = 0L, + var deviceTimestampMs: Long = 0L, + var serverTimestampMs: Long = 0L, + var serverOffsetNs: Long = 0L, + var serverOffsetMs: Long = 0L +) : TimeProvider { + + override fun getDeviceTimestamp(): Long = deviceTimestampMs + + override fun getServerTimestamp(): Long = serverTimestampMs + + override fun getDeviceElapsedTimeNs(): Long = elapsedTimeNs + + override fun getServerOffsetNanos(): Long = serverOffsetNs + + override fun getServerOffsetMillis(): Long = serverOffsetMs +} diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt index 3e0299b38b..34a7f2ac11 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt @@ -11,8 +11,12 @@ import com.datadog.android.flags.internal.model.PrecomputedFlag import com.datadog.android.flags.internal.storage.RecordWriter import com.datadog.android.flags.model.EvaluationContext import com.datadog.android.flags.model.ExposureEvent +import com.datadog.android.internal.time.TimeProvider -internal class ExposureEventsProcessor(private val writer: RecordWriter) : EventsProcessor { +internal class ExposureEventsProcessor( + private val writer: RecordWriter, + private val timeProvider: TimeProvider +) : EventsProcessor { private data class CacheKey( val targetingKey: String, @@ -65,7 +69,7 @@ internal class ExposureEventsProcessor(private val writer: RecordWriter) : Event } private fun buildExposureEvent(flagName: String, context: EvaluationContext, data: PrecomputedFlag): ExposureEvent { - val now = System.currentTimeMillis() + val now = timeProvider.getDeviceTimestamp() return ExposureEvent( timestamp = now, allocation = ExposureEvent.Identifier(data.allocationKey), diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsFeature.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsFeature.kt index 23b1fb7b52..62d10cd609 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsFeature.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsFeature.kt @@ -106,7 +106,10 @@ internal class FlagsFeature(private val sdkCore: FeatureSdkCore, internal val fl isInitialized = true sdkCore.setContextUpdateReceiver(this) dataWriter = createDataWriter() - processor = ExposureEventsProcessor(dataWriter) + processor = ExposureEventsProcessor( + writer = dataWriter, + timeProvider = sdkCore.timeProvider + ) } override fun onStop() { diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/model/FlagsStateEntry.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/model/FlagsStateEntry.kt index d5a256b727..d61d3f383f 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/model/FlagsStateEntry.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/model/FlagsStateEntry.kt @@ -11,5 +11,5 @@ import com.datadog.android.flags.model.EvaluationContext internal data class FlagsStateEntry( val evaluationContext: EvaluationContext, val flags: Map, - val lastUpdateTimestamp: Long = System.currentTimeMillis() + val lastUpdateTimestamp: Long ) diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/persistence/FlagsPersistenceManager.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/persistence/FlagsPersistenceManager.kt index 447139fa56..9563a6eb8d 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/persistence/FlagsPersistenceManager.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/persistence/FlagsPersistenceManager.kt @@ -32,12 +32,13 @@ internal class FlagsPersistenceManager( internal fun saveFlagsState( context: EvaluationContext, flags: Map, + currentTimestamp: Long, callback: DataStoreWriteCallback? = null ) { val entry = FlagsStateEntry( evaluationContext = context, flags = flags, - lastUpdateTimestamp = System.currentTimeMillis() + lastUpdateTimestamp = currentTimestamp ) dataStore.setValue( diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt index 376e28f67e..f90a1b5b42 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt @@ -53,6 +53,7 @@ internal class DefaultFlagsRepository( persistenceManager.saveFlagsState( context = context, flags = flags, + currentTimestamp = featureSdkCore.timeProvider.getDeviceTimestamp(), object : DataStoreWriteCallback { override fun onSuccess() { } diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt index 602ea1a461..c7eccdc153 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt @@ -11,7 +11,9 @@ import com.datadog.android.flags.internal.storage.RecordWriter import com.datadog.android.flags.model.EvaluationContext import com.datadog.android.flags.model.ExposureEvent import com.datadog.android.flags.utils.forge.ForgeConfigurator +import com.datadog.android.internal.time.TimeProvider import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -26,8 +28,10 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeast import org.mockito.kotlin.atMost +import org.mockito.kotlin.doReturn import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @ExtendWith(MockitoExtension::class, ForgeExtension::class) @ForgeConfiguration(ForgeConfigurator::class) @@ -36,6 +40,12 @@ internal class ExposureEventsProcessorTest { @Mock lateinit var mockRecordWriter: RecordWriter + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeTimestamp = 0L + @StringForgery lateinit var fakeFlagName: String @@ -53,7 +63,7 @@ internal class ExposureEventsProcessorTest { @BeforeEach fun `set up`(forge: Forge) { - testedProcessor = ExposureEventsProcessor(mockRecordWriter) + testedProcessor = ExposureEventsProcessor(mockRecordWriter, mockTimeProvider) fakeFlag = forge.getForgery().copy( allocationKey = fakeAllocationKey, variationKey = fakeVariationKey @@ -75,6 +85,7 @@ internal class ExposureEventsProcessorTest { ) // When + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp testedProcessor.processEvent(fakeFlagName, fakeContext, fakeFlag) // Then @@ -88,7 +99,7 @@ internal class ExposureEventsProcessorTest { assertThat(capturedEvent.subject.id).isEqualTo(fakeTargetingKey) assertThat(capturedEvent.subject.attributes.additionalProperties).containsKeys("user_id", "plan", "age") assertThat(capturedEvent.subject.attributes.additionalProperties).hasSize(3) - assertThat(capturedEvent.timestamp).isGreaterThan(0) + assertThat(capturedEvent.timestamp).isEqualTo(fakeTimestamp) } @Test @@ -227,22 +238,23 @@ internal class ExposureEventsProcessorTest { attributes = mapOf("user_id" to forge.anAlphabeticalString()) ) - val beforeTime = System.currentTimeMillis() + val fakeTimestamp1 = forge.aPositiveLong() + val fakeTimestamp2 = forge.aPositiveLong() + whenever(mockTimeProvider.getDeviceTimestamp()) + .thenReturn(fakeTimestamp1) + .thenReturn(fakeTimestamp2) // When testedProcessor.processEvent(fakeFlagName, fakeContext1, fakeFlag) testedProcessor.processEvent(fakeFlagName, fakeContext2, fakeFlag) - val afterTime = System.currentTimeMillis() - // Then val eventCaptor = argumentCaptor() verify(mockRecordWriter, times(2)).write(eventCaptor.capture()) val capturedEvents = eventCaptor.allValues - capturedEvents.forEach { event -> - assertThat(event.timestamp).isBetween(beforeTime, afterTime) - } + assertThat(capturedEvents[0].timestamp).isEqualTo(fakeTimestamp1) + assertThat(capturedEvents[1].timestamp).isEqualTo(fakeTimestamp2) } // endregion @@ -481,7 +493,7 @@ internal class ExposureEventsProcessorTest { Thread { repeat(10) { Thread.sleep(5) - testedProcessor = ExposureEventsProcessor(mockRecordWriter) + testedProcessor = ExposureEventsProcessor(mockRecordWriter, mockTimeProvider) } } } diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/FlagsFeatureTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/FlagsFeatureTest.kt index 74f53391ed..69dd75d05e 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/FlagsFeatureTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/FlagsFeatureTest.kt @@ -62,6 +62,7 @@ internal class FlagsFeatureTest { @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.timeProvider) doReturn mock() whenever(mockSdkCore.createSingleThreadExecutorService(any())) doReturn mockExecutorService // Setup mockContext with default release build (flags = 0) diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/persistence/FlagsStateSerializerTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/persistence/FlagsStateSerializerTest.kt index 762aac3651..6aee9ce7b5 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/persistence/FlagsStateSerializerTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/persistence/FlagsStateSerializerTest.kt @@ -11,6 +11,7 @@ import com.datadog.android.flags.internal.model.FlagsStateEntry import com.datadog.android.flags.internal.model.PrecomputedFlag import com.datadog.android.flags.model.EvaluationContext import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat import org.json.JSONObject @@ -26,6 +27,9 @@ internal class FlagsStateSerializerTest { @Mock lateinit var mockInternalLogger: InternalLogger + @LongForgery(min = 0L) + private var fakeTimestamp = 0L + private lateinit var testedSerializer: FlagsStateSerializer @BeforeEach @@ -65,8 +69,7 @@ internal class FlagsStateSerializerTest { ) ) - val timestamp = System.currentTimeMillis() - val flagsState = FlagsStateEntry(evaluationContext, flags, timestamp) + val flagsState = FlagsStateEntry(evaluationContext, flags, fakeTimestamp) // When val serialized = testedSerializer.serialize(flagsState) @@ -78,7 +81,7 @@ internal class FlagsStateSerializerTest { .hasTargetingKey(targetingKey) .hasFlag("flag1") .hasFlag("flag2") - .hasTimestamp(timestamp) + .hasTimestamp(fakeTimestamp) } @Test @@ -86,7 +89,7 @@ internal class FlagsStateSerializerTest { // Given val targetingKey = forge.anAlphabeticalString() val evaluationContext = EvaluationContext(targetingKey, emptyMap()) - val flagsState = FlagsStateEntry(evaluationContext, emptyMap()) + val flagsState = FlagsStateEntry(evaluationContext, emptyMap(), fakeTimestamp) // When val serialized = testedSerializer.serialize(flagsState) @@ -103,7 +106,7 @@ internal class FlagsStateSerializerTest { // Given // Create a state that could potentially cause serialization issues val evaluationContext = EvaluationContext("key", emptyMap()) - val flagsState = FlagsStateEntry(evaluationContext, emptyMap()) + val flagsState = FlagsStateEntry(evaluationContext, emptyMap(), fakeTimestamp) // When val serialized = testedSerializer.serialize(flagsState) diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepositoryTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepositoryTest.kt index 205b15b9e3..0ce9aee07e 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepositoryTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepositoryTest.kt @@ -6,7 +6,6 @@ package com.datadog.android.flags.internal.repository -import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.datastore.DataStoreHandler import com.datadog.android.api.storage.datastore.DataStoreReadCallback @@ -29,6 +28,7 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.CountDownLatch @@ -47,9 +47,6 @@ internal class DefaultFlagsRepositoryTest { @Mock lateinit var mockDataStore: DataStoreHandler - @Mock - lateinit var mockInternalLogger: InternalLogger - private lateinit var testedRepository: DefaultFlagsRepository private lateinit var testContext: EvaluationContext @@ -58,7 +55,8 @@ internal class DefaultFlagsRepositoryTest { @BeforeEach fun `set up`(forge: Forge) { - whenever(mockFeatureSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockFeatureSdkCore.internalLogger) doReturn mock() + whenever(mockFeatureSdkCore.timeProvider) doReturn mock() whenever( mockDataStore.value( key = any(), diff --git a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt index 10372fd1b0..739fb6e3aa 100644 --- a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt +++ b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt @@ -45,7 +45,7 @@ internal class DatadogLogHandler( return } - val resolvedTimeStamp = timestamp ?: System.currentTimeMillis() + val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestamp() val combinedAttributes = mutableMapOf() val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME) if (logsFeature != null) { @@ -112,7 +112,7 @@ internal class DatadogLogHandler( return } - val resolvedTimeStamp = timestamp ?: System.currentTimeMillis() + val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestamp() val combinedAttributes = mutableMapOf() val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME) if (logsFeature != null) { diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt index b6041789ce..d45a84a5a6 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt @@ -16,6 +16,7 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.core.sampling.Sampler +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.log.LogAttributes import com.datadog.android.log.assertj.LogEventAssert.Companion.assertThat import com.datadog.android.log.internal.LogsFeature @@ -71,6 +72,8 @@ internal class DatadogLogHandlerTest { private lateinit var fakeAttributes: Map private var fakeLevel: Int = 0 + private var fakeTimestamp: Long = 0L + @Forgery lateinit var fakeThrowable: Throwable @@ -92,6 +95,9 @@ internal class DatadogLogHandlerTest { @Mock lateinit var mockSdkCore: FeatureSdkCore + @Mock + lateinit var mockTimeProvider: TimeProvider + @Mock lateinit var mockLogsFeatureScope: FeatureScope @@ -122,6 +128,7 @@ internal class DatadogLogHandlerTest { fakeLoggerName = forge.anAlphabeticalString() fakeMessage = forge.anAlphabeticalString() fakeLevel = forge.anInt(2, 8) + fakeTimestamp = forge.aLong(min = 0L) fakeAttributes = forge.aMap { anAlphabeticalString() to anInt() } fakeTags = forge.aList { anAlphabeticalString() }.toSet() fakeDatadogContext = fakeDatadogContext.copy( @@ -159,6 +166,9 @@ internal class DatadogLogHandlerTest { mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME) ) doReturn mockRumFeature + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + testedHandler = DatadogLogHandler( loggerName = fakeLoggerName, logGenerator = DatadogLogGenerator( @@ -173,9 +183,6 @@ internal class DatadogLogHandlerTest { @Test fun `forward log to LogWriter`() { - // Given - val now = System.currentTimeMillis() - // When testedHandler.handleLog( fakeLevel, @@ -199,7 +206,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasBuildId(fakeDatadogContext.appBuildId) @@ -256,9 +263,6 @@ internal class DatadogLogHandlerTest { @Test fun `forward log to LogWriter with throwable`() { - // Given - val now = System.currentTimeMillis() - // When testedHandler.handleLog( fakeLevel, @@ -282,7 +286,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -321,9 +325,6 @@ internal class DatadogLogHandlerTest { @StringForgery errorMessage: String, @StringForgery errorStack: String ) { - // Given - val now = System.currentTimeMillis() - // When testedHandler.handleLog( fakeLevel, @@ -349,7 +350,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -638,7 +639,6 @@ internal class DatadogLogHandlerTest { @Test fun `forward log to LogWriter on background thread`(forge: Forge) { // Given - val now = System.currentTimeMillis() val threadName = forge.anAlphabeticalString() val countDownLatch = CountDownLatch(1) @@ -674,7 +674,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(threadName) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -702,7 +702,6 @@ internal class DatadogLogHandlerTest { @Test fun `forward log to LogWriter without network info`() { // Given - val now = System.currentTimeMillis() testedHandler = DatadogLogHandler( loggerName = fakeLoggerName, logGenerator = DatadogLogGenerator( @@ -737,7 +736,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .doesNotHaveNetworkInfo() .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -765,7 +764,6 @@ internal class DatadogLogHandlerTest { @Test fun `forward minimal log to LogWriter`() { // Given - val now = System.currentTimeMillis() fakeDatadogContext = fakeDatadogContext.copy( featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { remove(Feature.RUM_FEATURE_NAME) @@ -805,7 +803,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .doesNotHaveNetworkInfo() .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -1080,7 +1078,6 @@ internal class DatadogLogHandlerTest { @Test fun `it will sample in the logs when required`() { // Given - val now = System.currentTimeMillis() whenever(mockSampler.sample(Unit)).thenReturn(true) testedHandler = DatadogLogHandler( loggerName = fakeLoggerName, @@ -1113,7 +1110,7 @@ internal class DatadogLogHandlerTest { .hasLoggerName(fakeLoggerName) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(now) + .hasDateAround(fakeTimestamp) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) diff --git a/features/dd-sdk-android-ndk/src/androidTest/kotlin/com/datadog/android/ndk/NdkTests.kt b/features/dd-sdk-android-ndk/src/androidTest/kotlin/com/datadog/android/ndk/NdkTests.kt index 825cbd9c81..57c982e73a 100644 --- a/features/dd-sdk-android-ndk/src/androidTest/kotlin/com/datadog/android/ndk/NdkTests.kt +++ b/features/dd-sdk-android-ndk/src/androidTest/kotlin/com/datadog/android/ndk/NdkTests.kt @@ -17,7 +17,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith -import java.lang.RuntimeException import java.nio.charset.Charset import java.util.concurrent.TimeUnit diff --git a/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt b/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt index 7862619c7c..2e80d2e56b 100644 --- a/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt +++ b/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt @@ -64,8 +64,10 @@ internal class NdkCrashReportsFeature( ) return } + val nowMs = sdkCore.timeProvider.getDeviceTimestamp() + val nowElapsedNs = sdkCore.timeProvider.getDeviceElapsedTimeNs() val appStartTimestamp = - TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) - System.nanoTime() + sdkCore.appStartTimeNs + TimeUnit.MILLISECONDS.toNanos(nowMs) - nowElapsedNs + sdkCore.appStartTimeNs registerSignalHandler( ndkCrashesDirs.absolutePath, consentToInt(internalSdkCore.trackingConsent), diff --git a/features/dd-sdk-android-ndk/src/test/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeatureTest.kt b/features/dd-sdk-android-ndk/src/test/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeatureTest.kt index 330686958c..f9960a0bcc 100644 --- a/features/dd-sdk-android-ndk/src/test/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeatureTest.kt +++ b/features/dd-sdk-android-ndk/src/test/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeatureTest.kt @@ -50,6 +50,7 @@ class NdkCrashReportsFeatureTest { @BeforeEach fun `set up`() { + whenever(mockSdkCore.timeProvider) doReturn mock() testedFeature = NdkCrashReportsFeature(mockSdkCore) } @@ -100,7 +101,7 @@ class NdkCrashReportsFeatureTest { @ParameterizedTest @EnumSource(TrackingConsent::class) - fun `M do nothing W register { nativeLibrary not loaded }`( + fun `M do nothing W register { nativeLibrary not loaded }`( trackingConsent: TrackingConsent ) { // GIVEN diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt index d78fd1e658..943bcb30b8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt @@ -326,7 +326,7 @@ internal class DatadogLateCrashReporter( private val ViewEvent.isWithinSessionAvailability: Boolean get() { - val now = System.currentTimeMillis() + val now = sdkCore.timeProvider.getDeviceTimestamp() val sessionsTimeDifference = now - this.date return sessionsTimeDifference < VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index f00cae4072..d257d9c37a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -186,7 +186,8 @@ internal class RumFeature( if (configuration.collectAccessibility) { accessibilityReader = DefaultAccessibilityReader( internalLogger = sdkCore.internalLogger, - applicationContext = appContext + applicationContext = appContext, + timeProvider = sdkCore.timeProvider ) accessibilitySnapshotManager = DefaultAccessibilitySnapshotManager(accessibilityReader) } @@ -288,7 +289,8 @@ internal class RumFeature( metricDispatcher = DefaultUISlownessMetricDispatcher( slowFramesConfiguration, sdkCore.internalLogger - ) + ), + sdkCore.timeProvider ) } else { sdkCore.internalLogger.log( @@ -780,9 +782,7 @@ internal class RumFeature( touchTargetExtraAttributesProviders = emptyList(), interactionPredicate = NoOpInteractionPredicate(), viewTrackingStrategy = ActivityViewTrackingStrategy(false), - longTaskTrackingStrategy = MainLooperLongTaskStrategy( - DEFAULT_LONG_TASK_THRESHOLD_MS - ), + longTaskTrackingStrategy = MainLooperLongTaskStrategy(DEFAULT_LONG_TASK_THRESHOLD_MS), viewEventMapper = NoOpEventMapper(), errorEventMapper = NoOpEventMapper(), resourceEventMapper = NoOpEventMapper(), diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt index 46bf71569b..01659dfe12 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt @@ -21,6 +21,7 @@ import android.view.View import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.internal.domain.InfoProvider import java.util.concurrent.atomic.AtomicLong @@ -28,6 +29,7 @@ import java.util.concurrent.atomic.AtomicLong internal class DefaultAccessibilityReader( private val internalLogger: InternalLogger, private val applicationContext: Context, + private val timeProvider: TimeProvider, private val resources: Resources = applicationContext.resources, private val activityManager: ActivityManager? = applicationContext.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager, @@ -97,7 +99,7 @@ internal class DefaultAccessibilityReader( @Synchronized override fun getState(): AccessibilityInfo { - val currentTime = System.currentTimeMillis() + val currentTime = timeProvider.getDeviceTimestamp() val shouldPoll = currentTime - lastPollTime.get() >= POLL_THRESHOLD if (shouldPoll) { lastPollTime.set(currentTime) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 9725edc1ad..8e04c474a1 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -65,7 +65,7 @@ internal class RumSessionScope( internal var sessionState: State = State.NOT_TRACKED private var startReason: StartReason = StartReason.USER_APP_LAUNCH internal var isActive: Boolean = true - private val sessionStartNs = AtomicLong(System.nanoTime()) + private val sessionStartNs = AtomicLong(sdkCore.timeProvider.getDeviceElapsedTimeNs()) private val lastUserInteractionNs = AtomicLong(0L) @@ -228,7 +228,7 @@ internal class RumSessionScope( @Suppress("ComplexMethod") private fun updateSession(event: RumRawEvent) { - val nanoTime = System.nanoTime() + val nanoTime = sdkCore.timeProvider.getDeviceElapsedTimeNs() val isNewSession = sessionId == RumContext.NULL_UUID val timeSinceLastInteractionNs = nanoTime - lastUserInteractionNs.get() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt index 0d23e18f3d..6d4cb7b398 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt @@ -10,6 +10,7 @@ import android.content.Context import android.os.Looper import android.util.Printer import com.datadog.android.api.SdkCore +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor import com.datadog.android.rum.tracking.TrackingStrategy @@ -17,9 +18,7 @@ import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean -internal class MainLooperLongTaskStrategy( - internal val thresholdMs: Long -) : Printer, TrackingStrategy { +internal class MainLooperLongTaskStrategy(internal val thresholdMs: Long) : Printer, TrackingStrategy { private val thresholdNS = TimeUnit.MILLISECONDS.toNanos(thresholdMs) private var startUptimeNs: Long = 0L @@ -79,7 +78,7 @@ internal class MainLooperLongTaskStrategy( // region Internal private fun detectLongTask(message: String) { - val now = System.nanoTime() + val now = (sdkCore as FeatureSdkCore).timeProvider.getDeviceElapsedTimeNs() if (message.startsWith(PREFIX_START)) { @Suppress("UnsafeThirdPartyFunctionCall") // substring can't throw IndexOutOfBounds target = message.substring(PREFIX_START_LENGTH) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt index 16f2f53741..9c4e61adc0 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.metric.slowframes import androidx.metrics.performance.FrameData +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.configuration.SlowFramesConfiguration import com.datadog.android.rum.internal.domain.FrameMetricsData import com.datadog.android.rum.internal.domain.state.SlowFrameRecord @@ -22,21 +23,22 @@ internal interface SlowFramesListener : FrameStateListener { internal class DefaultSlowFramesListener( internal val configuration: SlowFramesConfiguration, - internal val metricDispatcher: UISlownessMetricDispatcher + internal val metricDispatcher: UISlownessMetricDispatcher, + timeProvider: TimeProvider ) : SlowFramesListener { @Volatile private var currentViewId: String? = null @Volatile - private var currentViewStartedTimeStampNs: Long = System.nanoTime() + private var currentViewStartedTimestampNs: Long = timeProvider.getDeviceElapsedTimeNs() private val slowFramesRecords = ConcurrentHashMap() // Called from the main thread override fun onViewCreated(viewId: String, startedTimestampNs: Long) { currentViewId = viewId - currentViewStartedTimeStampNs = startedTimestampNs + currentViewStartedTimestampNs = startedTimestampNs metricDispatcher.onViewCreated(viewId) } @@ -61,7 +63,7 @@ internal class DefaultSlowFramesListener( // Called from the background thread override fun onFrame(volatileFrameData: FrameData) { val viewId = currentViewId - if (viewId == null || volatileFrameData.frameStartNanos < currentViewStartedTimeStampNs) { + if (viewId == null || volatileFrameData.frameStartNanos < currentViewStartedTimestampNs) { if (viewId != null) { metricDispatcher.incrementMissedFrameCount(viewId) } @@ -135,7 +137,7 @@ internal class DefaultSlowFramesListener( private fun getViewPerformanceReport(viewId: String) = slowFramesRecords.getOrPut(viewId) { ViewUIPerformanceReport( - currentViewStartedTimeStampNs, + currentViewStartedTimestampNs, configuration.maxSlowFramesAmount, minimumViewLifetimeThresholdNs = configuration.minViewLifetimeThresholdNs ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt index 6a95e53a60..4728065b03 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt @@ -23,7 +23,7 @@ internal interface RumFirstDrawTimeReporter { fun create(sdkCore: InternalSdkCore): RumFirstDrawTimeReporter { return RumFirstDrawTimeReporterImpl( internalLogger = sdkCore.internalLogger, - timeProviderNs = { System.nanoTime() }, + timeProviderNs = { sdkCore.timeProvider.getDeviceElapsedTimeNs() }, windowCallbacksRegistry = RumWindowCallbacksRegistryImpl(), handler = Handler(Looper.getMainLooper()) ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt index 6af626f5de..db323aa306 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt @@ -8,6 +8,8 @@ package com.datadog.android.rum.resource import com.datadog.android.Datadog import com.datadog.android.api.SdkCore +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind @@ -43,10 +45,12 @@ constructor( private var firstByte: Long = 0L private var lastByte: Long = 0L + private val timeProvider: TimeProvider = (sdkCore as FeatureSdkCore).timeProvider + init { val rumMonitor = GlobalRumMonitor.get(sdkCore) rumMonitor.startResource(key, METHOD, url) - callStart = System.nanoTime() + callStart = timeProvider.getDeviceElapsedTimeNs() if (rumMonitor is AdvancedRumMonitor) { rumMonitor.waitForResourceTiming(key) } @@ -56,36 +60,36 @@ constructor( /** @inheritdoc */ override fun read(): Int { - if (firstByte == 0L) firstByte = System.nanoTime() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read().also { if (it >= 0) size++ - lastByte = System.nanoTime() + lastByte = timeProvider.getDeviceElapsedTimeNs() } } } /** @inheritdoc */ override fun read(b: ByteArray): Int { - if (firstByte == 0L) firstByte = System.nanoTime() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read(b).also { if (it >= 0) size += it - lastByte = System.nanoTime() + lastByte = timeProvider.getDeviceElapsedTimeNs() } } } /** @inheritdoc */ override fun read(b: ByteArray, off: Int, len: Int): Int { - if (firstByte == 0L) firstByte = System.nanoTime() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read(b, off, len).also { if (it >= 0) size += it - lastByte = System.nanoTime() + lastByte = timeProvider.getDeviceElapsedTimeNs() } } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt index 564f260bf1..865f184800 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt @@ -62,6 +62,7 @@ internal class RumTest { @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mock() + whenever(mockSdkCore.timeProvider) doReturn mock() whenever(mockSdkCore.firstPartyHostResolver) doReturn mock() whenever(mockSdkCore.createSingleThreadExecutorService(any())) doReturn mock() whenever(mockSdkCore.createScheduledExecutorService(any())) doReturn mock() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt index 9431514cf4..8dabc72d50 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.persistence.Deserializer +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.assertj.ErrorEventAssert import com.datadog.android.rum.assertj.ViewEventAssert @@ -97,12 +98,20 @@ internal class DatadogLateCrashReporterTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeCurrentTimeMs: Long = 0L + @Forgery lateinit var fakeDatadogContext: DatadogContext @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeCurrentTimeMs + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockEventWriteScope.invoke(any())) doAnswer { @@ -144,7 +153,7 @@ internal class DatadogLateCrashReporterTest { ) val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ), @@ -246,7 +255,7 @@ internal class DatadogLateCrashReporterTest { ) val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ), @@ -349,7 +358,7 @@ internal class DatadogLateCrashReporterTest { ) val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ), @@ -409,7 +418,7 @@ internal class DatadogLateCrashReporterTest { ) val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ), @@ -495,7 +504,7 @@ internal class DatadogLateCrashReporterTest { ) ) val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD + 1 ), usr = ViewEvent.Usr( @@ -686,22 +695,13 @@ internal class DatadogLateCrashReporterTest { @Test fun `M send RUM view+error W handleAnrCrash()`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, @Forgery fakeUserInfo: UserInfo, forge: Forge ) { // Given - val fakeServerOffset = - forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) - fakeDatadogContext = fakeDatadogContext.copy( - time = fakeDatadogContext.time.copy( - serverTimeOffsetMs = fakeServerOffset - ) - ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ), @@ -713,6 +713,15 @@ internal class DatadogLateCrashReporterTest { additionalProperties = fakeUserInfo.additionalProperties.toMutableMap() ) ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) + val fakeServerOffset = + forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) + fakeDatadogContext = fakeDatadogContext.copy( + time = fakeDatadogContext.time.copy( + serverTimeOffsetMs = fakeServerOffset + ) + ) + val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -785,11 +794,18 @@ internal class DatadogLateCrashReporterTest { @Test fun `M send RUM view+error W handleAnrCrash() { view without user }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, forge: Forge ) { // Given + val fakeViewEvent = viewEvent.copy( + date = fakeCurrentTimeMs - forge.aLong( + min = 0L, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 + ), + usr = null + ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) val fakeServerOffset = forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) fakeDatadogContext = fakeDatadogContext.copy( @@ -798,13 +814,6 @@ internal class DatadogLateCrashReporterTest { ) ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = 0L, - max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 - ), - usr = null - ) val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -869,23 +878,15 @@ internal class DatadogLateCrashReporterTest { @Test fun `M send only RUM error W handleAnrCrash() { view is too old }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, @Forgery fakeUserInfo: UserInfo, forge: Forge ) { // Given - val fakeServerOffset = - forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) - fakeDatadogContext = fakeDatadogContext.copy( - time = fakeDatadogContext.time.copy( - serverTimeOffsetMs = fakeServerOffset - ) - ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD + 1 + date = fakeCurrentTimeMs - forge.aLong( + min = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD + 1, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD * 2 ), usr = ViewEvent.Usr( id = fakeUserInfo.id, @@ -895,6 +896,15 @@ internal class DatadogLateCrashReporterTest { additionalProperties = fakeUserInfo.additionalProperties.toMutableMap() ) ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) + val fakeServerOffset = + forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) + fakeDatadogContext = fakeDatadogContext.copy( + time = fakeDatadogContext.time.copy( + serverTimeOffsetMs = fakeServerOffset + ) + ) + val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -962,11 +972,17 @@ internal class DatadogLateCrashReporterTest { @Test fun `M log warning and not send anything W handleAnrCrash() { RUM feature not registered }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, forge: Forge ) { // Given + val fakeViewEvent = viewEvent.copy( + date = fakeCurrentTimeMs - forge.aLong( + min = 0L, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 + ) + ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) val fakeServerOffset = forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) fakeDatadogContext = fakeDatadogContext.copy( @@ -975,12 +991,6 @@ internal class DatadogLateCrashReporterTest { ) ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = 0L, - max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 - ) - ) val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -1033,7 +1043,7 @@ internal class DatadogLateCrashReporterTest { ) { // Given val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( + date = fakeCurrentTimeMs - forge.aLong( min = 0L, max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 ) @@ -1056,11 +1066,17 @@ internal class DatadogLateCrashReporterTest { @Test fun `M not send anything W handleAnrCrash() { last view event belongs to the current session }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, forge: Forge ) { // Given + val fakeViewEvent = viewEvent.copy( + date = fakeCurrentTimeMs - forge.aLong( + min = 0L, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 + ) + ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) val fakeServerOffset = forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) fakeDatadogContext = fakeDatadogContext.copy( @@ -1072,12 +1088,6 @@ internal class DatadogLateCrashReporterTest { ) ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = 0L, - max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 - ) - ) val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -1096,11 +1106,17 @@ internal class DatadogLateCrashReporterTest { @Test fun `M not send anything W handleAnrCrash() { ANR was already sent }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, forge: Forge ) { // Given + val fakeViewEvent = viewEvent.copy( + date = fakeCurrentTimeMs - forge.aLong( + min = 0L, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 + ) + ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) val fakeServerOffset = forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) fakeDatadogContext = fakeDatadogContext.copy( @@ -1109,12 +1125,6 @@ internal class DatadogLateCrashReporterTest { ) ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = 0L, - max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 - ) - ) val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent @@ -1134,11 +1144,17 @@ internal class DatadogLateCrashReporterTest { @Test fun `M not send anything W handleAnrCrash() { empty threads dump }`( - @LongForgery(min = 1) fakeTimestamp: Long, @Forgery viewEvent: ViewEvent, forge: Forge ) { // Given + val fakeViewEvent = viewEvent.copy( + date = fakeCurrentTimeMs - forge.aLong( + min = 0L, + max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 + ) + ) + val fakeTimestamp = fakeViewEvent.date + forge.aLong(min = 1L, max = 1000000L) val fakeServerOffset = forge.aLong(min = -fakeTimestamp, max = Long.MAX_VALUE - fakeTimestamp) fakeDatadogContext = fakeDatadogContext.copy( @@ -1147,12 +1163,6 @@ internal class DatadogLateCrashReporterTest { ) ) - val fakeViewEvent = viewEvent.copy( - date = System.currentTimeMillis() - forge.aLong( - min = 0L, - max = DatadogLateCrashReporter.VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD - 1000 - ) - ) val fakeViewEventJson = fakeViewEvent.toJson().asJsonObject whenever(mockRumEventDeserializer.deserialize(fakeViewEventJson)) doReturn fakeViewEvent diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 78f5fdf4bc..699f68ef16 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -150,6 +150,7 @@ internal class RumFeatureTest { @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.timeProvider) doReturn mock() whenever(mockSdkCore.createScheduledExecutorService(any())) doReturn mockScheduledExecutorService val mockContentResolver = mock() @@ -812,6 +813,7 @@ internal class RumFeatureTest { verify(mockLongTaskTrackingStrategy).unregister(appContext.mockInstance) } + @Test fun `M clean up all RUM context update receivers W onStop()`() { // Given testedFeature.onInitialize(appContext.mockInstance) @@ -822,7 +824,7 @@ internal class RumFeatureTest { // Then rumContextUpdateReceivers.forEach { - verify(mockSdkCore.removeContextUpdateReceiver(it)) + verify(mockSdkCore).removeContextUpdateReceiver(it) } assertThat(testedFeature.rumContextUpdateReceivers).isEmpty() } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt index 2517883d08..85e0466e9c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt @@ -20,12 +20,14 @@ import android.view.View import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.internal.domain.accessibility.DefaultAccessibilityReader.Companion.CAPTIONING_ENABLED_KEY import com.datadog.android.rum.utils.forge.Configurator import com.datadog.tools.unit.annotations.TestTargetApi import com.datadog.tools.unit.extensions.ApiLevelExtension import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -84,12 +86,19 @@ internal class DefaultAccessibilityReaderTest { @Mock lateinit var mockHandler: Handler + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeTimestamp: Long = 0L + private lateinit var testedReader: DefaultAccessibilityReader @BeforeEach fun setup() { whenever(mockContext.contentResolver) doReturn mockContentResolver whenever(mockResources.configuration) doReturn mockConfiguration + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp setupDefaultMockBehavior() @@ -101,6 +110,7 @@ internal class DefaultAccessibilityReaderTest { return DefaultAccessibilityReader( internalLogger = mockInternalLogger, applicationContext = mockContext, + timeProvider = mockTimeProvider, resources = mockResources, activityManager = mockActivityManager, accessibilityManager = mockAccessibilityManager, @@ -157,6 +167,7 @@ internal class DefaultAccessibilityReaderTest { testedReader = DefaultAccessibilityReader( internalLogger = mockInternalLogger, applicationContext = mockContext, + timeProvider = mockTimeProvider, resources = mockResources, activityManager = mockActivityManager, accessibilityManager = null, @@ -336,6 +347,7 @@ internal class DefaultAccessibilityReaderTest { testedReader = DefaultAccessibilityReader( internalLogger = mockInternalLogger, applicationContext = mockContext, + timeProvider = mockTimeProvider, resources = mockResources, activityManager = null, accessibilityManager = mockAccessibilityManager, @@ -616,6 +628,7 @@ internal class DefaultAccessibilityReaderTest { testedReader = DefaultAccessibilityReader( internalLogger = mockInternalLogger, applicationContext = mockContext, + timeProvider = mockTimeProvider, resources = mockResources, activityManager = mockActivityManager, accessibilityManager = null, @@ -763,18 +776,17 @@ internal class DefaultAccessibilityReaderTest { val lastPollTimeField = testedReader.javaClass.getDeclaredField("lastPollTime") lastPollTimeField.isAccessible = true val lastPollTime = lastPollTimeField.get(testedReader) as AtomicLong - val oldTime = System.currentTimeMillis() - 31_000 + val oldTime = fakeTimestamp - 31_000 lastPollTime.set(oldTime) // When - Call after threshold exceeded - val currentTimeBefore = System.currentTimeMillis() + val newTimestamp = fakeTimestamp + 1000 + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn newTimestamp testedReader.getState() - val currentTimeAfter = System.currentTimeMillis() - // Then - lastPollTime should be updated to current time (within reasonable range) + // Then - lastPollTime should be updated to the new timestamp val newPollTime = lastPollTime.get() - assertThat(newPollTime).isGreaterThanOrEqualTo(currentTimeBefore) - assertThat(newPollTime).isLessThanOrEqualTo(currentTimeAfter) + assertThat(newPollTime).isEqualTo(newTimestamp) assertThat(newPollTime).isGreaterThan(oldTime) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeAttributePropagationTest.kt index 2e7d7b31eb..4e92796761 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeAttributePropagationTest.kt @@ -56,6 +56,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -208,8 +209,7 @@ internal class RumActionScopeAttributePropagationTest { expectedAttributes.putAll(fakeActionAttributes) // When - Thread.sleep(TEST_INACTIVITY_MS * 2) - testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent(mockEvent(TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then argumentCaptor { @@ -223,12 +223,19 @@ internal class RumActionScopeAttributePropagationTest { // region Internal - private fun mockEvent(): RumRawEvent { + private fun mockEvent(timeOffset: Long = 0L): RumRawEvent { val event: RumRawEvent = mock() - whenever(event.eventTime) doReturn Time() + whenever(event.eventTime) doReturn timeWithOffset(timeOffset) return event } + private fun timeWithOffset(offsetMs: Long): Time { + return Time( + fakeEventTime.timestamp + offsetMs, + fakeEventTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(offsetMs) + ) + } + // endregion companion object { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt index b8e98b86ed..6eb650fce3 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScopeTest.kt @@ -227,11 +227,15 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap()) + fakeEvent = + RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap(), timeWithOffset(TEST_INACTIVITY_MS * 2)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -298,11 +302,15 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopResource(key2, statusCode, size, kind, emptyMap()) + fakeEvent = + RumRawEvent.StopResource(key2, statusCode, size, kind, emptyMap(), timeWithOffset(TEST_INACTIVITY_MS * 2)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verify(mockParentScope, never()).handleEvent(any(), any(), any(), any()) @@ -325,18 +333,22 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) fakeEvent = RumRawEvent.StopResourceWithError( key, statusCode, message, source, throwable, - emptyMap() + emptyMap(), + timeWithOffset(TEST_INACTIVITY_MS * 2) ) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -411,7 +423,6 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) fakeEvent = RumRawEvent.StopResourceWithStackTrace( key, statusCode, @@ -419,11 +430,16 @@ internal class RumActionScopeTest { source, stackTrace, errorType, - emptyMap() + emptyMap(), + timeWithOffset(TEST_INACTIVITY_MS * 2) ) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -494,18 +510,22 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) val fakeEvent2 = RumRawEvent.StopResourceWithError( key2, statusCode, message, source, throwable, - emptyMap() + emptyMap(), + timeWithOffset(TEST_INACTIVITY_MS * 2) ) val result2 = testedScope.handleEvent(fakeEvent2, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter) @@ -532,7 +552,6 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) val fakeEvent2 = RumRawEvent.StopResourceWithStackTrace( key2, statusCode, @@ -540,11 +559,16 @@ internal class RumActionScopeTest { source, stackTrace, errorType, - emptyMap() + emptyMap(), + timeWithOffset(TEST_INACTIVITY_MS * 2) ) val result2 = testedScope.handleEvent(fakeEvent2, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 4 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter) @@ -564,11 +588,15 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key.toString(), url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = mockEvent() + fakeEvent = mockEvent(TEST_INACTIVITY_MS * 2) key = null System.gc() - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 2 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -638,8 +666,12 @@ internal class RumActionScopeTest { attributes = emptyMap() ) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 2 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -703,8 +735,12 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.AddLongTask(duration, target) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 2 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1710,8 +1746,12 @@ internal class RumActionScopeTest { whenever(mockParentScope.getRumContext()) doReturn fakeParentContext // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1794,8 +1834,12 @@ internal class RumActionScopeTest { ) // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1860,10 +1904,14 @@ internal class RumActionScopeTest { expectedAttributes.putAll(fakeParentAttributes) expectedAttributes.putAll(fakeAttributes) whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes - Thread.sleep(TEST_INACTIVITY_MS) // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1918,11 +1966,13 @@ internal class RumActionScopeTest { @Test fun `M send event with user extra attributes W handleEvent(any)`() { - // Given - Thread.sleep(TEST_INACTIVITY_MS) - // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1983,8 +2033,12 @@ internal class RumActionScopeTest { testedScope.resourceCount = count // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2045,8 +2099,12 @@ internal class RumActionScopeTest { testedScope.errorCount = count // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2113,8 +2171,12 @@ internal class RumActionScopeTest { testedScope.crashCount = fatalCount // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2173,8 +2235,12 @@ internal class RumActionScopeTest { @Test fun `M send Action only once W handleEvent(any) twice`() { // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2480,8 +2546,7 @@ internal class RumActionScopeTest { testedScope.errorCount = 0 testedScope.crashCount = 0 testedScope.longTaskCount = 0 - Thread.sleep(TEST_INACTIVITY_MS) - fakeEvent = mockEvent() + fakeEvent = mockEvent(TEST_INACTIVITY_MS + 1) // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -2558,8 +2623,12 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 2 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -2576,8 +2645,12 @@ internal class RumActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_MAX_DURATION_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_MAX_DURATION_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2634,8 +2707,12 @@ internal class RumActionScopeTest { @Test fun `M send Action after timeout W handleEvent(any)`() { // When - Thread.sleep(TEST_INACTIVITY_MS) - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2691,9 +2768,13 @@ internal class RumActionScopeTest { @Test fun `M send custom Action after timeout W handleEvent(any) and no side effect`() { // When - Thread.sleep(TEST_INACTIVITY_MS) testedScope.type = RumActionType.CUSTOM - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2839,13 +2920,13 @@ internal class RumActionScopeTest { attributes = emptyMap() ) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) fakeEvent = RumRawEvent.StartAction( RumActionType.TAP, name, false, - emptyMap() + emptyMap(), + timeWithOffset(TEST_INACTIVITY_MS * 2 + 1) ) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -2959,12 +3040,19 @@ internal class RumActionScopeTest { // region Internal - private fun mockEvent(): RumRawEvent { + private fun mockEvent(timeOffset: Long = 0L): RumRawEvent { val event: RumRawEvent = mock() - whenever(event.eventTime) doReturn Time() + whenever(event.eventTime) doReturn timeWithOffset(timeOffset) return event } + private fun timeWithOffset(offsetMs: Long): Time { + return Time( + fakeEventTime.timestamp + offsetMs, + fakeEventTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(offsetMs) + ) + } + private fun resolveExpectedTimestamp(): Long { return fakeEventTime.timestamp + fakeServerOffset } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt index 2e40a20488..581d4abbc2 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt @@ -187,6 +187,7 @@ internal class RumApplicationScopeAttributePropagationTest { } whenever(rumMonitor.mockSdkCore.internalLogger) doReturn mock() + whenever(rumMonitor.mockSdkCore.timeProvider) doReturn mock() fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } testedScope = RumApplicationScope( applicationId = fakeApplicationId, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index 081d21ea3f..1c1e4fe01a 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -159,6 +159,7 @@ internal class RumApplicationScopeTest { whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockSdkCore.time) doReturn fakeTimeInfoAtScopeStart whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.timeProvider) doReturn mock() whenever(mockSlowFramesListener.resolveReport(any(), any(), any())) doReturn viewUIPerformanceReport whenever(mockAccessibilitySnapshotManager.getIfChanged()) doReturn mock() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt index 7e91dd5c50..909fd12602 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt @@ -176,11 +176,15 @@ internal class RumContinuousActionScopeTest { @LongForgery(1) count: Long ) { // Given - Thread.sleep(RumActionScope.ACTION_INACTIVITY_MS) testedScope.resourceCount = count // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(RumActionScope.ACTION_INACTIVITY_MS), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -192,11 +196,15 @@ internal class RumContinuousActionScopeTest { @LongForgery(1) count: Long ) { // Given - Thread.sleep(RumActionScope.ACTION_INACTIVITY_MS) testedScope.errorCount = count // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(RumActionScope.ACTION_INACTIVITY_MS), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -209,12 +217,16 @@ internal class RumContinuousActionScopeTest { @LongForgery(1) fatalCount: Long ) { // Given - Thread.sleep(RumActionScope.ACTION_INACTIVITY_MS) testedScope.errorCount = nonFatalCount + fatalCount testedScope.crashCount = fatalCount // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(RumActionScope.ACTION_INACTIVITY_MS), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -223,11 +235,13 @@ internal class RumContinuousActionScopeTest { @Test fun `M send Action after timeout W handleEvent(any)`() { - // Given - Thread.sleep(TEST_MAX_DURATION_MS) - // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_MAX_DURATION_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -291,13 +305,17 @@ internal class RumContinuousActionScopeTest { val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) expectedAttributes.putAll(attributes) + val stopActionTime = TEST_INACTIVITY_MS * 2 // When - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopAction(type, name, attributes) + fakeEvent = RumRawEvent.StopAction(type, name, attributes, timeWithOffset(stopActionTime)) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -364,13 +382,17 @@ internal class RumContinuousActionScopeTest { val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) expectedAttributes.putAll(attributes) + val stopActionTime = TEST_INACTIVITY_MS * 2 // When - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopAction(null, null, attributes) + fakeEvent = RumRawEvent.StopAction(null, null, attributes, timeWithOffset(stopActionTime)) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -440,14 +462,18 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap()) + val stopResourceTime = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap(), timeWithOffset(stopResourceTime)) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result4 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result4 = testedScope.handleEvent( + mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2 + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -519,21 +545,26 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) + val stopResourceTime = TEST_INACTIVITY_MS * 2 fakeEvent = RumRawEvent.StopResourceWithError( key, statusCode, message, source, throwable, - emptyMap() + emptyMap(), + timeWithOffset(stopResourceTime) ) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result4 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result4 = testedScope.handleEvent( + mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val expectedFrustrationCount = if (fakeType == RumActionType.TAP) { 1 } else { @@ -618,10 +649,10 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) + val stopResourceTime = TEST_INACTIVITY_MS * 2 fakeEvent = RumRawEvent.StopResourceWithStackTrace( key, statusCode, @@ -629,11 +660,16 @@ internal class RumContinuousActionScopeTest { source, stackTrace, errorType, - emptyMap() + emptyMap(), + timeWithOffset(stopResourceTime) ) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result4 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result4 = testedScope.handleEvent( + mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val expectedFrustrations = if (fakeType == RumActionType.TAP) { 1 } else { @@ -710,15 +746,15 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key.toString(), url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = mockEvent() + val gcTime = TEST_INACTIVITY_MS * 2 + fakeEvent = mockEvent(gcTime) @Suppress("UNUSED_VALUE") key = null System.gc() - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent(mockEvent(gcTime), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then argumentCaptor { @@ -792,12 +828,16 @@ internal class RumContinuousActionScopeTest { attributes = emptyMap() ) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + val stopActionTime = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val expectedFrustrations = if (fakeType == RumActionType.TAP) { 1 } else { @@ -1728,11 +1768,15 @@ internal class RumContinuousActionScopeTest { @Test fun `M send Action after threshold W handleEvent(StopAction+any) {viewTreeChangeCount!=0}`() { // When - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1820,7 +1864,7 @@ internal class RumContinuousActionScopeTest { rumSessionTypeOverride = fakeRumSessionType ) whenever(rumMonitor.mockInstance.getAttributes()) doReturn emptyMap() - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime fakeParentContext = fakeParentContext.copy( syntheticsTestId = fakeTestId, @@ -1830,8 +1874,12 @@ internal class RumContinuousActionScopeTest { // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1917,12 +1965,16 @@ internal class RumContinuousActionScopeTest { rumSessionTypeOverride = fakeRumSessionType ) whenever(rumMonitor.mockInstance.getAttributes()) doReturn emptyMap() - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -1987,12 +2039,16 @@ internal class RumContinuousActionScopeTest { expectedAttributes.putAll(fakeParentAttributes) expectedAttributes.putAll(fakeAttributes) whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2051,13 +2107,17 @@ internal class RumContinuousActionScopeTest { ) { // Given testedScope.resourceCount = count - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2121,14 +2181,18 @@ internal class RumContinuousActionScopeTest { ) { // Given testedScope.errorCount = count - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val expectedFrustrationCount = if (fakeType == RumActionType.TAP) 1 else 0 // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2198,14 +2262,18 @@ internal class RumContinuousActionScopeTest { // Given testedScope.errorCount = nonFatalCount + fatalCount testedScope.crashCount = fatalCount - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val expectedFrustrationCount = if (fakeType == RumActionType.TAP) 1 else 0 // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2269,14 +2337,23 @@ internal class RumContinuousActionScopeTest { @Test fun `M send Action only once W handleEvent(StopAction) + handleEvent(any) twice`() { // Given - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime // When val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + val result3 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2608,10 +2685,14 @@ internal class RumContinuousActionScopeTest { testedScope.errorCount = 0 testedScope.crashCount = 0 testedScope.longTaskCount = 0 - Thread.sleep(TEST_INACTIVITY_MS) // When - val result = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -2637,8 +2718,12 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -2655,11 +2740,15 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + val stopActionTime = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockWriter, mockParentScope) @@ -2675,10 +2764,14 @@ internal class RumContinuousActionScopeTest { @StringForgery(regex = "http(s?)://[a-z]+\\.com/[a-z]+") url: String ) { // When - fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) + fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap(), fakeEventTime) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_MAX_DURATION_MS) - val result2 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result2 = testedScope.handleEvent( + mockEvent(TEST_MAX_DURATION_MS + 1), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2740,11 +2833,15 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_INACTIVITY_MS * 2) - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap()) + val stopActionTime = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(TEST_MAX_DURATION_MS) - val result3 = testedScope.handleEvent(mockEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent( + mockEvent(stopActionTime + TEST_MAX_DURATION_MS), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -2862,9 +2959,16 @@ internal class RumContinuousActionScopeTest { return fakeEventTime.timestamp + fakeServerOffset } - private fun mockEvent(): RumRawEvent { + private fun timeWithOffset(offsetMs: Long): Time { + return Time( + fakeEventTime.timestamp + offsetMs, + fakeEventTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(offsetMs) + ) + } + + private fun mockEvent(timeOffset: Long = 0L): RumRawEvent { val event: RumRawEvent = mock() - whenever(event.eventTime) doReturn Time() + whenever(event.eventTime) doReturn timeWithOffset(timeOffset) return event } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt index d2b430bae5..7c95d6a732 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt @@ -42,12 +42,13 @@ internal fun Forge.stopViewEvent(): RumRawEvent.StopView { ) } -internal fun Forge.startActionEvent(continuous: Boolean? = null): RumRawEvent.StartAction { +internal fun Forge.startActionEvent(continuous: Boolean? = null, eventTime: Time = Time()): RumRawEvent.StartAction { return RumRawEvent.StartAction( type = aValueFrom(RumActionType::class.java), name = anAlphabeticalString(), waitForStop = continuous ?: aBool(), - attributes = exhaustiveAttributes() + attributes = exhaustiveAttributes(), + eventTime = eventTime ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeAttributePropagationTest.kt index 87dc8f0de8..1d05650a0c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeAttributePropagationTest.kt @@ -61,6 +61,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -261,8 +262,12 @@ internal class RumResourceScopeAttributePropagationTest { ) // When - Thread.sleep(RESOURCE_DURATION_MS) - testedScope.handleEvent(event, fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + event.copy(eventTime = timeWithOffset(RESOURCE_DURATION_MS)), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then argumentCaptor { @@ -274,6 +279,17 @@ internal class RumResourceScopeAttributePropagationTest { // endregion + // region Internal + + private fun timeWithOffset(offsetMs: Long): Time { + return Time( + fakeEventTime.timestamp + offsetMs, + fakeEventTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(offsetMs) + ) + } + + // endregion + companion object { val rumMonitor = GlobalRumMonitorTestConfiguration() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt index b79be9c750..b0d925d515 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScopeTest.kt @@ -272,8 +272,8 @@ internal class RumResourceScopeTest { expectedAttributes.putAll(attributes) // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -344,8 +344,8 @@ internal class RumResourceScopeTest { expectedAttributes.putAll(attributes) // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -433,8 +433,8 @@ internal class RumResourceScopeTest { expectedAttributes.putAll(attributes) // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -512,8 +512,8 @@ internal class RumResourceScopeTest { attributes[RumAttributes.RULE_PSR] = fakeRulePsr // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -585,8 +585,8 @@ internal class RumResourceScopeTest { whenever(mockParentScope.getRumContext()) doReturn context // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -678,8 +678,8 @@ internal class RumResourceScopeTest { ) // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -744,8 +744,8 @@ internal class RumResourceScopeTest { @LongForgery(0, 1024) size: Long ) { // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -808,8 +808,8 @@ internal class RumResourceScopeTest { @LongForgery(0, 1024) size: Long ) { // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -863,8 +863,8 @@ internal class RumResourceScopeTest { @LongForgery(0, 1024) size: Long ) { // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -883,8 +883,8 @@ internal class RumResourceScopeTest { @LongForgery(0, 1024) size: Long ) { // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, null, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, null, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -930,8 +930,8 @@ internal class RumResourceScopeTest { whenever(rumMonitor.mockInstance.getAttributes()) doReturn emptyMap() // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1004,8 +1004,8 @@ internal class RumResourceScopeTest { whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap()) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, emptyMap(), timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1078,8 +1078,8 @@ internal class RumResourceScopeTest { // When mockEvent = RumRawEvent.AddResourceTiming(fakeKey, timing) val resultTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1154,8 +1154,8 @@ internal class RumResourceScopeTest { // When mockEvent = RumRawEvent.AddResourceTiming("not_the_$fakeKey", timing) val resultTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1232,11 +1232,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1316,11 +1316,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1395,11 +1395,11 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1474,7 +1474,8 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) fakeParentContext = fakeParentContext.copy( syntheticsTestId = fakeTestId, @@ -1498,7 +1499,6 @@ internal class RumResourceScopeTest { ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1581,7 +1581,8 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) testedScope = RumResourceScope( parentScope = mockParentScope, @@ -1600,7 +1601,6 @@ internal class RumResourceScopeTest { ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1691,11 +1691,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1791,11 +1791,11 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1873,11 +1873,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -1957,11 +1957,11 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2038,12 +2038,12 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) whenever(mockParentScope.getRumContext()) doReturn context // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2122,12 +2122,12 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) whenever(mockParentScope.getRumContext()) doReturn context // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2209,11 +2209,11 @@ internal class RumResourceScopeTest { message, source, throwable, - errorAttributes + errorAttributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2298,11 +2298,11 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - errorAttributes + errorAttributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -2369,9 +2369,15 @@ internal class RumResourceScopeTest { val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeResourceAttributes) expectedAttributes.putAll(attributes) - mockEvent = RumRawEvent.StopResource("not_the_$fakeKey", statusCode, size, kind, attributes) + mockEvent = RumRawEvent.StopResource( + "not_the_$fakeKey", + statusCode, + size, + kind, + attributes, + timeWithOffset(RESOURCE_DURATION_MS) + ) - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) verify(mockParentScope, atMost(1)).getRumContext() @@ -2395,10 +2401,10 @@ internal class RumResourceScopeTest { message, source, throwable, - emptyMap() + emptyMap(), + timeWithOffset(RESOURCE_DURATION_MS) ) - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) verify(mockParentScope, atMost(1)).getRumContext() @@ -2425,10 +2431,10 @@ internal class RumResourceScopeTest { source, stackTrace, errorType, - emptyMap() + emptyMap(), + timeWithOffset(RESOURCE_DURATION_MS) ) - Thread.sleep(RESOURCE_DURATION_MS) val result = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) verify(mockParentScope, atMost(1)).getRumContext() @@ -2454,8 +2460,8 @@ internal class RumResourceScopeTest { mockEvent = RumRawEvent.WaitForResourceTiming(fakeKey) val resultWaitForTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val resultStop = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) verify(mockParentScope, atMost(1)).getRumContext() @@ -2482,8 +2488,8 @@ internal class RumResourceScopeTest { mockEvent = RumRawEvent.WaitForResourceTiming("not_the_$fakeKey") val resultWaitForTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val resultStop = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) argumentCaptor { @@ -2553,8 +2559,8 @@ internal class RumResourceScopeTest { testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) mockEvent = RumRawEvent.AddResourceTiming(fakeKey, timing) val resultTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) val resultStop = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) argumentCaptor { @@ -2625,8 +2631,7 @@ internal class RumResourceScopeTest { testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) val resultStop = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.AddResourceTiming(fakeKey, timing) + mockEvent = RumRawEvent.AddResourceTiming(fakeKey, timing, timeWithOffset(RESOURCE_DURATION_MS)) val resultTiming = testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) argumentCaptor { @@ -3396,8 +3401,8 @@ internal class RumResourceScopeTest { val attributes = forge.exhaustiveAttributes(excludedKeys = fakeResourceAttributes.keys) // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3420,8 +3425,8 @@ internal class RumResourceScopeTest { whenever(mockWriter.write(eq(mockEventBatchWriter), isA(), eq(EventType.DEFAULT))) doReturn false // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3446,8 +3451,8 @@ internal class RumResourceScopeTest { ) doThrow forge.anException() // When - Thread.sleep(RESOURCE_DURATION_MS) - mockEvent = RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes) + mockEvent = + RumRawEvent.StopResource(fakeKey, statusCode, size, kind, attributes, timeWithOffset(RESOURCE_DURATION_MS)) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3474,11 +3479,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3506,11 +3511,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3540,11 +3545,11 @@ internal class RumResourceScopeTest { message, source, throwable, - attributes + attributes, + timeWithOffset(RESOURCE_DURATION_MS) ) // When - Thread.sleep(RESOURCE_DURATION_MS) testedScope.handleEvent(mockEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -3565,6 +3570,13 @@ internal class RumResourceScopeTest { return event } + private fun timeWithOffset(offsetMs: Long): Time { + return Time( + fakeEventTime.timestamp + offsetMs, + fakeEventTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(offsetMs) + ) + } + private fun resolveExpectedTimestamp(): Long { return fakeEventTime.timestamp + fakeServerOffset } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index baed3ee54d..34cef10406 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -158,6 +158,7 @@ internal class RumSessionScopeAttributePropagationTest { whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes.toMutableMap() whenever(mockSdkCore.internalLogger) doReturn mock() + whenever(mockSdkCore.timeProvider) doReturn mock() fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } testedScope = RumSessionScope( parentScope = mockParentScope, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 002fb8bdbf..89a347948b 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -18,10 +18,12 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.api.storage.NoOpDataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.accessibility.AccessibilitySnapshotManager import com.datadog.android.rum.internal.domain.battery.BatteryInfo import com.datadog.android.rum.internal.domain.display.DisplayInfo @@ -39,6 +41,7 @@ import com.datadog.android.rum.model.RumVitalAppLaunchEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.forge.Configurator import com.datadog.tools.unit.forge.exhaustiveAttributes +import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery @@ -183,8 +186,11 @@ internal class RumSessionScopeTest { @Mock private lateinit var mockRumSessionScopeStartupManager: RumSessionScopeStartupManager + private lateinit var stubTimeProvider: StubTimeProvider + @BeforeEach fun `set up`(forge: Forge) { + stubTimeProvider = StubTimeProvider(elapsedTimeNs = TEST_INACTIVITY_NS + 1) fakeInitialViewEvent = forge.startViewEvent() whenever(mockParentScope.getRumContext()).doAnswer { fakeParentContext } @@ -193,6 +199,7 @@ internal class RumSessionScopeTest { mockSessionReplayFeatureScope whenever(mockSdkCore.time) doReturn (fakeTimeInfo) whenever(mockSdkCore.internalLogger) doReturn mock() + whenever(mockSdkCore.timeProvider) doReturn stubTimeProvider fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } @@ -637,7 +644,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -658,7 +665,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -678,7 +685,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -701,7 +708,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -725,7 +732,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.startResourceEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -749,7 +756,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -772,7 +779,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.startResourceEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -794,7 +801,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val result = testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -810,16 +817,26 @@ internal class RumSessionScopeTest { forge: Forge ) { // Given - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + forge.startViewEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val initialContext = testedScope.getRumContext() val repeatCount = (TEST_MAX_DURATION_MS / TEST_SLEEP_MS) + 1 repeat(repeatCount.toInt()) { - Thread.sleep(TEST_SLEEP_MS) - testedScope.handleEvent(forge.startActionEvent(false), fakeDatadogContext, mockEventWriteScope, mockWriter) + advanceTimeByMs(TEST_SLEEP_MS) + testedScope.handleEvent( + forge.startActionEvent(continuous = false, eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) } // When - Thread.sleep(TEST_SLEEP_MS) + advanceTimeByMs(TEST_SLEEP_MS) val result = testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -837,18 +854,32 @@ internal class RumSessionScopeTest { forge: Forge ) { // Given - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + forge.startViewEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val initialContext = testedScope.getRumContext() val repeatCount = (TEST_MAX_DURATION_MS / TEST_SLEEP_MS) + 1 repeat(repeatCount.toInt()) { - Thread.sleep(TEST_SLEEP_MS) - testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + advanceTimeByMs(TEST_SLEEP_MS) + testedScope.handleEvent( + forge.startActionEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) } // When - Thread.sleep(TEST_SLEEP_MS) - val result = - testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + advanceTimeByMs(TEST_SLEEP_MS) + val result = testedScope.handleEvent( + forge.startActionEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val context = testedScope.getRumContext() // Then @@ -865,18 +896,32 @@ internal class RumSessionScopeTest { forge: Forge ) { // Given - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + forge.startViewEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val initialContext = testedScope.getRumContext() val repeatCount = (TEST_MAX_DURATION_MS / TEST_SLEEP_MS) + 1 repeat(repeatCount.toInt()) { - Thread.sleep(TEST_SLEEP_MS) - testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + advanceTimeByMs(TEST_SLEEP_MS) + testedScope.handleEvent( + forge.startActionEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) } // When - Thread.sleep(TEST_SLEEP_MS) - val result = - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + advanceTimeByMs(TEST_SLEEP_MS) + val result = testedScope.handleEvent( + forge.startViewEvent(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) val context = testedScope.getRumContext() // Then @@ -980,12 +1025,12 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() val repeatCount = (TEST_MAX_DURATION_MS / TEST_SLEEP_MS) + 1 repeat(repeatCount.toInt()) { - Thread.sleep(TEST_SLEEP_MS) + advanceTimeByMs(TEST_SLEEP_MS) testedScope.handleEvent(forge.startActionEvent(false), fakeDatadogContext, mockEventWriteScope, mockWriter) } // When - Thread.sleep(TEST_MAX_DURATION_MS) + advanceTimeByMs(TEST_MAX_DURATION_MS) val newEvent = forge.startViewEvent() val result = testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -1009,7 +1054,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val newEvent = forge.startViewEvent() val result = testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -1058,12 +1103,12 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() val repeatCount = (TEST_MAX_DURATION_MS / TEST_SLEEP_MS) + 1 repeat(repeatCount.toInt()) { - Thread.sleep(TEST_SLEEP_MS) + advanceTimeByMs(TEST_SLEEP_MS) testedScope.handleEvent(forge.startActionEvent(false), fakeDatadogContext, mockEventWriteScope, mockWriter) } // When - Thread.sleep(TEST_MAX_DURATION_MS) + advanceTimeByMs(TEST_MAX_DURATION_MS) val newEvent = forge.startViewEvent() val result = testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -1088,7 +1133,7 @@ internal class RumSessionScopeTest { val initialContext = testedScope.getRumContext() // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val newEvent = forge.startViewEvent() val result = testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val context = testedScope.getRumContext() @@ -1219,7 +1264,7 @@ internal class RumSessionScopeTest { val firstSessionId = testedScope.getRumContext().sessionId // When - Thread.sleep(TEST_MAX_DURATION_MS) + advanceTimeByMs(TEST_MAX_DURATION_MS) val newEvent = forge.startViewEvent() testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val secondSessionId = testedScope.getRumContext().sessionId @@ -1256,7 +1301,7 @@ internal class RumSessionScopeTest { val firstSessionId = testedScope.getRumContext().sessionId // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val newEvent = forge.startViewEvent() testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val secondSessionId = testedScope.getRumContext().sessionId @@ -1345,7 +1390,7 @@ internal class RumSessionScopeTest { val firstSessionId = testedScope.getRumContext().sessionId // When - Thread.sleep(TEST_MAX_DURATION_MS) + advanceTimeByMs(TEST_MAX_DURATION_MS) val newEvent = forge.startViewEvent() testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val secondSessionId = testedScope.getRumContext().sessionId @@ -1384,7 +1429,7 @@ internal class RumSessionScopeTest { val firstSessionId = testedScope.getRumContext().sessionId // When - Thread.sleep(TEST_INACTIVITY_MS) + advanceTimeByMs(TEST_INACTIVITY_MS) val newEvent = forge.startViewEvent() testedScope.handleEvent(newEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val secondSessionId = testedScope.getRumContext().sessionId @@ -1588,6 +1633,17 @@ internal class RumSessionScopeTest { // region Internal + private fun advanceTimeByMs(ms: Long) { + stubTimeProvider.elapsedTimeNs += TimeUnit.MILLISECONDS.toNanos(ms) + } + + private fun currentFakeTime(): Time { + return Time( + timestamp = stubTimeProvider.deviceTimestampMs, + nanoTime = stubTimeProvider.elapsedTimeNs + ) + } + private fun initializeTestedScope( sampleRate: Float = 100f, withMockChildScope: Boolean = true, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt index 6d1d8003e2..9988c48425 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt @@ -348,19 +348,23 @@ internal class RumViewManagerScopeTest { @Test fun `M send gap message W handleEvent(StopView) + handleEvent(StartView)`( - @LongForgery(10, 30) fakeSleepMs: Long, + @LongForgery(10, 30) fakeGapMs: Long, forge: Forge ) { // Given - val firstViewEvent = forge.startViewEvent() + val baseTime = Time() + val firstViewEvent = forge.startViewEvent(eventTime = baseTime) testedScope.applicationDisplayed = true testedScope.handleEvent(firstViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopFirstViewEvent = RumRawEvent.StopView(firstViewEvent.key, emptyMap()) + val stopFirstViewEvent = RumRawEvent.StopView(firstViewEvent.key, emptyMap(), baseTime) testedScope.handleEvent(stopFirstViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // When - Thread.sleep(fakeSleepMs) - val secondViewEvent = forge.startViewEvent() + val secondViewTime = Time( + baseTime.timestamp + fakeGapMs, + baseTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(fakeGapMs) + ) + val secondViewEvent = forge.startViewEvent(eventTime = secondViewTime) testedScope.handleEvent(secondViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then @@ -376,9 +380,8 @@ internal class RumViewManagerScopeTest { assertThat(additionalPropertiesCaptor.firstValue).containsKey(RumViewManagerScope.ATTR_GAP_BETWEEN_VIEWS) val gapNs = additionalPropertiesCaptor.firstValue[RumViewManagerScope.ATTR_GAP_BETWEEN_VIEWS] as Long - val minNs = TimeUnit.MILLISECONDS.toNanos(fakeSleepMs) - val maxNs = TimeUnit.MILLISECONDS.toNanos(fakeSleepMs + 15) - assertThat(gapNs).isBetween(minNs, maxNs) + val expectedGapNs = TimeUnit.MILLISECONDS.toNanos(fakeGapMs) + assertThat(gapNs).isEqualTo(expectedGapNs) assertThat(messageBuilderCaptor.firstValue()).isEqualTo("[Mobile Metric] Gap between views") } @@ -387,13 +390,17 @@ internal class RumViewManagerScopeTest { forge: Forge ) { // Given - val firstViewEvent = forge.startViewEvent() + val baseTime = Time() + val firstViewEvent = forge.startViewEvent(eventTime = baseTime) testedScope.applicationDisplayed = true testedScope.handleEvent(firstViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // When - Thread.sleep(15) - val secondViewEvent = forge.startViewEvent() + val secondViewTime = Time( + baseTime.timestamp + 15, + baseTime.nanoTime + TimeUnit.MILLISECONDS.toNanos(15) + ) + val secondViewEvent = forge.startViewEvent(eventTime = secondViewTime) testedScope.handleEvent(secondViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) // Then diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt index bd8bbe8bb5..827f8b83d5 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt @@ -16,6 +16,7 @@ import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.getStaticValue import com.datadog.tools.unit.setStaticValue +import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -31,14 +32,14 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -54,10 +55,18 @@ internal class MainLooperLongTaskStrategyTest : ObjectTest>>>> Dispatching to $target $callback: $what") - Thread.sleep(duration) + stubTimeProvider.elapsedTimeNs += fakeDurationNs testedPrinter.println("<<<<< Finished to $target $callback") // Then - argumentCaptor { - verify(rumMonitor.mockInstance as AdvancedRumMonitor) - .addLongTask(capture(), eq("$target $callback: $what")) - val capturedMs = TimeUnit.NANOSECONDS.toMillis(firstValue) - assertThat(capturedMs) - .isBetween(duration, duration + 16L) - } + verify(rumMonitor.mockInstance as AdvancedRumMonitor) + .addLongTask(eq(fakeDurationNs), eq("$target $callback: $what")) } @Test fun `M do not report short task W print()`( - @LongForgery(min = 0, max = MAX_SHORT_TASK_DURATION_MS) duration: Long, + @LongForgery(min = 0, max = TEST_THRESHOLD_NS) fakeDurationNs: Long, @StringForgery target: String, @StringForgery callback: String, @IntForgery what: Int ) { - // Given - // When testedPrinter.println(">>>>> Dispatching to $target $callback: $what") - Thread.sleep(duration / 4) + stubTimeProvider.elapsedTimeNs += fakeDurationNs testedPrinter.println("<<<<< Finished to $target $callback") // Then @@ -163,8 +163,7 @@ internal class MainLooperLongTaskStrategyTest : ObjectTest, + private val timeProvider: TimeProvider, private val telemetrySampleRate: Float = TELEMETRY_SAMPLE_RATE_PERCENT, private val sampler: RateBasedSampler = RateBasedSampler(telemetrySampleRate) ) : DataQueueHandler { @@ -54,7 +56,8 @@ internal class RecordedDataQueueHandler( recordedQueuedItemContext = rumContextData, identifier = identifier, resourceData = resourceData, - mimeType + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs(), + mimeType = mimeType ) insertIntoRecordedDataQueue(item) @@ -71,6 +74,7 @@ internal class RecordedDataQueueHandler( val item = TouchEventRecordedDataQueueItem( recordedQueuedItemContext = rumContextData, + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs(), touchData = pointerInteractions ) @@ -86,7 +90,8 @@ internal class RecordedDataQueueHandler( val item = SnapshotRecordedDataQueueItem( recordedQueuedItemContext = rumContextData, - systemInformation = systemInformation + systemInformation = systemInformation, + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs() ) insertIntoRecordedDataQueue(item) @@ -128,7 +133,7 @@ internal class RecordedDataQueueHandler( val nextItem = recordedDataQueue.peek() if (nextItem != null) { - val nextItemAgeInNs = System.nanoTime() - nextItem.creationTimeStampInNs + val nextItemAgeInNs = timeProvider.getDeviceElapsedTimeNs() - nextItem.creationTimestampInNs if (!nextItem.isValid()) { if (sampler.sample(Unit)) { logInvalidQueueItemException(nextItem) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueItem.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueItem.kt index 0e02939187..77679b582b 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueItem.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueItem.kt @@ -10,7 +10,7 @@ import com.datadog.android.sessionreplay.internal.processor.RecordedQueuedItemCo internal abstract class RecordedDataQueueItem( internal val recordedQueuedItemContext: RecordedQueuedItemContext, - internal val creationTimeStampInNs: Long = System.nanoTime() + internal val creationTimestampInNs: Long ) { internal abstract fun isValid(): Boolean diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItem.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItem.kt index 4b68fbbb19..575cb9e771 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItem.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItem.kt @@ -12,8 +12,9 @@ internal class ResourceRecordedDataQueueItem( recordedQueuedItemContext: RecordedQueuedItemContext, val identifier: String, val resourceData: ByteArray, + creationTimestampInNs: Long, val mimeType: String? = null -) : RecordedDataQueueItem(recordedQueuedItemContext) { +) : RecordedDataQueueItem(recordedQueuedItemContext, creationTimestampInNs) { override fun isValid(): Boolean { return resourceData.isNotEmpty() diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItem.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItem.kt index 5139dac9ea..2780e8594d 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItem.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItem.kt @@ -13,8 +13,9 @@ import java.util.concurrent.atomic.AtomicInteger internal class SnapshotRecordedDataQueueItem( recordedQueuedItemContext: RecordedQueuedItemContext, - internal val systemInformation: SystemInformation -) : RecordedDataQueueItem(recordedQueuedItemContext) { + internal val systemInformation: SystemInformation, + creationTimestampInNs: Long +) : RecordedDataQueueItem(recordedQueuedItemContext, creationTimestampInNs) { @Volatile internal var nodes = emptyList() @Volatile internal var isFinishedTraversal = false diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItem.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItem.kt index 53090e48b4..95f34df668 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItem.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItem.kt @@ -11,8 +11,9 @@ import com.datadog.android.sessionreplay.model.MobileSegment internal class TouchEventRecordedDataQueueItem( recordedQueuedItemContext: RecordedQueuedItemContext, + creationTimestampInNs: Long, internal val touchData: List = emptyList() -) : RecordedDataQueueItem(recordedQueuedItemContext) { +) : RecordedDataQueueItem(recordedQueuedItemContext, creationTimestampInNs) { override fun isValid(): Boolean { return touchData.isNotEmpty() diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt index 12db3af535..0d6712dc4a 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt @@ -8,6 +8,7 @@ package com.datadog.android.sessionreplay.internal.processor import android.content.res.Configuration import androidx.annotation.WorkerThread +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.sessionreplay.internal.async.ResourceRecordedDataQueueItem import com.datadog.android.sessionreplay.internal.async.SnapshotRecordedDataQueueItem import com.datadog.android.sessionreplay.internal.async.TouchEventRecordedDataQueueItem @@ -27,6 +28,7 @@ internal class RecordedDataProcessor( private val resourcesWriter: ResourcesWriter, private val writer: RecordWriter, private val mutationResolver: MutationResolver, + private val timeProvider: TimeProvider, private val nodeFlattener: NodeFlattener = NodeFlattener() ) : Processor { private var prevSnapshot: List = emptyList() @@ -162,8 +164,8 @@ internal class RecordedDataProcessor( } private fun isTimeForFullSnapshot(): Boolean { - return if (System.nanoTime() - lastSnapshotTimestamp >= FULL_SNAPSHOT_INTERVAL_IN_NS) { - lastSnapshotTimestamp = System.nanoTime() + return if (timeProvider.getDeviceElapsedTimeNs() - lastSnapshotTimestamp >= FULL_SNAPSHOT_INTERVAL_IN_NS) { + lastSnapshotTimestamp = timeProvider.getDeviceElapsedTimeNs() true } else { false diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt index f2cab2e730..8a069b99ef 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt @@ -29,11 +29,11 @@ internal class Debouncer( // reason why we are not initializing this in the constructor is that in case the // component was initialized earlier than the first debounce request was requested // it will execute the runnable directly and will not pass through the handler. - lastTimeRecordWasPerformed = System.nanoTime() + lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNs() firstRequest = false } handler.removeCallbacksAndMessages(null) - val timePassedSinceLastExecution = System.nanoTime() - lastTimeRecordWasPerformed + val timePassedSinceLastExecution = sdkCore.timeProvider.getDeviceElapsedTimeNs() - lastTimeRecordWasPerformed if (timePassedSinceLastExecution >= maxRecordDelayInNs) { executeRunnable(runnable) } else { @@ -49,14 +49,14 @@ internal class Debouncer( } else { runnable.run() } - lastTimeRecordWasPerformed = System.nanoTime() + lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNs() } private fun runInTimeBalance(block: () -> Unit) { - if (timeBank.updateAndCheck(System.nanoTime())) { - val startTimeInNano = System.nanoTime() + if (timeBank.updateAndCheck(sdkCore.timeProvider.getDeviceElapsedTimeNs())) { + val startTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNs() block() - val endTimeInNano = System.nanoTime() + val endTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNs() timeBank.consume(endTimeInNano - startTimeInNano) } else { logSkippedFrame() diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt index 74e4415d6d..b50b0c6197 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt @@ -101,7 +101,8 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { resourceDataStoreManager, resourcesWriter, recordWriter, - MutationResolver(internalLogger) + MutationResolver(internalLogger), + timeProvider ) this.appContext = appContext @@ -116,7 +117,8 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { executorService = sdkCore.createSingleThreadExecutorService( "sr-event-processing" ), - recordedDataQueue = ConcurrentLinkedQueue() + recordedDataQueue = ConcurrentLinkedQueue(), + timeProvider = timeProvider ) val viewIdentifierResolver: ViewIdentifierResolver = DefaultViewIdentifierResolver diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt index ed35e3ff5f..56334fd555 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt @@ -49,7 +49,7 @@ internal class RecorderWindowCallback( private val pixelsDensity = appContext.resources.displayMetrics.density internal val pointerInteractions: MutableList = LinkedList() private var lastOnMoveUpdateTimeInNs: Long = 0L - private var lastPerformedFlushTimeInNs: Long = System.nanoTime() + private var lastPerformedFlushTimeInNs: Long = timeProvider.getDeviceElapsedTimeNs() private var shouldRecordMotion: Boolean = false // region Window.Callback @@ -99,19 +99,19 @@ internal class RecorderWindowCallback( when (event.action.and(MotionEvent.ACTION_MASK)) { MotionEvent.ACTION_DOWN -> { // reset the flush time to avoid flush in the next event - lastPerformedFlushTimeInNs = System.nanoTime() + lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNs() updatePositions(event, MobileSegment.PointerEventType.DOWN) // reset the on move update time in order to take into account the first move event lastOnMoveUpdateTimeInNs = 0 } MotionEvent.ACTION_MOVE -> { - if (System.nanoTime() - lastOnMoveUpdateTimeInNs >= motionUpdateThresholdInNs) { + if (timeProvider.getDeviceElapsedTimeNs() - lastOnMoveUpdateTimeInNs >= motionUpdateThresholdInNs) { updatePositions(event, MobileSegment.PointerEventType.MOVE) - lastOnMoveUpdateTimeInNs = System.nanoTime() + lastOnMoveUpdateTimeInNs = timeProvider.getDeviceElapsedTimeNs() } // make sure we flush from time to time to avoid glitches in the player - if (System.nanoTime() - lastPerformedFlushTimeInNs >= + if (timeProvider.getDeviceElapsedTimeNs() - lastPerformedFlushTimeInNs >= flushPositionBufferThresholdInNs ) { flushPositions() @@ -161,7 +161,7 @@ internal class RecorderWindowCallback( } pointerInteractions.clear() - lastPerformedFlushTimeInNs = System.nanoTime() + lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNs() } private fun logOrRethrowWrappedCallbackException(e: NullPointerException) { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt index de1e0837b9..d90d4b36c7 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt @@ -27,7 +27,7 @@ internal class ResourceDataStoreManager( ) { @Suppress("UnsafeThirdPartyFunctionCall") // map is initialized empty private val knownResources = Collections.newSetFromMap(ConcurrentHashMap()) - private val storedLastUpdateDateNs = AtomicLong(System.nanoTime()) + private val storedLastUpdateDateNs = AtomicLong(featureSdkCore.timeProvider.getDeviceElapsedTimeNs()) private val isInitialized = AtomicBoolean(false) // has init finished executing its async actions init { @@ -129,7 +129,7 @@ internal class ResourceDataStoreManager( ) private fun didDataStoreExpire(lastUpdateDate: Long): Boolean = - System.nanoTime() - lastUpdateDate > DATASTORE_EXPIRATION_NS + featureSdkCore.timeProvider.getDeviceElapsedTimeNs() - lastUpdateDate > DATASTORE_EXPIRATION_NS // endregion diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/ResourceRecordedDataQueueItemForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/ResourceRecordedDataQueueItemForgeryFactory.kt index dce9507019..81c2d0c4ca 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/ResourceRecordedDataQueueItemForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/ResourceRecordedDataQueueItemForgeryFactory.kt @@ -16,7 +16,8 @@ internal class ResourceRecordedDataQueueItemForgeryFactory : ForgeryFactory().toString(), - resourceData = forge.aString().toByteArray() + resourceData = forge.aString().toByteArray(), + creationTimestampInNs = forge.aLong(min = 0) ) } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SnapshotRecordedDataQueueItemForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SnapshotRecordedDataQueueItemForgeryFactory.kt index 6d9cb9546a..3154495af5 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SnapshotRecordedDataQueueItemForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SnapshotRecordedDataQueueItemForgeryFactory.kt @@ -14,7 +14,8 @@ internal class SnapshotRecordedDataQueueItemForgeryFactory : ForgeryFactory + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = RecordedDataQueueHandler.MAX_DELAY_NS * 2) + var fakeCurrentTimeNs: Long = 0L + @Forgery lateinit var fakeRecordedQueuedItemContext: RecordedQueuedItemContext @@ -127,6 +135,7 @@ internal class RecordedDataQueueHandlerTest { fakeNodeData = forge.aList { mock() } whenever(mockRateBasedSampler.sample(any())).thenReturn(true) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) testedHandler = RecordedDataQueueHandler( processor = mockProcessor, @@ -134,6 +143,7 @@ internal class RecordedDataQueueHandlerTest { executorService = spyExecutorService, internalLogger = mockInternalLogger, recordedDataQueue = fakeRecordedDataQueue, + timeProvider = mockTimeProvider, telemetrySampleRate = 1f, sampler = mockRateBasedSampler ) @@ -158,6 +168,7 @@ internal class RecordedDataQueueHandlerTest { executorService = spyExecutorService, internalLogger = mockInternalLogger, recordedDataQueue = mockQueue, + timeProvider = mockTimeProvider, telemetrySampleRate = 1f, sampler = mockRateBasedSampler ) @@ -289,9 +300,9 @@ internal class RecordedDataQueueHandlerTest { @Mock mockSnapshotItem: SnapshotRecordedDataQueueItem ) { // Given + val expiredTime = fakeCurrentTimeNs - RecordedDataQueueHandler.MAX_DELAY_NS - 1 mockSnapshotItem.apply { - val expiredTime = System.nanoTime() - RecordedDataQueueHandler.MAX_DELAY_NS - whenever(creationTimeStampInNs).thenReturn(expiredTime) + whenever(creationTimestampInNs).thenReturn(expiredTime) whenever(isValid()).thenReturn(true) whenever(isReady()).thenReturn(true) whenever(nodes).thenReturn(fakeNodeData) @@ -532,7 +543,7 @@ internal class RecordedDataQueueHandlerTest { mockSnapshotItem1.apply { whenever(systemInformation).thenReturn(mockSystemInformation) whenever(nodes).thenReturn(fakeNodeData) - whenever(creationTimeStampInNs).thenReturn(System.nanoTime()) + whenever(creationTimestampInNs).thenReturn(fakeCurrentTimeNs) whenever(isValid()).thenReturn(true) whenever(isReady()).thenReturn(true) } @@ -541,7 +552,7 @@ internal class RecordedDataQueueHandlerTest { mockSnapshotItem2.apply { whenever(systemInformation).thenReturn(mockSystemInformation) whenever(nodes).thenReturn(fakeNodeData) - whenever(creationTimeStampInNs).thenReturn(System.nanoTime()) + whenever(creationTimestampInNs).thenReturn(fakeCurrentTimeNs) whenever(isValid()).thenReturn(true) whenever(isReady()).thenReturn(false) } @@ -550,7 +561,7 @@ internal class RecordedDataQueueHandlerTest { mockSnapshotItem3.apply { whenever(systemInformation).thenReturn(mockSystemInformation) whenever(nodes).thenReturn(fakeNodeData) - whenever(creationTimeStampInNs).thenReturn(System.nanoTime()) + whenever(creationTimestampInNs).thenReturn(fakeCurrentTimeNs) whenever(isValid()).thenReturn(true) whenever(isReady()).thenReturn(true) } @@ -685,6 +696,7 @@ internal class RecordedDataQueueHandlerTest { executorService = spyExecutorService, internalLogger = mockInternalLogger, recordedDataQueue = fakeRecordedDataQueue, + timeProvider = mockTimeProvider, telemetrySampleRate = 0f, sampler = mockRateBasedSampler ) @@ -721,19 +733,21 @@ internal class RecordedDataQueueHandlerTest { @Mock mockSnapshotItem: SnapshotRecordedDataQueueItem ) { // Given + val expiredTime = fakeCurrentTimeNs - RecordedDataQueueHandler.MAX_DELAY_NS - 1 + testedHandler = RecordedDataQueueHandler( processor = mockProcessor, rumContextDataHandler = mockRumContextDataHandler, executorService = spyExecutorService, internalLogger = mockInternalLogger, recordedDataQueue = fakeRecordedDataQueue, + timeProvider = mockTimeProvider, telemetrySampleRate = 0f, sampler = mockRateBasedSampler ) mockSnapshotItem.apply { - val expiredTime = System.nanoTime() - RecordedDataQueueHandler.MAX_DELAY_NS - whenever(creationTimeStampInNs).thenReturn(expiredTime) + whenever(creationTimestampInNs).thenReturn(expiredTime) whenever(isValid()).thenReturn(true) whenever(isReady()).thenReturn(true) whenever(nodes).thenReturn(fakeNodeData) @@ -764,7 +778,7 @@ internal class RecordedDataQueueHandlerTest { private fun addSnapshotItemToQueue(): SnapshotRecordedDataQueueItem { val newRumContext = RecordedQueuedItemContext( - timestamp = System.currentTimeMillis(), + timestamp = fakeRecordedQueuedItemContext.timestamp, newRumContext = fakeRecordedQueuedItemContext.newRumContext ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItemTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItemTest.kt index 172a5e83d6..7f55b95718 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItemTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/ResourceRecordedDataQueueItemTest.kt @@ -33,7 +33,8 @@ internal class ResourceRecordedDataQueueItemTest { val testedRecordedDataQueueItem = ResourceRecordedDataQueueItem( recordedQueuedItemContext = fakeResourceRecordedDataQueueItem.recordedQueuedItemContext, identifier = fakeResourceRecordedDataQueueItem.identifier, - resourceData = ByteArray(0) + resourceData = ByteArray(0), + creationTimestampInNs = fakeResourceRecordedDataQueueItem.creationTimestampInNs ) // Then diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItemTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItemTest.kt index c53537477d..248b58572b 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItemTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/SnapshotRecordedDataQueueItemTest.kt @@ -67,7 +67,8 @@ internal class SnapshotRecordedDataQueueItemTest { // Given testedItem = SnapshotRecordedDataQueueItem( fakeRecordedQueuedItemContext, - mockSystemInformation + mockSystemInformation, + fakeSnapshotRecordedDataQueueItem.creationTimestampInNs ) // Then @@ -85,7 +86,7 @@ internal class SnapshotRecordedDataQueueItemTest { } @Test - fun `M return false W isReady() { not finished traveral }`() { + fun `M return false W isReady() { not finished traversal }`() { // Given testedItem.pendingJobs.set(0) testedItem.isFinishedTraversal = false diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItemTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItemTest.kt index ee5044187c..8715106fd2 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItemTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/TouchEventRecordedDataQueueItemTest.kt @@ -42,6 +42,7 @@ internal class TouchEventRecordedDataQueueItemTest { // Given testedItem = TouchEventRecordedDataQueueItem( recordedQueuedItemContext = fakeTouchEventRecordedDataQueueItem.recordedQueuedItemContext, + creationTimestampInNs = fakeTouchEventRecordedDataQueueItem.creationTimestampInNs, touchData = emptyList() ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt index 6adf01fe30..f9290d3c86 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt @@ -46,7 +46,6 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -59,9 +58,6 @@ internal class RecordedDataProcessorTest { @Mock lateinit var mockWriter: RecordWriter - @Mock - lateinit var mockTimeProvider: TimeProvider - @Mock lateinit var mockMutationResolver: MutationResolver @@ -71,7 +67,7 @@ internal class RecordedDataProcessorTest { @Mock lateinit var mockResourcesWriter: ResourcesWriter - @LongForgery + @LongForgery(min = 0L) var fakeTimestamp: Long = 0L @Forgery @@ -94,6 +90,9 @@ internal class RecordedDataProcessorTest { @Mock lateinit var mockResourceDataStoreManager: ResourceDataStoreManager + @Mock + lateinit var mockTimeProvider: TimeProvider + private val invalidRumContext = SessionReplayRumContext() private lateinit var invalidRecordedQueuedItemContext: RecordedQueuedItemContext @@ -153,13 +152,13 @@ internal class RecordedDataProcessorTest { .thenReturn(forge.aList { forge.getForgery() }) whenever(mockMutationResolver.resolveMutations(any(), any())) .thenReturn(forge.getForgery()) - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeTimestamp) testedProcessor = RecordedDataProcessor( + resourceDataStoreManager = mockResourceDataStoreManager, resourcesWriter = mockResourcesWriter, writer = mockWriter, mutationResolver = mockMutationResolver, - nodeFlattener = mockNodeFlattener, - resourceDataStoreManager = mockResourceDataStoreManager + timeProvider = mockTimeProvider, + nodeFlattener = mockNodeFlattener ) } @@ -280,6 +279,20 @@ internal class RecordedDataProcessorTest { forge: Forge ) { // Given + val fakeInitialElapsedTimeNs = forge.aLong( + min = RecordedDataProcessor.FULL_SNAPSHOT_INTERVAL_IN_NS, + max = Long.MAX_VALUE / 2 + ) + val fakeElapsedTimeAfterIntervalNs = + fakeInitialElapsedTimeNs + RecordedDataProcessor.FULL_SNAPSHOT_INTERVAL_IN_NS + + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn( + fakeInitialElapsedTimeNs, + fakeInitialElapsedTimeNs, + fakeElapsedTimeAfterIntervalNs, + fakeElapsedTimeAfterIntervalNs + ) + fakeSnapshot1.map { val fakeFlattenedSnapshot = forge.aList { getForgery(MobileSegment.Wireframe::class.java) @@ -297,15 +310,14 @@ internal class RecordedDataProcessorTest { currentRecordedQueuedItemContext = mockRumContextDataHandler.createRumContextData() ?: fail("RumContextData is null") - fakeSnapshotItem1 = createSnapshotItem(fakeSnapshot1) + fakeSnapshotItem1 = createSnapshotItem(fakeSnapshot1, creationTimestampInNs = fakeInitialElapsedTimeNs) testedProcessor.processScreenSnapshots(fakeSnapshotItem1) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(RecordedDataProcessor.FULL_SNAPSHOT_INTERVAL_IN_NS)) currentRecordedQueuedItemContext = mockRumContextDataHandler.createRumContextData() ?: fail("RumContextData is null") - fakeSnapshotItem2 = createSnapshotItem(fakeSnapshot2) + fakeSnapshotItem2 = createSnapshotItem(fakeSnapshot2, creationTimestampInNs = fakeElapsedTimeAfterIntervalNs) // When testedProcessor.processScreenSnapshots(fakeSnapshotItem2) @@ -746,6 +758,7 @@ internal class RecordedDataProcessorTest { val item = TouchEventRecordedDataQueueItem( recordedQueuedItemContext = rumContextData, + creationTimestampInNs = fakeTimestamp, touchData = fakeTouchRecords ) @@ -1315,17 +1328,20 @@ internal class RecordedDataProcessorTest { usedContext: RecordedQueuedItemContext = currentRecordedQueuedItemContext ): ResourceRecordedDataQueueItem = ResourceRecordedDataQueueItem( recordedQueuedItemContext = usedContext, + identifier = fakeIdentifier, resourceData = resourceData, - identifier = fakeIdentifier + creationTimestampInNs = fakeTimestamp ) private fun createSnapshotItem( snapshot: List, systemInformation: SystemInformation = fakeSystemInformation, - usedContext: RecordedQueuedItemContext = currentRecordedQueuedItemContext + usedContext: RecordedQueuedItemContext = currentRecordedQueuedItemContext, + creationTimestampInNs: Long = fakeTimestamp ): SnapshotRecordedDataQueueItem = SnapshotRecordedDataQueueItem( - usedContext, - systemInformation = systemInformation + recordedQueuedItemContext = usedContext, + systemInformation = systemInformation, + creationTimestampInNs = creationTimestampInNs ).apply { this.nodes = snapshot } @@ -1335,7 +1351,8 @@ internal class RecordedDataProcessorTest { usedContext: RecordedQueuedItemContext = currentRecordedQueuedItemContext ): TouchEventRecordedDataQueueItem = TouchEventRecordedDataQueueItem( - usedContext, + recordedQueuedItemContext = usedContext, + creationTimestampInNs = fakeTimestamp, touchData = touchEvent ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt index a78f612670..bc53f0b125 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt @@ -10,9 +10,11 @@ import android.os.Handler import com.datadog.android.api.feature.Feature.Companion.RUM_FEATURE_NAME import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.sessionreplay.forge.ForgeConfigurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -24,6 +26,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -51,6 +54,12 @@ internal class DebouncerTest { @Mock lateinit var mockRumFeature: FeatureScope + @Mock + lateinit var mockTimeProvider: TimeProvider + + @LongForgery(min = 0L) + var fakeInitialTimeNs: Long = 0L + @BoolForgery var fakeDynamicOptimizationEnabled: Boolean = false @@ -60,6 +69,8 @@ internal class DebouncerTest { fun `set up`() { whenever(mockTimeBank.updateAndCheck(any())).thenReturn(true) whenever(mockSdkCore.getFeature(RUM_FEATURE_NAME)).thenReturn(mockRumFeature) + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeInitialTimeNs testedDebouncer = Debouncer( mockHandler, TEST_MAX_DELAY_THRESHOLD_IN_NS, @@ -85,9 +96,12 @@ internal class DebouncerTest { // When testedDebouncer.debounce(fakeRunnable) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MAX_DELAY_THRESHOLD_IN_NS)) + + val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime testedDebouncer.debounce(fakeSecondRunnable) + // Then assertThat(fakeSecondRunnable.wasExecuted).isTrue() } @@ -107,7 +121,8 @@ internal class DebouncerTest { // When testedDebouncer.debounce(fakeRunnable) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MAX_DELAY_THRESHOLD_IN_NS)) + val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime testedDebouncer.debounce(fakeSecondRunnable) // Then @@ -137,7 +152,8 @@ internal class DebouncerTest { val fakeRunnable = TestRunnable() val fakeSecondRunnable = TestRunnable() testedDebouncer.debounce(fakeRunnable) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MAX_DELAY_THRESHOLD_IN_NS)) + val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime // When testedDebouncer.debounce(fakeSecondRunnable) @@ -160,18 +176,18 @@ internal class DebouncerTest { val fakeDelayedRunnables = forge.aList(size = forge.anInt(min = 1, max = 10)) { TestRunnable() } - // we remove 1ms just to make sure the threshold is not reached before the next debounce is - // called val delayInterval = (TEST_MAX_DELAY_THRESHOLD_IN_NS / fakeDelayedRunnables.size) - 1 val fakeExecutedRunnable = TestRunnable() + var currentTime = fakeInitialTimeNs + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { currentTime } + fakeDelayedRunnables.forEach { testedDebouncer.debounce(it) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayInterval)) + currentTime += delayInterval } // When - // wait for the removed 1ms - Thread.sleep(1L * fakeDelayedRunnables.size) + currentTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS testedDebouncer.debounce(fakeExecutedRunnable) // Then @@ -198,15 +214,19 @@ internal class DebouncerTest { // called val delayInterval = (TEST_MAX_DELAY_THRESHOLD_IN_NS / fakeDelayedRunnablesPack1.size) - 1 val fakeExecutedRunnable = TestRunnable() + var currentTime = fakeInitialTimeNs + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { currentTime } + fakeDelayedRunnablesPack1.forEach { testedDebouncer.debounce(it) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayInterval)) + currentTime += delayInterval } - // wait for the removed 1ms - Thread.sleep(1L * fakeDelayedRunnablesPack1.size) + + currentTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS testedDebouncer.debounce(fakeExecutedRunnable) // When + currentTime += (1L * fakeDelayedRunnablesPack1.size) fakeDelayedRunnablesPack2.forEach { testedDebouncer.debounce(it) } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt index 05252b8745..4929914b59 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt @@ -12,7 +12,6 @@ import android.util.DisplayMetrics import android.view.View import android.view.Window import com.datadog.android.api.InternalLogger -import com.datadog.android.internal.time.TimeProvider import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator @@ -59,9 +58,6 @@ internal class WindowCallbackInterceptorTest { @Mock lateinit var mockRecordedDataQueueHandler: RecordedDataQueueHandler - @Mock - lateinit var mockTimeProvider: TimeProvider - @Mock lateinit var mockRumContextProvider: RumContextProvider @@ -88,7 +84,7 @@ internal class WindowCallbackInterceptorTest { testedInterceptor = WindowCallbackInterceptor( recordedDataQueueHandler = mockRecordedDataQueueHandler, viewOnDrawInterceptor = mockViewOnDrawInterceptor, - timeProvider = mockTimeProvider, + timeProvider = mock(), rumContextProvider = mockRumContextProvider, internalLogger = mockInternalLogger, imagePrivacy = fakeImagePrivacy, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt index df51898a55..0745b4e77d 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt @@ -115,6 +115,9 @@ internal class RecorderWindowCallbackTest { @Forgery lateinit var fakeRumContext: SessionReplayRumContext + @LongForgery(min = 0L) + private var fakeElapsedTimeNs: Long = 0L + @BeforeEach fun `set up`() { val mockResources = mock { @@ -125,6 +128,7 @@ internal class RecorderWindowCallbackTest { .thenReturn(fakeTouchEventRecordedDataQueueItem) whenever(mockContext.resources).thenReturn(mockResources) whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeTimestamp) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { fakeElapsedTimeNs } whenever(mockRumContextProvider.getRumContext()).thenReturn(fakeRumContext) whenever(mockTouchPrivacyManager.shouldRecordTouch(any())) .thenReturn(true) @@ -281,10 +285,12 @@ internal class RecorderWindowCallbackTest { // When testedWindowCallback.dispatchTouchEvent(relatedMotionEvent1) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(relatedMotionEvent2) // must skip 3 as the motion update delay was not reached + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS / 2 testedWindowCallback.dispatchTouchEvent(relatedMotionEvent3) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS)) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(relatedMotionEvent4) testedWindowCallback.dispatchTouchEvent(relatedMotionEvent5) @@ -316,15 +322,15 @@ internal class RecorderWindowCallbackTest { testedWindowCallback.dispatchTouchEvent(fakeDownEvent) fakeMoveEventsBeforeFlush.forEachIndexed { index, event -> - if (index == fakeMoveEventsBeforeFlush.size - 1) { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_FLUSH_BUFFER_THRESHOLD_NS)) + fakeElapsedTimeNs += if (index == fakeMoveEventsBeforeFlush.size - 1) { + TEST_FLUSH_BUFFER_THRESHOLD_NS } else { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS)) + TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS } testedWindowCallback.dispatchTouchEvent(event) } fakeMoveEventsAfterFlush.forEach { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS)) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(it) } testedWindowCallback.dispatchTouchEvent(fakeUpEvent) @@ -364,9 +370,12 @@ internal class RecorderWindowCallbackTest { // When testedWindowCallback.dispatchTouchEvent(fakeGesture1DownEvent) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(fakeGesture1MoveEvent) testedWindowCallback.dispatchTouchEvent(fakeGesture1UpEvent) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(fakeGesture2DownEvent) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(fakeGesture2MoveEvent) testedWindowCallback.dispatchTouchEvent(fakeGesture2UpEvent) @@ -393,9 +402,9 @@ internal class RecorderWindowCallbackTest { // When testedWindowCallback.dispatchTouchEvent(fakeDownEvent) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_FLUSH_BUFFER_THRESHOLD_NS)) + fakeElapsedTimeNs += TEST_FLUSH_BUFFER_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(fakeMotion1Event) - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_FLUSH_BUFFER_THRESHOLD_NS)) + fakeElapsedTimeNs += TEST_FLUSH_BUFFER_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(fakeMotion2Event) // Then @@ -418,6 +427,7 @@ internal class RecorderWindowCallbackTest { // When testedWindowCallback.dispatchTouchEvent(relatedMotionEvent1) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(relatedMotionEvent2) testedWindowCallback.dispatchTouchEvent(relatedMotionEvent3) @@ -510,6 +520,7 @@ internal class RecorderWindowCallbackTest { // When testedWindowCallback.dispatchTouchEvent(relatedMotionEvent1) + fakeElapsedTimeNs += TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS testedWindowCallback.dispatchTouchEvent(relatedMotionEvent2) testedWindowCallback.dispatchTouchEvent(relatedMotionEvent3) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt index 753958c3d9..42882c02d1 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt @@ -15,11 +15,13 @@ import com.datadog.android.api.storage.datastore.DataStoreWriteCallback import com.datadog.android.core.internal.persistence.Deserializer import com.datadog.android.core.persistence.Serializer import com.datadog.android.core.persistence.datastore.DataStoreContent +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.resources.ResourceDataStoreManager.Companion.DATASTORE_EXPIRATION_NS import com.datadog.android.sessionreplay.internal.resources.ResourceDataStoreManager.Companion.DATASTORE_HASHES_ENTRY_NAME import com.datadog.android.sessionreplay.model.ResourceHashesEntry import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -65,15 +67,23 @@ internal class ResourceDataStoreManagerTest { @Mock lateinit var mockDataStoreHandler: DataStoreHandler + @Mock + lateinit var mockTimeProvider: TimeProvider + @StringForgery lateinit var fakeHash: String + @LongForgery(min = 0L) + var fakeCurrentTimeNs: Long = 0L + @BeforeEach fun setup() { whenever(mockFeatureSdkCore.getFeature(Feature.SESSION_REPLAY_RESOURCES_FEATURE_NAME)) .thenReturn(mockFeatureScope) whenever(mockFeatureScope.dataStore).thenReturn(mockDataStoreHandler) + whenever(mockFeatureSdkCore.timeProvider).thenReturn(mockTimeProvider) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) setRemoveDataSuccess() } @@ -138,7 +148,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true) + val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContent) // When @@ -190,7 +200,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true) + val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContent) // When @@ -212,7 +222,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContentEntry = generateDataStoreContent(forge, isExpired = false) + val mockDataStoreContentEntry = generateDataStoreContent(forge, isExpired = false, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContentEntry) // When @@ -249,7 +259,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContent = generateDataStoreContent(forge, isExpired = false) + val mockDataStoreContent = generateDataStoreContent(forge, isExpired = false, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContent) // When @@ -284,7 +294,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true) + val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContent) setRemoveDataSuccess() @@ -304,7 +314,7 @@ internal class ResourceDataStoreManagerTest { forge: Forge ) { // Given - val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true) + val mockDataStoreContent = generateDataStoreContent(forge, isExpired = true, fakeCurrentTimeNs) setFetchDataSuccess(mockDataStoreContent) setRemoveDataFailure() @@ -323,14 +333,15 @@ internal class ResourceDataStoreManagerTest { private fun generateDataStoreContent( forge: Forge, - isExpired: Boolean + isExpired: Boolean, + currentTime: Long ): DataStoreContent { val resourceHashes = forge.aList { aString() }.distinct() val fakeVersionCode = forge.anInt(min = 0) val entryTime = if (isExpired) { - System.nanoTime() - DATASTORE_EXPIRATION_NS + currentTime - DATASTORE_EXPIRATION_NS - 1 } else { - System.nanoTime() + currentTime } val mockResourceHashesEntry: ResourceHashesEntry = mock { diff --git a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt index b3eccacd5a..0938305e72 100644 --- a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt +++ b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt @@ -77,7 +77,7 @@ internal class DatadogSpanLogger( val logStatus = fields.remove(DatadogTracingConstants.LogAttributes.STATUS) ?: Log.VERBOSE fields[LogAttributes.DD_TRACE_ID] = span.context().traceId.toHexString() fields[LogAttributes.DD_SPAN_ID] = span.context().spanId.toString() - val timestamp = System.currentTimeMillis() + val timestamp = sdkCore.timeProvider.getDeviceTimestamp() logsFeature.sendEvent( buildMap { put("type", "span_log") diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt index bb9bba0d65..8d115d9681 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt @@ -9,6 +9,7 @@ import android.util.Log import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.internal.utils.loggableStackTrace import com.datadog.android.log.LogAttributes import com.datadog.android.trace.api.DatadogTracingConstants @@ -18,6 +19,7 @@ import com.datadog.android.trace.internal.DatadogSpanLogger.Companion.TRACE_LOGG import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -33,6 +35,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @Extensions( @@ -57,9 +60,15 @@ class DatadogSpanLoggerTest { @Forgery lateinit var fakeSpan: DatadogSpan + @LongForgery(min = 0L) + var fakeTimestamp: Long = 0L + @Mock lateinit var mockLogFeatureScope: FeatureScope + @Mock + lateinit var mockTimeProvider: TimeProvider + private lateinit var testedLogger: DatadogSpanLogger @BeforeEach @@ -68,6 +77,9 @@ class DatadogSpanLoggerTest { on { getFeature(Feature.LOGS_FEATURE_NAME) } doReturn mockLogFeatureScope } + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider + whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + testedLogger = DatadogSpanLogger(mockSdkCore) } diff --git a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/WebViewRumFeature.kt b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/WebViewRumFeature.kt index 462404c787..536cd301d5 100644 --- a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/WebViewRumFeature.kt +++ b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/WebViewRumFeature.kt @@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean internal class WebViewRumFeature( private val sdkCore: FeatureSdkCore, override val requestFactory: RequestFactory, - internal val nativeRumViewsCache: NativeRumViewsCache = WebViewNativeRumViewsCache() + internal val nativeRumViewsCache: NativeRumViewsCache = WebViewNativeRumViewsCache(sdkCore.timeProvider) ) : StorageBackedFeature, FeatureContextUpdateReceiver { internal var dataWriter: DataWriter = NoOpDataWriter() diff --git a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt index a20df43f65..f13296f81f 100644 --- a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt +++ b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt @@ -6,10 +6,12 @@ package com.datadog.android.webview.internal.rum.domain +import com.datadog.android.internal.time.TimeProvider import java.util.LinkedList import java.util.concurrent.TimeUnit internal class WebViewNativeRumViewsCache( + private val timeProvider: TimeProvider, private val entriesTtlLimitInMs: Long = DATA_PURGE_TTL_LIMIT_IN_MS ) : NativeRumViewsCache { @@ -87,7 +89,7 @@ internal class WebViewNativeRumViewsCache( private fun purgeHistory() { var cursor = parentViewsHistoryQueue.peekLast() while (cursor != null) { - val timeSinceLastSnapshot = System.currentTimeMillis() - cursor.timestamp + val timeSinceLastSnapshot = timeProvider.getDeviceTimestamp() - cursor.timestamp if (timeSinceLastSnapshot > entriesTtlLimitInMs) { parentViewsHistoryQueue.remove(cursor) cursor = parentViewsHistoryQueue.peekLast() diff --git a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt index 8e8bb1e7ad..aa7a5cc55b 100644 --- a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt +++ b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt @@ -125,6 +125,7 @@ internal class WebViewTrackingTest { mockReplayFeatureScope.unwrap() ) doReturn mockReplayFeature whenever(mockCore.internalLogger) doReturn mockInternalLogger + whenever(mockCore.timeProvider) doReturn mock() whenever(mockRumFeature.requestFactory) doReturn mockRumRequestFactory whenever(mockLogsFeature.requestFactory) doReturn mockLogsRequestFactory diff --git a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt index 0375ddebf2..a330fd37a4 100644 --- a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt +++ b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt @@ -6,8 +6,10 @@ package com.datadog.android.webview.internal.rum.domain +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -16,11 +18,12 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong @Extensions( ExtendWith(MockitoExtension::class), @@ -31,16 +34,28 @@ import java.util.concurrent.atomic.AtomicLong internal class WebViewNativeRumViewsCacheTest { private lateinit var fakeIdGenerator: FakeIdGenerator - private lateinit var fakeClock: FakeClock + + @LongForgery(min = 0L) + private var fakeCurrentTimeMs: Long = 0L + + @Mock + private lateinit var mockTimeProvider: TimeProvider + private lateinit var testedCache: WebViewNativeRumViewsCache // region Unit Tests @BeforeEach fun `set up`(forge: Forge) { - fakeClock = FakeClock() fakeIdGenerator = FakeIdGenerator(forge) - testedCache = WebViewNativeRumViewsCache() + whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeCurrentTimeMs) + testedCache = WebViewNativeRumViewsCache(mockTimeProvider) + } + + private fun nextTimestamp(): Long { + fakeCurrentTimeMs++ + whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeCurrentTimeMs) + return fakeCurrentTimeMs } @Test @@ -51,18 +66,18 @@ internal class WebViewNativeRumViewsCacheTest { val fakeEntries = forge.aList(size = forge.anInt(min = 1, max = 10)) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to true ) } val fakeNoReplayEntries = forge.aList(size = forge.anInt(min = 1, max = 10)) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to false ) } - val fakeBrowserEventTimestampInMs = fakeClock.nextCurrentTimeMillis() + val fakeBrowserEventTimestampInMs = nextTimestamp() fakeEntries.forEach { testedCache.addToCache(it) } @@ -85,14 +100,14 @@ internal class WebViewNativeRumViewsCacheTest { val fakeEntries = forge.aList { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to false ) } fakeEntries.forEach { testedCache.addToCache(it) } - val fakeBrowserEventTimestampInMs = fakeClock.nextCurrentTimeMillis() + val fakeBrowserEventTimestampInMs = nextTimestamp() // When val resolvedParentId = testedCache.resolveLastParentIdForBrowserEvent(fakeBrowserEventTimestampInMs) @@ -106,11 +121,11 @@ internal class WebViewNativeRumViewsCacheTest { forge: Forge ) { // Given - val fakeBrowserEventTimestampInMs = fakeClock.nextCurrentTimeMillis() + val fakeBrowserEventTimestampInMs = nextTimestamp() val fakeEntries = forge.aList { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -128,7 +143,7 @@ internal class WebViewNativeRumViewsCacheTest { @Test fun `M return null W resolveLastParentIdForBrowserEvent() { no data in cache }`() { // Given - val fakeBrowserEventTimestampInMs = fakeClock.nextCurrentTimeMillis() + val fakeBrowserEventTimestampInMs = nextTimestamp() // When val resolvedParentId = testedCache.resolveLastParentIdForBrowserEvent(fakeBrowserEventTimestampInMs) @@ -145,14 +160,14 @@ internal class WebViewNativeRumViewsCacheTest { val fakeEntries = forge.aList { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } fakeEntries.forEach { Thread { testedCache.addToCache(it) }.apply { start() }.join(5000) } - val fakeBrowserEventTimestampInMs = fakeClock.nextCurrentTimeMillis() + val fakeBrowserEventTimestampInMs = nextTimestamp() // Then assertDoesNotThrow { testedCache.resolveLastParentIdForBrowserEvent(fakeBrowserEventTimestampInMs) } @@ -164,18 +179,22 @@ internal class WebViewNativeRumViewsCacheTest { ) { // Given val entriesTtlLimitInMs = TimeUnit.SECONDS.toMillis(1) - testedCache = WebViewNativeRumViewsCache(entriesTtlLimitInMs) + val oldEntriesTimestamp = fakeCurrentTimeMs + testedCache = WebViewNativeRumViewsCache(mockTimeProvider, entriesTtlLimitInMs) + var oldEntryTimestamp = oldEntriesTimestamp val fakeOldEntries = forge.aList(size = forge.anInt(min = 1, max = 10)) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to System.currentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to ++oldEntryTimestamp, WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } + whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(oldEntriesTimestamp) fakeOldEntries.forEach { testedCache.addToCache(it) } // When - Thread.sleep(entriesTtlLimitInMs) + val newEntriesTimestamp = oldEntryTimestamp + entriesTtlLimitInMs + 1 + var newEntryTimestamp = newEntriesTimestamp val fakeNewEntries = forge.aList( size = forge.anInt( min = 1, @@ -184,7 +203,7 @@ internal class WebViewNativeRumViewsCacheTest { ) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to System.currentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to ++newEntryTimestamp, WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -195,6 +214,8 @@ internal class WebViewNativeRumViewsCacheTest { it[WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY] as Boolean ) } + // Simulate time passing: device time is beyond TTL for old entries + whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(newEntriesTimestamp) fakeNewEntries.forEach { testedCache.addToCache(it) } // Then @@ -215,7 +236,7 @@ internal class WebViewNativeRumViewsCacheTest { ) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId() + index++, - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -246,7 +267,7 @@ internal class WebViewNativeRumViewsCacheTest { val fakeEntries = forge.aList(size = forge.anInt(min = 1, max = 10)) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeViewId, - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to System.currentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -274,7 +295,7 @@ internal class WebViewNativeRumViewsCacheTest { ) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -286,7 +307,7 @@ internal class WebViewNativeRumViewsCacheTest { ) { mapOf( WebViewNativeRumViewsCache.VIEW_ID_KEY to fakeIdGenerator.generateId(), - WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to fakeClock.nextCurrentTimeMillis(), + WebViewNativeRumViewsCache.VIEW_TIMESTAMP_KEY to nextTimestamp(), WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } @@ -313,15 +334,6 @@ internal class WebViewNativeRumViewsCacheTest { // region Utils - private class FakeClock { - - val initialTimeMillis = AtomicLong(System.currentTimeMillis()) - - fun nextCurrentTimeMillis(): Long { - return initialTimeMillis.incrementAndGet() - } - } - private class FakeIdGenerator(private val forge: Forge) { var index = 0 fun generateId(): String { diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt index 61976d7a5c..a2d4ea823c 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt @@ -20,6 +20,8 @@ import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver +import com.datadog.android.internal.time.TimeProvider +import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import org.mockito.Mockito.mock import org.mockito.kotlin.doReturn @@ -159,6 +161,8 @@ class StubSDKCore( override val internalLogger: InternalLogger = StubInternalLogger() + override val timeProvider: TimeProvider = StubTimeProvider() + override fun registerFeature(feature: Feature) { stubFeatureScope(feature, StubFeatureScope(feature, { datadogContext })) } @@ -187,7 +191,7 @@ class StubSDKCore( } override fun createScheduledExecutorService(executorContext: String): ScheduledExecutorService { - return StubScheduledExecutorService(executorContext) + return StubScheduledExecutorService(executorContext, timeProvider::getDeviceTimestamp) } override fun createSingleThreadExecutorService(executorContext: String): ExecutorService { diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubScheduledExecutorService.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubScheduledExecutorService.kt index e060ffcb69..de6bfc295d 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubScheduledExecutorService.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubScheduledExecutorService.kt @@ -20,7 +20,10 @@ import kotlin.math.sign * Stubbed version of a [ScheduledExecutorService]. */ @Suppress("UndocumentedPublicFunction") -class StubScheduledExecutorService(executorContext: String) : ScheduledExecutorService { +class StubScheduledExecutorService( + executorContext: String, + private val nowProvider: () -> Long +) : ScheduledExecutorService { private var isShutdown = false private var isTerminated = false @@ -97,10 +100,11 @@ class StubScheduledExecutorService(executorContext: String) : ScheduledExecutorS override fun schedule(command: Runnable?, delay: Long, unit: TimeUnit?): ScheduledFuture<*> { val futureTask = FutureTask(command, null) val delayMs = unit!!.toMillis(delay) - val triggerTimestamp = System.currentTimeMillis() + delayMs + val triggerTimestamp = nowProvider() + delayMs val scheduledFutureTask = ScheduledFutureTask( futureTask, - triggerTimestamp + triggerTimestamp, + nowProvider ) Thread { Thread.sleep(delayMs) @@ -112,10 +116,11 @@ class StubScheduledExecutorService(executorContext: String) : ScheduledExecutorS override fun schedule(callable: Callable?, delay: Long, unit: TimeUnit?): ScheduledFuture { val futureTask = FutureTask(callable) val delayMs = unit!!.toMillis(delay) - val triggerTimestamp = System.currentTimeMillis() + delayMs + val triggerTimestamp = nowProvider() + delayMs val scheduledFutureTask = ScheduledFutureTask( futureTask, - triggerTimestamp + triggerTimestamp, + nowProvider ) Thread { Thread.sleep(delayMs) @@ -147,10 +152,12 @@ class StubScheduledExecutorService(executorContext: String) : ScheduledExecutorS * @param V the type of the task's result * @property delegate the delegate [FutureTask] * @property triggerTimestamp the timestamp at which the task should be triggered + * @property nowProvider a function that provides the current timestamp in milliseconds */ class ScheduledFutureTask( val delegate: FutureTask, - val triggerTimestamp: Long + val triggerTimestamp: Long, + val nowProvider: () -> Long ) : RunnableFuture by delegate, ScheduledFuture { override fun compareTo(other: Delayed?): Int { val delay = getDelay(TimeUnit.MILLISECONDS) @@ -159,7 +166,7 @@ class StubScheduledExecutorService(executorContext: String) : ScheduledExecutorS } override fun getDelay(unit: TimeUnit?): Long { - val delayMs = triggerTimestamp - System.currentTimeMillis() + val delayMs = triggerTimestamp - nowProvider() return unit!!.convert(delayMs, TimeUnit.MILLISECONDS) } } diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/DatadogVitalsMeter.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/DatadogVitalsMeter.kt index 5985a8a65b..13689799de 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/DatadogVitalsMeter.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/DatadogVitalsMeter.kt @@ -6,6 +6,8 @@ package com.datadog.benchmark +import com.datadog.android.Datadog +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.internal.profiler.GlobalBenchmark import com.datadog.benchmark.exporter.DatadogMetricExporter import com.datadog.benchmark.exporter.DatadogSpanExporter @@ -35,7 +37,9 @@ class DatadogVitalsMeter private constructor(private val meter: Meter) : Datadog private val cpuVitalReader: CPUVitalReader = CPUVitalReader() private val memoryVitalReader: MemoryVitalReader = MemoryVitalReader() - private val fpsVitalReader: FpsVitalReader = FpsVitalReader() + private val fpsVitalReader: FpsVitalReader = FpsVitalReader( + (Datadog.getInstance() as FeatureSdkCore).timeProvider + ) private val gaugesByMetricName: MutableMap = mutableMapOf() diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt index 1c5d59d388..58efc6881e 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt @@ -7,9 +7,10 @@ package com.datadog.benchmark.internal.reader import android.view.Choreographer +import com.datadog.android.internal.time.TimeProvider import java.util.concurrent.TimeUnit -internal class FpsVitalReader : VitalReader { +internal class FpsVitalReader(timeProvider: TimeProvider) : VitalReader { private var currentFps: Double = 0.0 private var frameCount = 0 @@ -19,11 +20,11 @@ internal class FpsVitalReader : VitalReader { private val frameCallback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { if (lastFrameTime == 0L) { - lastFrameTime = System.nanoTime() + lastFrameTime = timeProvider.getDeviceElapsedTimeNs() } frameCount++ - val currentFrameTime = System.nanoTime() + val currentFrameTime = timeProvider.getDeviceElapsedTimeNs() val elapsedTime: Long = currentFrameTime - lastFrameTime if (elapsedTime >= TimeUnit.MILLISECONDS.toNanos(intervalMs)) { diff --git a/tools/unit/build.gradle.kts b/tools/unit/build.gradle.kts index bab94aa56e..a14039a77b 100644 --- a/tools/unit/build.gradle.kts +++ b/tools/unit/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(libs.bundles.testTools) implementation(libs.gson) implementation(libs.mockitoKotlin) + implementation(project(":dd-sdk-android-internal")) testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) From fff5f31cabe3220b7d91e5b8c3b0800cd74f1a81 Mon Sep 17 00:00:00 2001 From: Francisco Veiga Date: Thu, 11 Dec 2025 17:25:00 +0000 Subject: [PATCH 2/5] RUM-10363: Fix changes according to develop branch --- .../android/core/internal/DatadogCore.kt | 13 +--- .../core/internal/NoOpInternalSdkCore.kt | 2 +- .../time/DefaultAppStartTimeProviderTest.kt | 69 +++++-------------- 3 files changed, 22 insertions(+), 62 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index d65924a9cb..96407a2c52 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -34,6 +34,7 @@ import com.datadog.android.core.internal.logger.SdkInternalLogger import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.core.internal.time.DefaultAppStartTimeProvider +import com.datadog.android.core.internal.time.composeTimeInfo import com.datadog.android.core.internal.utils.executeSafe import com.datadog.android.core.internal.utils.getSafe import com.datadog.android.core.internal.utils.scheduleSafe @@ -101,17 +102,7 @@ internal class DatadogCore( /** @inheritDoc */ override val time: TimeInfo get() { - return with(coreFeature.timeProvider) { - val deviceTimeMs = getDeviceTimestamp() - val serverTimeMs = getServerTimestamp() - TimeInfo( - deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(deviceTimeMs), - serverTimeNs = TimeUnit.MILLISECONDS.toNanos(serverTimeMs), - serverTimeOffsetNs = TimeUnit.MILLISECONDS - .toNanos(serverTimeMs - deviceTimeMs), - serverTimeOffsetMs = serverTimeMs - deviceTimeMs - ) - } + return timeProvider.composeTimeInfo() } /** @inheritDoc */ diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt index 18bb8b58b5..566d436747 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt @@ -48,7 +48,7 @@ internal object NoOpInternalSdkCore : InternalSdkCore { override val name: String = "no-op" override val time: TimeInfo = with(timeProvider.getDeviceTimestamp()) { - TimeInfo( + TimeInfo.EMPTY.copy( deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(this), serverTimeNs = TimeUnit.MILLISECONDS.toNanos(this), serverTimeOffsetNs = 0L, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt index efe04f08ad..b019a1b1dd 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt @@ -12,7 +12,6 @@ import android.os.SystemClock import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.DdRumContentProvider -import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -40,55 +39,23 @@ class DefaultAppStartTimeProviderTest { @Test fun `M return process start time W appStartTime { N+ }`( @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, - @LongForgery(min = 0L) fakeCurrentTimeNs: Long, - forge: Forge + @LongForgery(min = 0L) fakeCurrentTimeNs: Long ) { // Given whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) - - DdRumContentProvider.createTimeNs = startTimeNs + - forge.aLong(min = 0, max = DefaultAppStartTimeProvider.PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS) - - val testedProvider = DefaultAppStartTimeProvider( - mockTimeProvider, - mockBuildSdkVersionProvider - ) + val expectedStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) // When - val providedStartTime = testedProvider.appStartTimeNs - - // Then - assertThat(providedStartTime).isEqualTo(startTimeNs) - } - - @Test - fun `M fall back to DdRumContentProvider W appStartTime { N+ getStartElapsedRealtime returns buggy value }`( - @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, - @LongForgery(min = 0L) fakeCurrentTimeNs: Long, - forge: Forge - ) { - // GIVEN - whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) - - val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) - - DdRumContentProvider.createTimeNs = startTimeNs + - forge.aLong(min = DefaultAppStartTimeProvider.PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS) - - // WHEN - val testedProvider = DefaultAppStartTimeProvider( + val testedAppStartTimeProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) + val providedStartTime = testedAppStartTimeProvider.appStartTimeNs - val providedStartTime = testedProvider.appStartTimeNs - - // THEN - assertThat(providedStartTime).isEqualTo(DdRumContentProvider.createTimeNs) + // Then + assertThat(providedStartTime).isEqualTo(expectedStartTimeNs) } @Test @@ -98,13 +65,13 @@ class DefaultAppStartTimeProviderTest { // Given whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) val startTimeNs = DdRumContentProvider.createTimeNs - val testedProvider = DefaultAppStartTimeProvider( + + // When + val testedAppStartTimeProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) - - // When - val providedStartTime = testedProvider.appStartTimeNs + val providedStartTime = testedAppStartTimeProvider.appStartTimeNs // Then assertThat(providedStartTime).isEqualTo(startTimeNs) @@ -125,12 +92,13 @@ class DefaultAppStartTimeProviderTest { .thenReturn(fakeStartTimeNs + fakeUptimeNs) // When - val testedProvider = DefaultAppStartTimeProvider( + val testedAppStartTimeProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) - testedProvider.appStartTimeNs - val uptime = testedProvider.appUptimeNs + // First call initializes appStartTimeNs + testedAppStartTimeProvider.appStartTimeNs + val uptime = testedAppStartTimeProvider.appUptimeNs // Then assertThat(uptime).isEqualTo(fakeUptimeNs) @@ -151,13 +119,14 @@ class DefaultAppStartTimeProviderTest { .thenReturn(fakeStartTimeNs + 200L) // When - val testedProvider = DefaultAppStartTimeProvider( + val testedAppStartTimeProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) - testedProvider.appStartTimeNs - val uptime1 = testedProvider.appUptimeNs - val uptime2 = testedProvider.appUptimeNs + // First call initializes appStartTimeNs + testedAppStartTimeProvider.appStartTimeNs + val uptime1 = testedAppStartTimeProvider.appUptimeNs + val uptime2 = testedAppStartTimeProvider.appUptimeNs // Then assertThat(uptime2).isGreaterThan(uptime1) From 4d218ab2a237183e5f88712eaa3c0f4160210511 Mon Sep 17 00:00:00 2001 From: Francisco Veiga Date: Thu, 11 Dec 2025 17:39:23 +0000 Subject: [PATCH 3/5] RUM-10363: Fix appUptimeNs and RumResourceInputStreamTest.kt tests --- .../time/DefaultAppStartTimeProviderTest.kt | 56 ++++++++++--------- .../android/rum/resource/ContextExtTest.kt | 6 +- .../rum/resource/InputStreamExtTest.kt | 7 ++- .../resource/RumResourceInputStreamTest.kt | 39 +++++++------ 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt index b019a1b1dd..e41d137727 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt @@ -21,6 +21,7 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever import java.util.concurrent.TimeUnit @@ -42,8 +43,8 @@ class DefaultAppStartTimeProviderTest { @LongForgery(min = 0L) fakeCurrentTimeNs: Long ) { // Given - whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) + whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeCurrentTimeNs val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val expectedStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) @@ -63,7 +64,7 @@ class DefaultAppStartTimeProviderTest { @IntForgery(min = Build.VERSION_CODES.M, max = Build.VERSION_CODES.N) apiVersion: Int ) { // Given - whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion val startTimeNs = DdRumContentProvider.createTimeNs // When @@ -80,25 +81,27 @@ class DefaultAppStartTimeProviderTest { @Test fun `M return app uptime W appUptimeNs`( @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, - @LongForgery(min = 1000000L) fakeCurrentTimeNs: Long, - @LongForgery(min = 100L, max = 999999L) fakeUptimeNs: Long + @LongForgery(min = 1000000L) fakeStartTimeNs: Long, + @LongForgery(min = 1000L, max = 100000L) fakeUptimeNs: Long ) { // Given - whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion + val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val fakeStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) + val fakeCurrentTimeNs = fakeStartTimeNs + TimeUnit.MILLISECONDS.toNanos(diffMs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeCurrentTimeNs) - .thenReturn(fakeStartTimeNs + fakeUptimeNs) + .doReturn(fakeCurrentTimeNs) + .doReturn(fakeStartTimeNs + fakeUptimeNs) - // When - val testedAppStartTimeProvider = DefaultAppStartTimeProvider( + val testedProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) - // First call initializes appStartTimeNs - testedAppStartTimeProvider.appStartTimeNs - val uptime = testedAppStartTimeProvider.appUptimeNs + + // When + testedProvider.appStartTimeNs + val uptime = testedProvider.appUptimeNs // Then assertThat(uptime).isEqualTo(fakeUptimeNs) @@ -107,26 +110,27 @@ class DefaultAppStartTimeProviderTest { @Test fun `M return increasing uptime W appUptimeNs called multiple times`( @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, - @LongForgery(min = 1000000L) fakeCurrentTimeNs: Long + @LongForgery(min = 1000000L) fakeStartTimeNs: Long ) { // Given - whenever(mockBuildSdkVersionProvider.version).thenReturn(apiVersion) + whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val fakeStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) + val fakeCurrentTimeNs = fakeStartTimeNs + TimeUnit.MILLISECONDS.toNanos(diffMs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeCurrentTimeNs) - .thenReturn(fakeStartTimeNs + 100L) - .thenReturn(fakeStartTimeNs + 200L) + .doReturn(fakeCurrentTimeNs) + .doReturn(fakeStartTimeNs + 100L) + .doReturn(fakeStartTimeNs + 200L) - // When - val testedAppStartTimeProvider = DefaultAppStartTimeProvider( + val testedProvider = DefaultAppStartTimeProvider( mockTimeProvider, mockBuildSdkVersionProvider ) - // First call initializes appStartTimeNs - testedAppStartTimeProvider.appStartTimeNs - val uptime1 = testedAppStartTimeProvider.appUptimeNs - val uptime2 = testedAppStartTimeProvider.appUptimeNs + + // When + testedProvider.appStartTimeNs + val uptime1 = testedProvider.appUptimeNs + val uptime2 = testedProvider.appUptimeNs // Then assertThat(uptime2).isGreaterThan(uptime1) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/ContextExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/ContextExtTest.kt index abaaa785b7..6bb4c50d1d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/ContextExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/ContextExtTest.kt @@ -9,7 +9,7 @@ package com.datadog.android.rum.resource import android.content.Context import android.content.res.AssetManager import android.content.res.Resources -import com.datadog.android.api.SdkCore +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.internal.utils.toHexString import com.datadog.tools.unit.forge.BaseConfigurator import fr.xgouchet.elmyr.annotation.IntForgery @@ -45,7 +45,7 @@ class ContextExtTest { lateinit var mockContext: Context @Mock - lateinit var mockSdkCore: SdkCore + lateinit var mockSdkCore: FeatureSdkCore @Mock lateinit var mockAssetManager: AssetManager @@ -57,6 +57,8 @@ class ContextExtTest { fun `set up`() { whenever(mockContext.assets) doReturn mockAssetManager whenever(mockContext.resources) doReturn mockResources + + whenever(mockSdkCore.timeProvider) doReturn mock() } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/InputStreamExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/InputStreamExtTest.kt index 0f72680a02..1a5548d61b 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/InputStreamExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/InputStreamExtTest.kt @@ -6,7 +6,7 @@ package com.datadog.android.rum.resource -import com.datadog.android.api.SdkCore +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.tools.unit.forge.BaseConfigurator import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -17,7 +17,9 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.io.InputStream @@ -37,7 +39,8 @@ class InputStreamExtTest { ) { // Given val mockIS: InputStream = mock() - val mockSdkCore: SdkCore = mock() + val mockSdkCore: FeatureSdkCore = mock() + whenever(mockSdkCore.timeProvider) doReturn mock() // When val result = mockIS.asRumResource(url, mockSdkCore) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt index 2d897be96a..f4c9ed16fb 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt @@ -6,6 +6,8 @@ package com.datadog.android.rum.resource +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.internal.domain.event.ResourceTiming @@ -45,10 +47,7 @@ import org.mockito.quality.Strictness import java.io.BufferedReader import java.io.IOException import java.io.InputStream -import java.lang.RuntimeException -import java.util.concurrent.TimeUnit import kotlin.math.min -import kotlin.system.measureNanoTime @Extensions( ExtendWith(MockitoExtension::class), @@ -64,6 +63,9 @@ internal class RumResourceInputStreamTest { @Mock lateinit var mockInputStream: InputStream + @Mock + lateinit var mockTimeProvider: TimeProvider + @StringForgery lateinit var fakeUrl: String @@ -72,6 +74,7 @@ internal class RumResourceInputStreamTest { @BeforeEach fun `set up`() { + whenever((rumMonitor.mockSdkCore as FeatureSdkCore).timeProvider) doReturn mockTimeProvider testedInputStream = RumResourceInputStream(mockInputStream, fakeUrl, rumMonitor.mockSdkCore) // M start resource W init @@ -658,24 +661,26 @@ internal class RumResourceInputStreamTest { @Test fun `M register resource with timing W read() + close() {bufferedReader}`( - @StringForgery content: String + @StringForgery content: String, + @LongForgery(min = 0L) fakeCallStartNs: Long, + @LongForgery(min = 0L) fakeDownloadStartNs: Long, + @LongForgery(min = 0L) fakeDownloadDurationNs: Long ) { // Given val contentBytes = content.toByteArray() val inputStream = contentBytes.inputStream() + val fakeFirstByteNs = fakeCallStartNs + fakeDownloadStartNs + val fakeLastByteNs = fakeFirstByteNs + fakeDownloadDurationNs + + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + .thenReturn(fakeCallStartNs) + .thenReturn(fakeFirstByteNs) + .thenReturn(fakeLastByteNs) + testedInputStream = RumResourceInputStream(inputStream, fakeUrl, rumMonitor.mockSdkCore) - Thread.sleep(500) - var download: Long // When - val result = testedInputStream.bufferedReader().use { - var text: String - download = measureNanoTime { - text = it.readText() - } - Thread.sleep(500) - text - } + val result = testedInputStream.bufferedReader().use { it.readText() } // Then assertThat(result).isEqualTo(content) @@ -697,10 +702,8 @@ internal class RumResourceInputStreamTest { assertThat(firstValue.dnsDuration).isEqualTo(0L) assertThat(firstValue.sslDuration).isEqualTo(0L) assertThat(firstValue.firstByteDuration).isEqualTo(0L) - assertThat(firstValue.downloadStart).isGreaterThan( - TimeUnit.MILLISECONDS.toNanos(500) - ) - assertThat(firstValue.downloadDuration).isLessThanOrEqualTo(download) + assertThat(firstValue.downloadStart).isEqualTo(fakeDownloadStartNs) + assertThat(firstValue.downloadDuration).isEqualTo(fakeDownloadDurationNs) } verify(rumMonitor.mockInstance).stopResource( testedInputStream.key, From af30673ed253b4d44311fcc5ef7019ffa8b899ac Mon Sep 17 00:00:00 2001 From: Francisco Veiga Date: Wed, 17 Dec 2025 12:21:40 +0000 Subject: [PATCH 4/5] RUM-10363: Apply suggested changes --- .../android/core/internal/DatadogCore.kt | 2 +- .../time/DefaultAppStartTimeProvider.kt | 7 +-- .../core/internal/time/KronosTimeProvider.kt | 7 ++- .../datadog/android/core/DatadogCoreTest.kt | 2 +- .../data/upload/RotatingDnsResolverTest.kt | 10 ++-- .../MoveDataMigrationOperationTest.kt | 10 ++-- .../WipeDataMigrationOperationTest.kt | 5 +- .../file/batch/BatchFileOrchestratorTest.kt | 2 +- .../thread/ThreadPoolExecutorExtTest.kt | 33 +++-------- .../time/DefaultAppStartTimeProviderTest.kt | 59 ++++++++++++++----- .../core/internal/utils/MiscUtilsTest.kt | 23 ++++++++ .../internal/DatadogExceptionHandlerTest.kt | 2 +- dd-sdk-android-internal/api/apiSurface | 5 +- .../api/dd-sdk-android-internal.api | 15 +++-- .../internal/time/DefaultTimeProvider.kt | 6 +- .../android/internal/time/TimeProvider.kt | 22 ++++--- .../internal/logger/DatadogLogHandlerTest.kt | 14 ++--- .../scope/RumContinuousActionScopeTest.kt | 54 ++++++++--------- .../domain/scope/RumSessionScopeTest.kt | 3 +- .../MainLooperLongTaskStrategyTest.kt | 5 +- .../resources/ResourceDataStoreManagerTest.kt | 2 +- .../datadog/android/core/stub/StubSDKCore.kt | 9 ++- .../benchmark/sample/di/app/DatadogModule.kt | 7 ++- .../datadog/benchmark/DatadogVitalsMeter.kt | 18 +++--- tools/unit/build.gradle.kts | 1 - 25 files changed, 186 insertions(+), 137 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index 96407a2c52..63691458cb 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -457,7 +457,7 @@ internal class DatadogCore( executorServiceFactory ?: CoreFeature.DEFAULT_FLUSHABLE_EXECUTOR_SERVICE_FACTORY coreFeature = CoreFeature( internalLogger, - DefaultAppStartTimeProvider(), + DefaultAppStartTimeProvider(timeProviderFactory = { timeProvider }), flushableExecutorServiceFactory, CoreFeature.DEFAULT_SCHEDULED_EXECUTOR_SERVICE_FACTORY ) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt index ad407dc1fd..69da956fbb 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt @@ -11,14 +11,13 @@ import android.os.Build import android.os.Process import android.os.SystemClock import com.datadog.android.core.internal.system.BuildSdkVersionProvider -import com.datadog.android.internal.time.DefaultTimeProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.DdRumContentProvider import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.seconds internal class DefaultAppStartTimeProvider( - private val timeProvider: TimeProvider = DefaultTimeProvider(), + private val timeProviderFactory: () -> TimeProvider, buildSdkVersionProvider: BuildSdkVersionProvider = BuildSdkVersionProvider.DEFAULT ) : AppStartTimeProvider { @@ -27,7 +26,7 @@ internal class DefaultAppStartTimeProvider( when { buildSdkVersionProvider.version >= Build.VERSION_CODES.N -> { val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val result = timeProvider.getDeviceElapsedTimeNs() - TimeUnit.MILLISECONDS.toNanos(diffMs) + val result = timeProviderFactory().getDeviceElapsedTimeNs() - TimeUnit.MILLISECONDS.toNanos(diffMs) /** * Occasionally [Process.getStartElapsedRealtime] returns buggy values. We filter them and fall back @@ -44,7 +43,7 @@ internal class DefaultAppStartTimeProvider( } override val appUptimeNs: Long - get() = timeProvider.getDeviceElapsedTimeNs() - appStartTimeNs + get() = timeProviderFactory().getDeviceElapsedTimeNs() - appStartTimeNs companion object { internal val PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS = 10.seconds.inWholeNanoseconds diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt index 85a798fd7f..1bc54392da 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt @@ -7,6 +7,7 @@ package com.datadog.android.core.internal.time import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.time.BaseTimeProvider import com.datadog.android.internal.time.TimeProvider import com.lyft.kronos.Clock import java.util.concurrent.TimeUnit @@ -14,16 +15,16 @@ import java.util.concurrent.TimeUnit /** * A [TimeProvider] implementation that uses Kronos NTP for server time synchronization. * - * Device timestamp and elapsed time are inherited from [TimeProvider] default implementations. + * Device timestamp and elapsed time are inherited from [BaseTimeProvider]. */ internal class KronosTimeProvider( private val clock: Clock, private val internalLogger: InternalLogger -) : TimeProvider { +) : BaseTimeProvider() { override fun getServerTimestamp(): Long { return clock.safeGetCurrentTimeMs() - .getOrElse { System.currentTimeMillis() } + .getOrElse { getDeviceTimestamp() } } override fun getServerOffsetMillis(): Long { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index bfd5369756..593dc6b458 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -28,6 +28,7 @@ import com.datadog.android.core.internal.privacy.ConsentProvider import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.core.internal.user.MutableUserInfoProvider import com.datadog.android.core.thread.FlushableExecutorService +import com.datadog.android.internal.tests.stub.StubTimeProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent @@ -39,7 +40,6 @@ import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes -import com.datadog.tools.unit.stub.StubTimeProvider import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.AdvancedForgery diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt index 1f69761c8e..0e35e23bf0 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt @@ -60,8 +60,6 @@ internal class RotatingDnsResolverTest { fun `set up`(forge: Forge) { fakeInetAddresses = forge.aList { mock() } - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeElapsedTimeNs - testedDns = RotatingDnsResolver(mockDelegate, TEST_TTL_MS, mockTimeProvider) } @@ -84,7 +82,7 @@ internal class RotatingDnsResolverTest { val result = mutableListOf() // When - fakeInetAddresses.forEach { _ -> + repeat(fakeInetAddresses.size) { result.add(testedDns.lookup(fakeHostname).first()) } @@ -134,15 +132,15 @@ internal class RotatingDnsResolverTest { // the real use case where we have a small number of addresses to rotate fakeInetAddresses = forge.aList(size = forge.anInt(min = 1, max = 3)) { mock() } whenever(mockDelegate.lookup(fakeHostname)) doReturn fakeInetAddresses - - val fakeExpiredTime = fakeElapsedTimeNs + TEST_TTL_MS.inWholeNanoseconds - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).doReturn(fakeElapsedTimeNs, fakeExpiredTime) + // just wait the TTL time to make sure all threads are concurrently accessing the lookup + Thread.sleep(TEST_TTL_MS.inWholeMilliseconds) var exceptionThrown: Exception? = null // When List(100) { Thread { + Thread.sleep(forge.aLong(min = 0, max = 100)) try { testedDns.lookup(fakeHostname) } catch (e: Exception) { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt index d39904ee21..4d3a13c903 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt @@ -150,8 +150,9 @@ internal class MoveDataMigrationOperationTest { } @Test - fun `M not wait for real delay W run() {move fails once, time provider mocked}`() { + fun `M retry with 500ms delay W run() {move fails once}`() { // Given + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false, true) @@ -162,7 +163,7 @@ internal class MoveDataMigrationOperationTest { // Then verify(mockFileMover, times(2)).moveFiles(fakeFromDirectory, fakeToDirectory) - assertThat(duration).isLessThan(100L) + assertThat(duration).isBetween(500L, 550L) } @Test @@ -179,8 +180,9 @@ internal class MoveDataMigrationOperationTest { } @Test - fun `M not wait for real delay W run() {move always fails, time provider mocked}`() { + fun `M retry with 500ms delay W run() {move always fails}`() { // Given + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false) @@ -191,7 +193,7 @@ internal class MoveDataMigrationOperationTest { // Then verify(mockFileMover, times(3)).moveFiles(fakeFromDirectory, fakeToDirectory) - assertThat(duration).isLessThan(100L) + assertThat(duration).isBetween(1000L, 1100L) } companion object { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt index 65343f0ca4..1ba7660519 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt @@ -134,8 +134,9 @@ internal class WipeDataMigrationOperationTest { } @Test - fun `M not wait for real delay W run() {move always fails, time provider mocked}`() { + fun `M retry with 500ms delay W run() {move always fails}`() { // Given + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } whenever(mockFileMover.delete(fakeTargetDirectory)) .doReturn(false) @@ -146,7 +147,7 @@ internal class WipeDataMigrationOperationTest { // Then verify(mockFileMover, times(3)).delete(fakeTargetDirectory) - assertThat(duration).isLessThan(100L) + assertThat(duration).isBetween(1000L, 1100L) } companion object { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt index e394540762..f33e04eb3f 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt @@ -12,9 +12,9 @@ import com.datadog.android.core.internal.metrics.MetricsDispatcher import com.datadog.android.core.internal.metrics.RemovalReason import com.datadog.android.core.internal.persistence.file.FileOrchestrator import com.datadog.android.core.internal.persistence.file.FilePersistenceConfig +import com.datadog.android.internal.tests.stub.StubTimeProvider import com.datadog.android.utils.forge.Configurator import com.datadog.android.utils.verifyLog -import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt index b724abd74b..e32481239e 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt @@ -14,6 +14,7 @@ import fr.xgouchet.elmyr.annotation.LongForgery 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.Test import org.junit.jupiter.api.extension.ExtendWith @@ -24,7 +25,7 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit +import kotlin.system.measureTimeMillis @Extensions( ExtendWith(MockitoExtension::class), @@ -43,12 +44,9 @@ internal class ThreadPoolExecutorExtTest { @Mock lateinit var mockTimeProvider: TimeProvider - @LongForgery(min = 0L) - var fakeElapsedTimeNs: Long = 0L - @BeforeEach fun `set up`() { - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeElapsedTimeNs) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } } @Test @@ -61,21 +59,16 @@ internal class ThreadPoolExecutorExtTest { val fakeCompletedCount = forge.aLong(min = 0, max = fakeTaskCount - 1) whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount).thenReturn(fakeCompletedCount) - val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeElapsedTimeNs) - .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) // WHEN val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) - testedMockExecutor.isIdle() // THEN assertThat(isIdled).isFalse() } @Test - fun `M exit loop after timeout W waitToIdle { executor not idled }`( + fun `M wait max timeout milliseconds W waitToIdle { executor not idled }`( @LongForgery(min = 500, max = 1000) fakeTimeout: Long, forge: Forge ) { @@ -84,16 +77,14 @@ internal class ThreadPoolExecutorExtTest { val fakeCompletedCount = forge.aLong(min = 0, max = fakeTaskCount - 1) whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount).thenReturn(fakeCompletedCount) - val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeElapsedTimeNs) - .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) // WHEN - val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) + val duration = measureTimeMillis { + testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) + } // THEN - assertThat(isIdled).isFalse() + assertThat(duration).isCloseTo(fakeTimeout, Offset.offset(130L)) } @Test @@ -127,10 +118,6 @@ internal class ThreadPoolExecutorExtTest { whenever(testedMockExecutor.taskCount).thenReturn(fakeTaskCount) whenever(testedMockExecutor.completedTaskCount) .thenReturn(fakeTaskCount / 2).thenReturn(fakeTaskCount) - val timeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeElapsedTimeNs) - .thenReturn(fakeElapsedTimeNs + timeoutNs / 2) // WHEN val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) @@ -189,10 +176,6 @@ internal class ThreadPoolExecutorExtTest { whenever(testedMockExecutor.completedTaskCount) .thenReturn(fakeTaskCount / 2) .thenReturn(fakeTaskCount + 2) - val fakeTimeoutNs = TimeUnit.MILLISECONDS.toNanos(fakeTimeout) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) - .thenReturn(fakeElapsedTimeNs) - .thenReturn(fakeElapsedTimeNs + fakeTimeoutNs / 2) // WHEN val isIdled = testedMockExecutor.waitToIdle(fakeTimeout, mockInternalLogger, mockTimeProvider) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt index e41d137727..f252c985be 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt @@ -12,6 +12,7 @@ import android.os.SystemClock import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.DdRumContentProvider +import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -42,39 +43,65 @@ class DefaultAppStartTimeProviderTest { @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, @LongForgery(min = 0L) fakeCurrentTimeNs: Long ) { - // Given + // GIVEN whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeCurrentTimeNs val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val expectedStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) - - // When - val testedAppStartTimeProvider = DefaultAppStartTimeProvider( - mockTimeProvider, + DdRumContentProvider.createTimeNs = expectedStartTimeNs + val testedProvider = DefaultAppStartTimeProvider( + { mockTimeProvider }, mockBuildSdkVersionProvider ) - val providedStartTime = testedAppStartTimeProvider.appStartTimeNs - // Then + // WHEN + val providedStartTime = testedProvider.appStartTimeNs + + // THEN assertThat(providedStartTime).isEqualTo(expectedStartTimeNs) } + @Test + fun `M fall back to DdRumContentProvider W appStartTime { N+ getStartElapsedRealtime returns buggy value }`( + @IntForgery(min = Build.VERSION_CODES.N) apiVersion: Int, + @LongForgery(min = 0L) fakeCurrentTimeNs: Long, + forge: Forge + ) { + // GIVEN + whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion + whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeCurrentTimeNs + val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() + val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) + DdRumContentProvider.createTimeNs = startTimeNs + + forge.aLong(min = DefaultAppStartTimeProvider.PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS + 1) + val testedProvider = DefaultAppStartTimeProvider( + { mockTimeProvider }, + mockBuildSdkVersionProvider + ) + + // WHEN + val providedStartTime = testedProvider.appStartTimeNs + + // THEN + assertThat(providedStartTime).isEqualTo(DdRumContentProvider.createTimeNs) + } + @Test fun `M return content provider load time W appStartTime { Legacy }`( @IntForgery(min = Build.VERSION_CODES.M, max = Build.VERSION_CODES.N) apiVersion: Int ) { - // Given + // GIVEN whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion val startTimeNs = DdRumContentProvider.createTimeNs - - // When - val testedAppStartTimeProvider = DefaultAppStartTimeProvider( - mockTimeProvider, + val testedProvider = DefaultAppStartTimeProvider( + { mockTimeProvider }, mockBuildSdkVersionProvider ) - val providedStartTime = testedAppStartTimeProvider.appStartTimeNs - // Then + // WHEN + val providedStartTime = testedProvider.appStartTimeNs + + // THEN assertThat(providedStartTime).isEqualTo(startTimeNs) } @@ -95,7 +122,7 @@ class DefaultAppStartTimeProviderTest { .doReturn(fakeStartTimeNs + fakeUptimeNs) val testedProvider = DefaultAppStartTimeProvider( - mockTimeProvider, + { mockTimeProvider }, mockBuildSdkVersionProvider ) @@ -123,7 +150,7 @@ class DefaultAppStartTimeProviderTest { .doReturn(fakeStartTimeNs + 200L) val testedProvider = DefaultAppStartTimeProvider( - mockTimeProvider, + { mockTimeProvider }, mockBuildSdkVersionProvider ) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt index 8d16387e45..1c49130216 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt @@ -33,6 +33,7 @@ import fr.xgouchet.elmyr.annotation.StringForgeryType 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.json.JSONArray import org.json.JSONObject import org.junit.jupiter.api.Test @@ -50,6 +51,7 @@ import org.mockito.quality.Strictness import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit +import kotlin.system.measureNanoTime @Extensions( ExtendWith(MockitoExtension::class), @@ -87,6 +89,27 @@ internal class MiscUtilsTest { verify(mockedBlock, times(fakeTimes)).invoke() } + @Test + fun `M execute the block in a delayed loop W retryWithDelay`(forge: Forge) { + // Given + val fakeTimes = forge.anInt(min = 1, max = 4) + val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) + val mockedBlock: () -> Boolean = mock() + whenever(mockedBlock.invoke()).thenReturn(false) + whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + + // When + val executionTime = measureNanoTime { + retryWithDelay(mockedBlock, fakeTimes, fakeDelay, mockInternalLogger, mockTimeProvider) + } + + // Then + assertThat(executionTime).isCloseTo( + fakeTimes * fakeDelay, + Offset.offset(TimeUnit.SECONDS.toNanos(1)) + ) + } + @Test fun `M do nothing W retryWithDelay { times less or equal than 0 }`(forge: Forge) { // Given diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt index c217fe83bc..bd2c712709 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt @@ -21,6 +21,7 @@ import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.thread.waitToIdle import com.datadog.android.core.internal.utils.TAG_DATADOG_UPLOAD import com.datadog.android.core.internal.utils.UPLOAD_WORKER_NAME +import com.datadog.android.internal.tests.stub.StubTimeProvider import com.datadog.android.internal.time.TimeProvider import com.datadog.android.internal.utils.loggableStackTrace import com.datadog.android.utils.config.ApplicationContextTestConfiguration @@ -30,7 +31,6 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.setStaticValue -import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.LongForgery diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index e6be90000a..fa889a7443 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -116,7 +116,7 @@ class com.datadog.android.internal.thread.NamedRunnable : NamedExecutionUnit, Ru constructor(String, Runnable) class com.datadog.android.internal.thread.NamedCallable : NamedExecutionUnit, java.util.concurrent.Callable constructor(String, java.util.concurrent.Callable) -class com.datadog.android.internal.time.DefaultTimeProvider : TimeProvider +class com.datadog.android.internal.time.DefaultTimeProvider : BaseTimeProvider override fun getServerTimestamp(): Long override fun getServerOffsetNanos(): Long override fun getServerOffsetMillis(): Long @@ -126,6 +126,9 @@ interface com.datadog.android.internal.time.TimeProvider fun getDeviceElapsedTimeNs(): Long fun getServerOffsetNanos(): Long fun getServerOffsetMillis(): Long +abstract class com.datadog.android.internal.time.BaseTimeProvider : TimeProvider + override fun getDeviceTimestamp(): Long + override fun getDeviceElapsedTimeNs(): Long fun ByteArray.toHexString(): String interface com.datadog.android.internal.utils.DDCoreSubscription fun addListener(T) diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index a9319aa49e..f1d110e58d 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -294,10 +294,14 @@ public final class com/datadog/android/internal/thread/NamedRunnable : com/datad public fun run ()V } -public final class com/datadog/android/internal/time/DefaultTimeProvider : com/datadog/android/internal/time/TimeProvider { +public abstract class com/datadog/android/internal/time/BaseTimeProvider : com/datadog/android/internal/time/TimeProvider { + public fun ()V + public final fun getDeviceElapsedTimeNs ()J + public final fun getDeviceTimestamp ()J +} + +public final class com/datadog/android/internal/time/DefaultTimeProvider : com/datadog/android/internal/time/BaseTimeProvider { public fun ()V - public fun getDeviceElapsedTimeNs ()J - public fun getDeviceTimestamp ()J public fun getServerOffsetMillis ()J public fun getServerOffsetNanos ()J public fun getServerTimestamp ()J @@ -311,11 +315,6 @@ public abstract interface class com/datadog/android/internal/time/TimeProvider { public abstract fun getServerTimestamp ()J } -public final class com/datadog/android/internal/time/TimeProvider$DefaultImpls { - public static fun getDeviceElapsedTimeNs (Lcom/datadog/android/internal/time/TimeProvider;)J - public static fun getDeviceTimestamp (Lcom/datadog/android/internal/time/TimeProvider;)J -} - public final class com/datadog/android/internal/utils/ByteArrayExtKt { public static final fun toHexString ([B)Ljava/lang/String; } diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt index 452b8ead5c..ae24ccd066 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt @@ -10,10 +10,10 @@ package com.datadog.android.internal.time * A [TimeProvider] implementation that provides the current device time as both device and server time. * The offsets are always 0. * - * Device timestamp and elapsed time are inherited from [TimeProvider] default implementations. + * Device timestamp and elapsed time are inherited from [BaseTimeProvider]. */ -class DefaultTimeProvider : TimeProvider { - override fun getServerTimestamp(): Long = System.currentTimeMillis() +class DefaultTimeProvider : BaseTimeProvider() { + override fun getServerTimestamp(): Long = getDeviceTimestamp() override fun getServerOffsetNanos(): Long = 0L diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt index 03c348c5ae..e734a33dd8 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt @@ -15,11 +15,9 @@ interface TimeProvider { /** * Returns the current device timestamp in milliseconds. - * - * Default implementation returns [System.currentTimeMillis]. - * This should not be overridden as device time always refers to the local system clock. + * This is implemented in [BaseTimeProvider] as [System.currentTimeMillis]. */ - fun getDeviceTimestamp(): Long = System.currentTimeMillis() + fun getDeviceTimestamp(): Long /** * Returns the current server timestamp in milliseconds. @@ -27,12 +25,10 @@ interface TimeProvider { fun getServerTimestamp(): Long /** - * Returns the current device elapsed time in nanoseconds. - * - * Default implementation returns [System.nanoTime]. - * This should not be overridden as device elapsed time always refers to the monotonic system clock. + * Returns the current device monotonic elapsed time in nanoseconds. + * This is implemented in [BaseTimeProvider] as [System.nanoTime]. */ - fun getDeviceElapsedTimeNs(): Long = System.nanoTime() + fun getDeviceElapsedTimeNs(): Long /** * Returns the offset between the device and server time references in nanoseconds. @@ -44,3 +40,11 @@ interface TimeProvider { */ fun getServerOffsetMillis(): Long } + +/** + * A base implementation of [TimeProvider] that provides the device time using system calls. + */ +abstract class BaseTimeProvider : TimeProvider { + final override fun getDeviceTimestamp(): Long = System.currentTimeMillis() + final override fun getDeviceElapsedTimeNs(): Long = System.nanoTime() +} diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt index d45a84a5a6..5a85306907 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt @@ -206,7 +206,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasBuildId(fakeDatadogContext.appBuildId) @@ -286,7 +286,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -350,7 +350,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -674,7 +674,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(threadName) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -736,7 +736,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .doesNotHaveNetworkInfo() .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -803,7 +803,7 @@ internal class DatadogLogHandlerTest { .hasThreadName(Thread.currentThread().name) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .doesNotHaveNetworkInfo() .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) @@ -1110,7 +1110,7 @@ internal class DatadogLogHandlerTest { .hasLoggerName(fakeLoggerName) .hasStatus(fakeLevel.asLogStatus()) .hasMessage(fakeMessage) - .hasDateAround(fakeTimestamp) + .hasDate(fakeTimestamp.toIsoFormattedTimestamp()) .hasNetworkInfo(fakeDatadogContext.networkInfo) .hasUserInfo(fakeDatadogContext.userInfo) .hasAccountInfo(fakeDatadogContext.accountInfo) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt index 909fd12602..13a70bd2b0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumContinuousActionScopeTest.kt @@ -305,13 +305,13 @@ internal class RumContinuousActionScopeTest { val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) expectedAttributes.putAll(attributes) - val stopActionTime = TEST_INACTIVITY_MS * 2 + val stopActionDelay = TEST_INACTIVITY_MS * 2 // When - fakeEvent = RumRawEvent.StopAction(type, name, attributes, timeWithOffset(stopActionTime)) + fakeEvent = RumRawEvent.StopAction(type, name, attributes, timeWithOffset(stopActionDelay)) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result2 = testedScope.handleEvent( - mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopActionDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -382,13 +382,13 @@ internal class RumContinuousActionScopeTest { val expectedAttributes = mutableMapOf() expectedAttributes.putAll(fakeAttributes) expectedAttributes.putAll(attributes) - val stopActionTime = TEST_INACTIVITY_MS * 2 + val stopActionDelay = TEST_INACTIVITY_MS * 2 // When - fakeEvent = RumRawEvent.StopAction(null, null, attributes, timeWithOffset(stopActionTime)) + fakeEvent = RumRawEvent.StopAction(null, null, attributes, timeWithOffset(stopActionDelay)) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result2 = testedScope.handleEvent( - mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopActionDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -465,11 +465,11 @@ internal class RumContinuousActionScopeTest { fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopResourceTime = TEST_INACTIVITY_MS * 2 - fakeEvent = RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap(), timeWithOffset(stopResourceTime)) + val stopResourceDelay = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopResource(key, statusCode, size, kind, emptyMap(), timeWithOffset(stopResourceDelay)) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result4 = testedScope.handleEvent( - mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2 + 1), + mockEvent(stopResourceDelay + TEST_INACTIVITY_MS * 2 + 1), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -548,7 +548,7 @@ internal class RumContinuousActionScopeTest { fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopResourceTime = TEST_INACTIVITY_MS * 2 + val stopResourceDelay = TEST_INACTIVITY_MS * 2 fakeEvent = RumRawEvent.StopResourceWithError( key, statusCode, @@ -556,11 +556,11 @@ internal class RumContinuousActionScopeTest { source, throwable, emptyMap(), - timeWithOffset(stopResourceTime) + timeWithOffset(stopResourceDelay) ) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result4 = testedScope.handleEvent( - mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopResourceDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -652,7 +652,7 @@ internal class RumContinuousActionScopeTest { fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopResourceTime = TEST_INACTIVITY_MS * 2 + val stopResourceDelay = TEST_INACTIVITY_MS * 2 fakeEvent = RumRawEvent.StopResourceWithStackTrace( key, statusCode, @@ -661,11 +661,11 @@ internal class RumContinuousActionScopeTest { stackTrace, errorType, emptyMap(), - timeWithOffset(stopResourceTime) + timeWithOffset(stopResourceDelay) ) val result3 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result4 = testedScope.handleEvent( - mockEvent(stopResourceTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopResourceDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -749,12 +749,12 @@ internal class RumContinuousActionScopeTest { fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), fakeEventTime) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val gcTime = TEST_INACTIVITY_MS * 2 - fakeEvent = mockEvent(gcTime) + val gcDelay = TEST_INACTIVITY_MS * 2 + fakeEvent = mockEvent(gcDelay) @Suppress("UNUSED_VALUE") key = null System.gc() - val result3 = testedScope.handleEvent(mockEvent(gcTime), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result3 = testedScope.handleEvent(mockEvent(gcDelay), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then argumentCaptor { @@ -828,12 +828,12 @@ internal class RumContinuousActionScopeTest { attributes = emptyMap() ) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopActionTime = TEST_INACTIVITY_MS * 2 - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) + val stopActionDelay = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionDelay)) val expectedStoppedTimestamp = fakeEvent.eventTime.nanoTime val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result3 = testedScope.handleEvent( - mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopActionDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -2740,11 +2740,11 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopActionTime = TEST_INACTIVITY_MS * 2 - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) + val stopActionDelay = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionDelay)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result3 = testedScope.handleEvent( - mockEvent(stopActionTime + TEST_INACTIVITY_MS * 2), + mockEvent(stopActionDelay + TEST_INACTIVITY_MS * 2), fakeDatadogContext, mockEventWriteScope, mockWriter @@ -2833,11 +2833,11 @@ internal class RumContinuousActionScopeTest { // When fakeEvent = RumRawEvent.StartResource(key, url, method, emptyMap()) val result = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - val stopActionTime = TEST_INACTIVITY_MS * 2 - fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionTime)) + val stopActionDelay = TEST_INACTIVITY_MS * 2 + fakeEvent = RumRawEvent.StopAction(fakeType, fakeName, emptyMap(), timeWithOffset(stopActionDelay)) val result2 = testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) val result3 = testedScope.handleEvent( - mockEvent(stopActionTime + TEST_MAX_DURATION_MS), + mockEvent(stopActionDelay + TEST_MAX_DURATION_MS), fakeDatadogContext, mockEventWriteScope, mockWriter diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 89a347948b..d9fe19ff59 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -18,7 +18,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.api.storage.NoOpDataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver -import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.internal.tests.stub.StubTimeProvider import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.InfoProvider @@ -41,7 +41,6 @@ import com.datadog.android.rum.model.RumVitalAppLaunchEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.forge.Configurator import com.datadog.tools.unit.forge.exhaustiveAttributes -import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt index 827f8b83d5..48840adcf0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategyTest.kt @@ -7,6 +7,7 @@ package com.datadog.android.rum.internal.instrumentation import android.os.Looper +import com.datadog.android.internal.tests.stub.StubTimeProvider import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor import com.datadog.android.rum.utils.config.GlobalRumMonitorTestConfiguration import com.datadog.android.rum.utils.forge.Configurator @@ -16,7 +17,6 @@ import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.getStaticValue import com.datadog.tools.unit.setStaticValue -import com.datadog.tools.unit.stub.StubTimeProvider import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -33,7 +33,6 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -124,7 +123,7 @@ internal class MainLooperLongTaskStrategyTest : ObjectTest = mutableMapOf() @@ -94,7 +95,10 @@ class DatadogVitalsMeter private constructor(private val meter: Meter) : Datadog /** * Creates an instance of [DatadogVitalsMeter] with the given configuration. */ - fun create(datadogExporterConfiguration: DatadogExporterConfiguration): DatadogVitalsMeter { + fun create( + datadogExporterConfiguration: DatadogExporterConfiguration, + sdkCore: SdkCore + ): DatadogVitalsMeter { val datadogExporter = DatadogMetricExporter(datadogExporterConfiguration) val sdkMeterProvider = SdkMeterProvider.builder() .registerMetricReader( @@ -113,7 +117,7 @@ class DatadogVitalsMeter private constructor(private val meter: Meter) : Datadog GlobalOpenTelemetry.set(openTelemetry) GlobalBenchmark.register(DDBenchmarkProfiler()) val meter = openTelemetry.getMeter(METER_INSTRUMENTATION_SCOPE_NAME) - return DatadogVitalsMeter(meter) + return DatadogVitalsMeter(meter, sdkCore) } private const val METER_INSTRUMENTATION_SCOPE_NAME = "datadog.open-telemetry" diff --git a/tools/unit/build.gradle.kts b/tools/unit/build.gradle.kts index a14039a77b..bab94aa56e 100644 --- a/tools/unit/build.gradle.kts +++ b/tools/unit/build.gradle.kts @@ -68,7 +68,6 @@ dependencies { implementation(libs.bundles.testTools) implementation(libs.gson) implementation(libs.mockitoKotlin) - implementation(project(":dd-sdk-android-internal")) testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) From 733a513d73b345b29f1243677d5cdd2adc4052bd Mon Sep 17 00:00:00 2001 From: Francisco Veiga Date: Fri, 2 Jan 2026 12:13:27 +0000 Subject: [PATCH 5/5] RUM-10363: Improve TimeProvider.kt functions' names --- .../android/core/internal/NoOpInternalSdkCore.kt | 2 +- .../internal/data/upload/RotatingDnsResolver.kt | 8 ++++++-- .../internal/metrics/BatchMetricsDispatcher.kt | 2 +- .../internal/metrics/MethodCalledTelemetry.kt | 4 ++-- .../file/batch/BatchFileOrchestrator.kt | 16 ++++++++-------- .../thread/BackPressuredBlockingQueue.kt | 2 +- .../internal/thread/ThreadPoolExecutorExt.kt | 4 ++-- .../internal/time/DefaultAppStartTimeProvider.kt | 4 ++-- .../core/internal/time/KronosTimeProvider.kt | 6 +++--- .../core/internal/time/TimeProviderExt.kt | 4 ++-- .../android/core/internal/utils/MiscUtils.kt | 6 +++--- .../com/datadog/android/core/DatadogCoreTest.kt | 4 ++-- .../core/internal/DatadogContextProviderTest.kt | 4 ++-- .../data/upload/RotatingDnsResolverTest.kt | 2 +- .../metrics/BatchMetricsDispatcherTest.kt | 2 +- .../metrics/MethodCalledTelemetryTest.kt | 2 +- .../advanced/MoveDataMigrationOperationTest.kt | 6 +++--- .../advanced/WipeDataMigrationOperationTest.kt | 4 ++-- .../internal/thread/ThreadPoolExecutorExtTest.kt | 2 +- .../time/DefaultAppStartTimeProviderTest.kt | 8 ++++---- .../core/internal/time/KronosTimeProviderTest.kt | 10 ++++++---- .../android/core/internal/utils/MiscUtilsTest.kt | 4 ++-- .../internal/DatadogExceptionHandlerTest.kt | 2 +- dd-sdk-android-internal/api/apiSurface | 12 ++++++------ .../api/dd-sdk-android-internal.api | 12 ++++++------ .../internal/profiler/DDExecutionTimer.kt | 4 ++-- .../android/internal/time/DefaultTimeProvider.kt | 2 +- .../android/internal/time/TimeProvider.kt | 10 +++++----- .../internal/tests/stub/StubTimeProvider.kt | 6 +++--- .../flags/internal/ExposureEventsProcessor.kt | 2 +- .../repository/DefaultFlagsRepository.kt | 2 +- .../internal/ExposureEventsProcessorTest.kt | 4 ++-- .../log/internal/logger/DatadogLogHandler.kt | 4 ++-- .../log/internal/logger/DatadogLogHandlerTest.kt | 2 +- .../ndk/internal/NdkCrashReportsFeature.kt | 4 ++-- .../rum/internal/DatadogLateCrashReporter.kt | 2 +- .../accessibility/DefaultAccessibilityReader.kt | 2 +- .../rum/internal/domain/scope/RumSessionScope.kt | 4 ++-- .../MainLooperLongTaskStrategy.kt | 2 +- .../metric/slowframes/SlowFramesListener.kt | 2 +- .../internal/startup/RumFirstDrawTimeReporter.kt | 2 +- .../rum/resource/RumResourceInputStream.kt | 14 +++++++------- .../rum/internal/DatadogLateCrashReporterTest.kt | 2 +- .../DefaultAccessibilityReaderTest.kt | 4 ++-- .../rum/resource/RumResourceInputStreamTest.kt | 2 +- .../internal/async/RecordedDataQueueHandler.kt | 8 ++++---- .../internal/processor/RecordedDataProcessor.kt | 4 ++-- .../internal/processor/RumContextDataHandler.kt | 2 +- .../sessionreplay/internal/recorder/Debouncer.kt | 12 ++++++------ .../recorder/callback/RecorderWindowCallback.kt | 15 ++++++++------- .../resources/ResourceDataStoreManager.kt | 4 ++-- .../async/RecordedDataQueueHandlerTest.kt | 2 +- .../processor/RecordedDataProcessorTest.kt | 2 +- .../RecordedQueuedItemContextHandlerTest.kt | 2 +- .../internal/recorder/DebouncerTest.kt | 12 ++++++------ .../callback/RecorderWindowCallbackTest.kt | 4 ++-- .../resources/ResourceDataStoreManagerTest.kt | 2 +- .../android/trace/internal/DatadogSpanLogger.kt | 2 +- .../trace/internal/DatadogSpanLoggerTest.kt | 2 +- .../rum/domain/WebViewNativeRumViewsCache.kt | 2 +- .../rum/domain/WebViewNativeRumViewsCacheTest.kt | 8 ++++---- .../com/datadog/android/core/stub/StubSDKCore.kt | 8 ++++---- .../benchmark/internal/reader/FpsVitalReader.kt | 4 ++-- 63 files changed, 155 insertions(+), 148 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt index 566d436747..7e8724db91 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt @@ -47,7 +47,7 @@ internal object NoOpInternalSdkCore : InternalSdkCore { override val name: String = "no-op" - override val time: TimeInfo = with(timeProvider.getDeviceTimestamp()) { + override val time: TimeInfo = with(timeProvider.getDeviceTimestampMillis()) { TimeInfo.EMPTY.copy( deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(this), serverTimeNs = TimeUnit.MILLISECONDS.toNanos(this), diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt index 424fced90b..17f6aaf55d 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolver.kt @@ -51,7 +51,11 @@ internal class RotatingDnsResolver( } else { @Suppress("UnsafeThirdPartyFunctionCall") // handled by caller val result = delegate.lookup(hostname) - knownHosts[hostname] = ResolvedHost(hostname, result.toMutableList(), timeProvider.getDeviceElapsedTimeNs()) + knownHosts[hostname] = ResolvedHost( + hostname, + result.toMutableList(), + timeProvider.getDeviceElapsedTimeNanos() + ) safeCopy(result) } } @@ -67,7 +71,7 @@ internal class RotatingDnsResolver( } private fun isValid(knownHost: ResolvedHost): Boolean { - return knownHost.getAge(timeProvider.getDeviceElapsedTimeNs()) < ttl && knownHost.addresses.isNotEmpty() + return knownHost.getAge(timeProvider.getDeviceElapsedTimeNanos()) < ttl && knownHost.addresses.isNotEmpty() } // endregion diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt index 925f1a483a..bb5c81b50d 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcher.kt @@ -90,7 +90,7 @@ internal class BatchMetricsDispatcher( numPendingBatches: Int ): Map? { val fileCreationTimestamp = file.nameAsTimestampSafe(internalLogger) ?: return null - val fileAgeInMillis = timeProvider.getDeviceTimestamp() - fileCreationTimestamp + val fileAgeInMillis = timeProvider.getDeviceTimestampMillis() - fileCreationTimestamp if (fileAgeInMillis < 0) { // the device time was manually modified or the time zone changed // we are dropping this metric to not skew our charts diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt index b5392edd0c..56524c3de1 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetry.kt @@ -28,10 +28,10 @@ internal class MethodCalledTelemetry( internal val timeProvider: TimeProvider ) : PerformanceMetric { - internal val startTime: Long = timeProvider.getDeviceElapsedTimeNs() + internal val startTime: Long = timeProvider.getDeviceElapsedTimeNanos() override fun stopAndSend(isSuccessful: Boolean) { - val executionTime = timeProvider.getDeviceElapsedTimeNs() - startTime + val executionTime = timeProvider.getDeviceElapsedTimeNanos() - startTime val additionalProperties: MutableMap = mutableMapOf() additionalProperties[EXECUTION_TIME] = executionTime diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt index 01a740f6b9..6f8dd65886 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestrator.kt @@ -66,7 +66,7 @@ internal class BatchFileOrchestrator( var files = listBatchFiles() files = deleteObsoleteFiles(files) freeSpaceIfNeeded(files) - lastCleanupTimestamp = timeProvider.getDeviceTimestamp() + lastCleanupTimestamp = timeProvider.getDeviceTimestampMillis() } return getReusableWritableFile() ?: createNewFile() @@ -81,7 +81,7 @@ internal class BatchFileOrchestrator( val files = listSortedBatchFiles().let { deleteObsoleteFiles(it) } - lastCleanupTimestamp = timeProvider.getDeviceTimestamp() + lastCleanupTimestamp = timeProvider.getDeviceTimestampMillis() pendingFiles.set(files.count()) return files.firstOrNull { @@ -204,7 +204,7 @@ internal class BatchFileOrchestrator( } private fun createNewFile(): File { - val newFileName = timeProvider.getDeviceTimestamp().toString() + val newFileName = timeProvider.getDeviceTimestampMillis().toString() val newFile = File(rootDir, newFileName) val closedFile = previousFile val closedFileLastAccessTimestamp = lastFileAccessTimestamp @@ -219,7 +219,7 @@ internal class BatchFileOrchestrator( } previousFile = newFile previousFileItemCount = 1 - lastFileAccessTimestamp = timeProvider.getDeviceTimestamp() + lastFileAccessTimestamp = timeProvider.getDeviceTimestampMillis() pendingFiles.incrementAndGet() return newFile } @@ -246,7 +246,7 @@ internal class BatchFileOrchestrator( return if (isRecentEnough && hasRoomForMore && hasSlotForMore) { previousFileItemCount = lastKnownFileItemCount + 1 - lastFileAccessTimestamp = timeProvider.getDeviceTimestamp() + lastFileAccessTimestamp = timeProvider.getDeviceTimestampMillis() lastFile } else { null @@ -254,13 +254,13 @@ internal class BatchFileOrchestrator( } private fun isFileRecent(file: File, delayMs: Long): Boolean { - val now = timeProvider.getDeviceTimestamp() + val now = timeProvider.getDeviceTimestampMillis() val fileTimestamp = file.name.toLongOrNull() ?: 0L return fileTimestamp >= (now - delayMs) } private fun deleteObsoleteFiles(files: List): List { - val threshold = timeProvider.getDeviceTimestamp() - config.oldFileThreshold + val threshold = timeProvider.getDeviceTimestampMillis() - config.oldFileThreshold return files .mapNotNull { val isOldFile = (it.name.toLongOrNull() ?: 0) < threshold @@ -330,7 +330,7 @@ internal class BatchFileOrchestrator( } private fun canDoCleanup(): Boolean { - return timeProvider.getDeviceTimestamp() - lastCleanupTimestamp > config.cleanupFrequencyThreshold + return timeProvider.getDeviceTimestampMillis() - lastCleanupTimestamp > config.cleanupFrequencyThreshold } private val File.metadata: File diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt index 683039802b..0e7c60c604 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/BackPressuredBlockingQueue.kt @@ -122,7 +122,7 @@ internal class BackPressuredBlockingQueue : ObservableLinkedBlockingQue } private fun notifyThresholdReached() { - val dump = dumpQueue(timeProvider.getDeviceTimestamp()) + val dump = dumpQueue(timeProvider.getDeviceTimestampMillis()) val backPressureMap = buildMap { put("capacity", capacity) if (!dump.isNullOrEmpty()) { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt index c7775b1938..c1abbee807 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExt.kt @@ -18,7 +18,7 @@ internal fun ThreadPoolExecutor.waitToIdle( internalLogger: InternalLogger, timeProvider: TimeProvider ): Boolean { - val startTime = timeProvider.getDeviceElapsedTimeNs() + val startTime = timeProvider.getDeviceElapsedTimeNanos() val timeoutInNs = TimeUnit.MILLISECONDS.toNanos(timeoutInMs) val sleepDurationInMs = timeoutInMs.coerceIn(0, MAX_SLEEP_DURATION_IN_MS) var interrupted: Boolean @@ -27,7 +27,7 @@ internal fun ThreadPoolExecutor.waitToIdle( return true } interrupted = sleepSafe(sleepDurationInMs, internalLogger) - } while (((timeProvider.getDeviceElapsedTimeNs() - startTime) < timeoutInNs) && !interrupted) + } while (((timeProvider.getDeviceElapsedTimeNanos() - startTime) < timeoutInNs) && !interrupted) return isIdle() } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt index 69da956fbb..5fefd086e5 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProvider.kt @@ -26,7 +26,7 @@ internal class DefaultAppStartTimeProvider( when { buildSdkVersionProvider.version >= Build.VERSION_CODES.N -> { val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() - val result = timeProviderFactory().getDeviceElapsedTimeNs() - TimeUnit.MILLISECONDS.toNanos(diffMs) + val result = timeProviderFactory().getDeviceElapsedTimeNanos() - TimeUnit.MILLISECONDS.toNanos(diffMs) /** * Occasionally [Process.getStartElapsedRealtime] returns buggy values. We filter them and fall back @@ -43,7 +43,7 @@ internal class DefaultAppStartTimeProvider( } override val appUptimeNs: Long - get() = timeProviderFactory().getDeviceElapsedTimeNs() - appStartTimeNs + get() = timeProviderFactory().getDeviceElapsedTimeNanos() - appStartTimeNs companion object { internal val PROCESS_START_TO_CP_START_DIFF_THRESHOLD_NS = 10.seconds.inWholeNanoseconds diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt index 1bc54392da..086084e19d 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt @@ -22,15 +22,15 @@ internal class KronosTimeProvider( private val internalLogger: InternalLogger ) : BaseTimeProvider() { - override fun getServerTimestamp(): Long { + override fun getServerTimestampMillis(): Long { return clock.safeGetCurrentTimeMs() - .getOrElse { getDeviceTimestamp() } + .getOrElse { getDeviceTimestampMillis() } } override fun getServerOffsetMillis(): Long { return clock.safeGetCurrentTimeMs() .map { server -> - val device = getDeviceTimestamp() + val device = getDeviceTimestampMillis() val delta = server - device delta } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/TimeProviderExt.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/TimeProviderExt.kt index 35efbfc8c9..a0632788ad 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/TimeProviderExt.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/TimeProviderExt.kt @@ -10,8 +10,8 @@ import com.datadog.android.internal.time.TimeProvider import java.util.concurrent.TimeUnit internal fun TimeProvider.composeTimeInfo(): TimeInfo { - val deviceTimeMs = getDeviceTimestamp() - val serverTimeMs = getServerTimestamp() + val deviceTimeMs = getDeviceTimestampMillis() + val serverTimeMs = getServerTimestampMillis() return TimeInfo( deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(deviceTimeMs), serverTimeNs = TimeUnit.MILLISECONDS.toNanos(serverTimeMs), diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt index 878daf7b72..d0462fcc3a 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/MiscUtils.kt @@ -40,9 +40,9 @@ internal inline fun retryWithDelay( ): Boolean { var retryCounter = 1 var wasSuccessful = false - var loopTimeOrigin = timeProvider.getDeviceElapsedTimeNs() - loopsDelayInNanos + var loopTimeOrigin = timeProvider.getDeviceElapsedTimeNanos() - loopsDelayInNanos while (retryCounter <= times && !wasSuccessful) { - if ((timeProvider.getDeviceElapsedTimeNs() - loopTimeOrigin) >= loopsDelayInNanos) { + if ((timeProvider.getDeviceElapsedTimeNanos() - loopTimeOrigin) >= loopsDelayInNanos) { wasSuccessful = try { block() } catch (e: Exception) { @@ -57,7 +57,7 @@ internal inline fun retryWithDelay( ) false } - loopTimeOrigin = timeProvider.getDeviceElapsedTimeNs() + loopTimeOrigin = timeProvider.getDeviceElapsedTimeNanos() retryCounter++ } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index 593dc6b458..71aaac6fc3 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -992,9 +992,9 @@ internal class DatadogCoreTest { fakeServerTimeOffsetMs ) whenever(mockTimeProvider.getServerOffsetMillis()) doReturn fakeServerTimeOffsetMs - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeDeviceTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeDeviceTimestamp whenever( - mockTimeProvider.getServerTimestamp() + mockTimeProvider.getServerTimestampMillis() ) doReturn fakeDeviceTimestamp + fakeServerTimeOffsetMs // When diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/DatadogContextProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/DatadogContextProviderTest.kt index 6ffc049f73..5ba303a16c 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/DatadogContextProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/DatadogContextProviderTest.kt @@ -88,9 +88,9 @@ internal class DatadogContextProviderTest { whenever(coreFeature.mockInstance.androidInfoProvider) doReturn fakeAndroidInfo - whenever(coreFeature.mockInstance.timeProvider.getDeviceTimestamp()) doReturn + whenever(coreFeature.mockInstance.timeProvider.getDeviceTimestampMillis()) doReturn fakeDeviceTimestamp - whenever(coreFeature.mockInstance.timeProvider.getServerTimestamp()) doReturn + whenever(coreFeature.mockInstance.timeProvider.getServerTimestampMillis()) doReturn fakeServerTimestamp whenever(coreFeature.mockInstance.trackingConsentProvider.getConsent()) doReturn diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt index 0e35e23bf0..3a76a6ce9c 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/RotatingDnsResolverTest.kt @@ -102,7 +102,7 @@ internal class RotatingDnsResolverTest { // When val result = testedDns.lookup(fakeHostname) val fakeExpiredTime = fakeElapsedTimeNs + TEST_TTL_MS.inWholeNanoseconds - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeExpiredTime val result2 = testedDns.lookup(fakeHostname) // Then diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt index 3ff98bd2b8..a784c55900 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/BatchMetricsDispatcherTest.kt @@ -81,7 +81,7 @@ internal class BatchMetricsDispatcherTest { Feature.SESSION_REPLAY_RESOURCES_FEATURE_NAME ) ) - whenever(mockTimeProvider.getDeviceTimestamp()).doReturn(fakeTimestamp) + whenever(mockTimeProvider.getDeviceTimestampMillis()).doReturn(fakeTimestamp) testedBatchMetricsDispatcher = BatchMetricsDispatcher( fakeFeatureName, fakeUploadConfiguration, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt index b0ee3395f1..00424410f3 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/metrics/MethodCalledTelemetryTest.kt @@ -114,7 +114,7 @@ internal class MethodCalledTelemetryTest { whenever(mockDeviceInfo.osVersion).thenReturn(fakeOsVersion) whenever(mockDeviceInfo.deviceBuildId).thenReturn(fakeOsBuild) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) .thenReturn(fakeStartTimeNs) .thenReturn(fakeStartTimeNs + fakeElapsedTimeNs) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt index 4d3a13c903..1ef280087e 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/MoveDataMigrationOperationTest.kt @@ -64,7 +64,7 @@ internal class MoveDataMigrationOperationTest { @BeforeEach fun `set up`() { val currentTime = AtomicLong(fakeElapsedTimeNs) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { currentTime.getAndAdd(RETRY_DELAY_NS) } @@ -152,7 +152,7 @@ internal class MoveDataMigrationOperationTest { @Test fun `M retry with 500ms delay W run() {move fails once}`() { // Given - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { System.nanoTime() } whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false, true) @@ -182,7 +182,7 @@ internal class MoveDataMigrationOperationTest { @Test fun `M retry with 500ms delay W run() {move always fails}`() { // Given - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { System.nanoTime() } whenever(mockFileMover.moveFiles(fakeFromDirectory, fakeToDirectory)) .doReturn(false) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt index 1ba7660519..86dcfe62a9 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/advanced/WipeDataMigrationOperationTest.kt @@ -62,7 +62,7 @@ internal class WipeDataMigrationOperationTest { @BeforeEach fun `set up`() { val currentTime = AtomicLong(fakeElapsedTimeNs) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { currentTime.getAndAdd(RETRY_DELAY_NS) } @@ -136,7 +136,7 @@ internal class WipeDataMigrationOperationTest { @Test fun `M retry with 500ms delay W run() {move always fails}`() { // Given - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { System.nanoTime() } whenever(mockFileMover.delete(fakeTargetDirectory)) .doReturn(false) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt index e32481239e..51ece5b52d 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/thread/ThreadPoolExecutorExtTest.kt @@ -46,7 +46,7 @@ internal class ThreadPoolExecutorExtTest { @BeforeEach fun `set up`() { - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { System.nanoTime() } } @Test diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt index f252c985be..5b69760981 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/DefaultAppStartTimeProviderTest.kt @@ -45,7 +45,7 @@ class DefaultAppStartTimeProviderTest { ) { // GIVEN whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeCurrentTimeNs + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeCurrentTimeNs val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val expectedStartTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) DdRumContentProvider.createTimeNs = expectedStartTimeNs @@ -69,7 +69,7 @@ class DefaultAppStartTimeProviderTest { ) { // GIVEN whenever(mockBuildSdkVersionProvider.version) doReturn apiVersion - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeCurrentTimeNs + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeCurrentTimeNs val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val startTimeNs = fakeCurrentTimeNs - TimeUnit.MILLISECONDS.toNanos(diffMs) DdRumContentProvider.createTimeNs = startTimeNs + @@ -117,7 +117,7 @@ class DefaultAppStartTimeProviderTest { val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val fakeCurrentTimeNs = fakeStartTimeNs + TimeUnit.MILLISECONDS.toNanos(diffMs) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) .doReturn(fakeCurrentTimeNs) .doReturn(fakeStartTimeNs + fakeUptimeNs) @@ -144,7 +144,7 @@ class DefaultAppStartTimeProviderTest { val diffMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime() val fakeCurrentTimeNs = fakeStartTimeNs + TimeUnit.MILLISECONDS.toNanos(diffMs) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) .doReturn(fakeCurrentTimeNs) .doReturn(fakeStartTimeNs + 100L) .doReturn(fakeStartTimeNs + 200L) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/KronosTimeProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/KronosTimeProviderTest.kt index 9a9a147005..bc0fb79271 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/KronosTimeProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/KronosTimeProviderTest.kt @@ -59,7 +59,7 @@ internal class KronosTimeProviderTest { @Test fun `returns clock's time as server time`() { - val result = testedTimeProvider.getServerTimestamp() + val result = testedTimeProvider.getServerTimestampMillis() assertThat(result).isEqualTo(fakeDate.time) } @@ -91,7 +91,7 @@ internal class KronosTimeProviderTest { @Test fun `returns device time`() { val now = System.currentTimeMillis() - val result = testedTimeProvider.getDeviceTimestamp() + val result = testedTimeProvider.getDeviceTimestampMillis() assertThat(result).isCloseTo(now, Offset.offset(TEST_OFFSET)) } @@ -118,14 +118,16 @@ internal class KronosTimeProviderTest { } @Test - fun `M log and return System currentTimeMillis W getServerTimestamp { getCurrentTimeMs throws }`(forge: Forge) { + fun `M log and return System currentTimeMillis W getServerTimestampMillis { getCurrentTimeMs throws }`( + forge: Forge + ) { // Given val exception = forge.anException() whenever(mockClock.getCurrentTimeMs()) doThrow exception // When val now = System.currentTimeMillis() - val result = testedTimeProvider.getServerTimestamp() + val result = testedTimeProvider.getServerTimestampMillis() // Then internalLogger.verifyLog( diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt index 1c49130216..be64acff3e 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/MiscUtilsTest.kt @@ -96,7 +96,7 @@ internal class MiscUtilsTest { val fakeDelay = TimeUnit.SECONDS.toNanos(forge.aLong(min = 0, max = 2)) val mockedBlock: () -> Boolean = mock() whenever(mockedBlock.invoke()).thenReturn(false) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { System.nanoTime() } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { System.nanoTime() } // When val executionTime = measureNanoTime { @@ -331,7 +331,7 @@ internal class MiscUtilsTest { get() = TimeUnit.SECONDS.toNanos(fakeDelaySeconds) private fun stubTimeProviderWithDelay() { - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { fakeCurrentTimeNs += fakeDelayNs fakeCurrentTimeNs } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt index bd2c712709..03993036b7 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt @@ -197,7 +197,7 @@ internal class DatadogExceptionHandlerTest { whenever(mockSdkCore.getPersistenceExecutorService()) doReturn mockScheduledThreadExecutor whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider val timeoutNs = DatadogExceptionHandler.MAX_WAIT_FOR_IDLE_TIME_IN_MS * 1_000_000L - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) .thenReturn(fakeElapsedTimeNs) .thenReturn(fakeElapsedTimeNs + timeoutNs + 1) Thread.setDefaultUncaughtExceptionHandler(null) diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index fa889a7443..ff103ab31f 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -117,18 +117,18 @@ class com.datadog.android.internal.thread.NamedRunnable : NamedExecutionUnit, Ru class com.datadog.android.internal.thread.NamedCallable : NamedExecutionUnit, java.util.concurrent.Callable constructor(String, java.util.concurrent.Callable) class com.datadog.android.internal.time.DefaultTimeProvider : BaseTimeProvider - override fun getServerTimestamp(): Long + override fun getServerTimestampMillis(): Long override fun getServerOffsetNanos(): Long override fun getServerOffsetMillis(): Long interface com.datadog.android.internal.time.TimeProvider - fun getDeviceTimestamp(): Long - fun getServerTimestamp(): Long - fun getDeviceElapsedTimeNs(): Long + fun getDeviceTimestampMillis(): Long + fun getServerTimestampMillis(): Long + fun getDeviceElapsedTimeNanos(): Long fun getServerOffsetNanos(): Long fun getServerOffsetMillis(): Long abstract class com.datadog.android.internal.time.BaseTimeProvider : TimeProvider - override fun getDeviceTimestamp(): Long - override fun getDeviceElapsedTimeNs(): Long + override fun getDeviceTimestampMillis(): Long + override fun getDeviceElapsedTimeNanos(): Long fun ByteArray.toHexString(): String interface com.datadog.android.internal.utils.DDCoreSubscription fun addListener(T) diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index f1d110e58d..733f5e7d84 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -296,23 +296,23 @@ public final class com/datadog/android/internal/thread/NamedRunnable : com/datad public abstract class com/datadog/android/internal/time/BaseTimeProvider : com/datadog/android/internal/time/TimeProvider { public fun ()V - public final fun getDeviceElapsedTimeNs ()J - public final fun getDeviceTimestamp ()J + public final fun getDeviceElapsedTimeNanos ()J + public final fun getDeviceTimestampMillis ()J } public final class com/datadog/android/internal/time/DefaultTimeProvider : com/datadog/android/internal/time/BaseTimeProvider { public fun ()V public fun getServerOffsetMillis ()J public fun getServerOffsetNanos ()J - public fun getServerTimestamp ()J + public fun getServerTimestampMillis ()J } public abstract interface class com/datadog/android/internal/time/TimeProvider { - public abstract fun getDeviceElapsedTimeNs ()J - public abstract fun getDeviceTimestamp ()J + public abstract fun getDeviceElapsedTimeNanos ()J + public abstract fun getDeviceTimestampMillis ()J public abstract fun getServerOffsetMillis ()J public abstract fun getServerOffsetNanos ()J - public abstract fun getServerTimestamp ()J + public abstract fun getServerTimestampMillis ()J } public final class com/datadog/android/internal/utils/ByteArrayExtKt { diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt index b1366b2101..fd4f886136 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/DDExecutionTimer.kt @@ -19,9 +19,9 @@ internal class DDExecutionTimer( return action() } - val requestStartTime = timeProvider.getDeviceElapsedTimeNs() + val requestStartTime = timeProvider.getDeviceElapsedTimeNanos() val result = action() - val latencyInSeconds = (timeProvider.getDeviceElapsedTimeNs() - requestStartTime) / NANOSECONDS_IN_A_SECOND + val latencyInSeconds = (timeProvider.getDeviceElapsedTimeNanos() - requestStartTime) / NANOSECONDS_IN_A_SECOND responseLatencyReport(latencyInSeconds, track) return result } diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt index ae24ccd066..9e46ba913d 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/DefaultTimeProvider.kt @@ -13,7 +13,7 @@ package com.datadog.android.internal.time * Device timestamp and elapsed time are inherited from [BaseTimeProvider]. */ class DefaultTimeProvider : BaseTimeProvider() { - override fun getServerTimestamp(): Long = getDeviceTimestamp() + override fun getServerTimestampMillis(): Long = getDeviceTimestampMillis() override fun getServerOffsetNanos(): Long = 0L diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt index e734a33dd8..fd4ecf5822 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/time/TimeProvider.kt @@ -17,18 +17,18 @@ interface TimeProvider { * Returns the current device timestamp in milliseconds. * This is implemented in [BaseTimeProvider] as [System.currentTimeMillis]. */ - fun getDeviceTimestamp(): Long + fun getDeviceTimestampMillis(): Long /** * Returns the current server timestamp in milliseconds. */ - fun getServerTimestamp(): Long + fun getServerTimestampMillis(): Long /** * Returns the current device monotonic elapsed time in nanoseconds. * This is implemented in [BaseTimeProvider] as [System.nanoTime]. */ - fun getDeviceElapsedTimeNs(): Long + fun getDeviceElapsedTimeNanos(): Long /** * Returns the offset between the device and server time references in nanoseconds. @@ -45,6 +45,6 @@ interface TimeProvider { * A base implementation of [TimeProvider] that provides the device time using system calls. */ abstract class BaseTimeProvider : TimeProvider { - final override fun getDeviceTimestamp(): Long = System.currentTimeMillis() - final override fun getDeviceElapsedTimeNs(): Long = System.nanoTime() + final override fun getDeviceTimestampMillis(): Long = System.currentTimeMillis() + final override fun getDeviceElapsedTimeNanos(): Long = System.nanoTime() } diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt index 384eaf67f8..8be6bdca38 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/stub/StubTimeProvider.kt @@ -25,11 +25,11 @@ class StubTimeProvider( var serverOffsetMs: Long = 0L ) : TimeProvider { - override fun getDeviceTimestamp(): Long = deviceTimestampMs + override fun getDeviceTimestampMillis(): Long = deviceTimestampMs - override fun getServerTimestamp(): Long = serverTimestampMs + override fun getServerTimestampMillis(): Long = serverTimestampMs - override fun getDeviceElapsedTimeNs(): Long = elapsedTimeNs + override fun getDeviceElapsedTimeNanos(): Long = elapsedTimeNs override fun getServerOffsetNanos(): Long = serverOffsetNs diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt index 34a7f2ac11..1a12978e16 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessor.kt @@ -69,7 +69,7 @@ internal class ExposureEventsProcessor( } private fun buildExposureEvent(flagName: String, context: EvaluationContext, data: PrecomputedFlag): ExposureEvent { - val now = timeProvider.getDeviceTimestamp() + val now = timeProvider.getDeviceTimestampMillis() return ExposureEvent( timestamp = now, allocation = ExposureEvent.Identifier(data.allocationKey), diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt index f90a1b5b42..37b10031e3 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/repository/DefaultFlagsRepository.kt @@ -53,7 +53,7 @@ internal class DefaultFlagsRepository( persistenceManager.saveFlagsState( context = context, flags = flags, - currentTimestamp = featureSdkCore.timeProvider.getDeviceTimestamp(), + currentTimestamp = featureSdkCore.timeProvider.getDeviceTimestampMillis(), object : DataStoreWriteCallback { override fun onSuccess() { } diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt index c7eccdc153..534b0334fb 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/ExposureEventsProcessorTest.kt @@ -85,7 +85,7 @@ internal class ExposureEventsProcessorTest { ) // When - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp testedProcessor.processEvent(fakeFlagName, fakeContext, fakeFlag) // Then @@ -240,7 +240,7 @@ internal class ExposureEventsProcessorTest { val fakeTimestamp1 = forge.aPositiveLong() val fakeTimestamp2 = forge.aPositiveLong() - whenever(mockTimeProvider.getDeviceTimestamp()) + whenever(mockTimeProvider.getDeviceTimestampMillis()) .thenReturn(fakeTimestamp1) .thenReturn(fakeTimestamp2) diff --git a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt index 739fb6e3aa..b761456890 100644 --- a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt +++ b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandler.kt @@ -45,7 +45,7 @@ internal class DatadogLogHandler( return } - val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestamp() + val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestampMillis() val combinedAttributes = mutableMapOf() val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME) if (logsFeature != null) { @@ -112,7 +112,7 @@ internal class DatadogLogHandler( return } - val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestamp() + val resolvedTimeStamp = timestamp ?: sdkCore.timeProvider.getDeviceTimestampMillis() val combinedAttributes = mutableMapOf() val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME) if (logsFeature != null) { diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt index 5a85306907..2b0a995324 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/logger/DatadogLogHandlerTest.kt @@ -167,7 +167,7 @@ internal class DatadogLogHandlerTest { ) doReturn mockRumFeature whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp testedHandler = DatadogLogHandler( loggerName = fakeLoggerName, diff --git a/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt b/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt index 2e80d2e56b..2caab38d74 100644 --- a/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt +++ b/features/dd-sdk-android-ndk/src/main/kotlin/com/datadog/android/ndk/internal/NdkCrashReportsFeature.kt @@ -64,8 +64,8 @@ internal class NdkCrashReportsFeature( ) return } - val nowMs = sdkCore.timeProvider.getDeviceTimestamp() - val nowElapsedNs = sdkCore.timeProvider.getDeviceElapsedTimeNs() + val nowMs = sdkCore.timeProvider.getDeviceTimestampMillis() + val nowElapsedNs = sdkCore.timeProvider.getDeviceElapsedTimeNanos() val appStartTimestamp = TimeUnit.MILLISECONDS.toNanos(nowMs) - nowElapsedNs + sdkCore.appStartTimeNs registerSignalHandler( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt index 943bcb30b8..ea3e89fcc3 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporter.kt @@ -326,7 +326,7 @@ internal class DatadogLateCrashReporter( private val ViewEvent.isWithinSessionAvailability: Boolean get() { - val now = sdkCore.timeProvider.getDeviceTimestamp() + val now = sdkCore.timeProvider.getDeviceTimestampMillis() val sessionsTimeDifference = now - this.date return sessionsTimeDifference < VIEW_EVENT_AVAILABILITY_TIME_THRESHOLD } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt index 01659dfe12..e68e579294 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReader.kt @@ -99,7 +99,7 @@ internal class DefaultAccessibilityReader( @Synchronized override fun getState(): AccessibilityInfo { - val currentTime = timeProvider.getDeviceTimestamp() + val currentTime = timeProvider.getDeviceTimestampMillis() val shouldPoll = currentTime - lastPollTime.get() >= POLL_THRESHOLD if (shouldPoll) { lastPollTime.set(currentTime) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 8e04c474a1..d40e000ae0 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -65,7 +65,7 @@ internal class RumSessionScope( internal var sessionState: State = State.NOT_TRACKED private var startReason: StartReason = StartReason.USER_APP_LAUNCH internal var isActive: Boolean = true - private val sessionStartNs = AtomicLong(sdkCore.timeProvider.getDeviceElapsedTimeNs()) + private val sessionStartNs = AtomicLong(sdkCore.timeProvider.getDeviceElapsedTimeNanos()) private val lastUserInteractionNs = AtomicLong(0L) @@ -228,7 +228,7 @@ internal class RumSessionScope( @Suppress("ComplexMethod") private fun updateSession(event: RumRawEvent) { - val nanoTime = sdkCore.timeProvider.getDeviceElapsedTimeNs() + val nanoTime = sdkCore.timeProvider.getDeviceElapsedTimeNanos() val isNewSession = sessionId == RumContext.NULL_UUID val timeSinceLastInteractionNs = nanoTime - lastUserInteractionNs.get() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt index 6d4cb7b398..4c6ff14635 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/MainLooperLongTaskStrategy.kt @@ -78,7 +78,7 @@ internal class MainLooperLongTaskStrategy(internal val thresholdMs: Long) : Prin // region Internal private fun detectLongTask(message: String) { - val now = (sdkCore as FeatureSdkCore).timeProvider.getDeviceElapsedTimeNs() + val now = (sdkCore as FeatureSdkCore).timeProvider.getDeviceElapsedTimeNanos() if (message.startsWith(PREFIX_START)) { @Suppress("UnsafeThirdPartyFunctionCall") // substring can't throw IndexOutOfBounds target = message.substring(PREFIX_START_LENGTH) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt index 9c4e61adc0..99b6cc610c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt @@ -31,7 +31,7 @@ internal class DefaultSlowFramesListener( private var currentViewId: String? = null @Volatile - private var currentViewStartedTimestampNs: Long = timeProvider.getDeviceElapsedTimeNs() + private var currentViewStartedTimestampNs: Long = timeProvider.getDeviceElapsedTimeNanos() private val slowFramesRecords = ConcurrentHashMap() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt index 4728065b03..245c488544 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporter.kt @@ -23,7 +23,7 @@ internal interface RumFirstDrawTimeReporter { fun create(sdkCore: InternalSdkCore): RumFirstDrawTimeReporter { return RumFirstDrawTimeReporterImpl( internalLogger = sdkCore.internalLogger, - timeProviderNs = { sdkCore.timeProvider.getDeviceElapsedTimeNs() }, + timeProviderNs = { sdkCore.timeProvider.getDeviceElapsedTimeNanos() }, windowCallbacksRegistry = RumWindowCallbacksRegistryImpl(), handler = Handler(Looper.getMainLooper()) ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt index db323aa306..256b667af5 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/resource/RumResourceInputStream.kt @@ -50,7 +50,7 @@ constructor( init { val rumMonitor = GlobalRumMonitor.get(sdkCore) rumMonitor.startResource(key, METHOD, url) - callStart = timeProvider.getDeviceElapsedTimeNs() + callStart = timeProvider.getDeviceElapsedTimeNanos() if (rumMonitor is AdvancedRumMonitor) { rumMonitor.waitForResourceTiming(key) } @@ -60,36 +60,36 @@ constructor( /** @inheritdoc */ override fun read(): Int { - if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNanos() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read().also { if (it >= 0) size++ - lastByte = timeProvider.getDeviceElapsedTimeNs() + lastByte = timeProvider.getDeviceElapsedTimeNanos() } } } /** @inheritdoc */ override fun read(b: ByteArray): Int { - if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNanos() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read(b).also { if (it >= 0) size += it - lastByte = timeProvider.getDeviceElapsedTimeNs() + lastByte = timeProvider.getDeviceElapsedTimeNanos() } } } /** @inheritdoc */ override fun read(b: ByteArray, off: Int, len: Int): Int { - if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNs() + if (firstByte == 0L) firstByte = timeProvider.getDeviceElapsedTimeNanos() return callWithErrorTracking(ERROR_READ) { @Suppress("UnsafeThirdPartyFunctionCall") // caller should handle the exception read(b, off, len).also { if (it >= 0) size += it - lastByte = timeProvider.getDeviceElapsedTimeNs() + lastByte = timeProvider.getDeviceElapsedTimeNanos() } } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt index 8dabc72d50..fbf608b7b2 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/DatadogLateCrashReporterTest.kt @@ -110,7 +110,7 @@ internal class DatadogLateCrashReporterTest { @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeCurrentTimeMs + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeCurrentTimeMs whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt index 85e0466e9c..3d1e49e651 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/accessibility/DefaultAccessibilityReaderTest.kt @@ -98,7 +98,7 @@ internal class DefaultAccessibilityReaderTest { fun setup() { whenever(mockContext.contentResolver) doReturn mockContentResolver whenever(mockResources.configuration) doReturn mockConfiguration - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp setupDefaultMockBehavior() @@ -781,7 +781,7 @@ internal class DefaultAccessibilityReaderTest { // When - Call after threshold exceeded val newTimestamp = fakeTimestamp + 1000 - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn newTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn newTimestamp testedReader.getState() // Then - lastPollTime should be updated to the new timestamp diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt index f4c9ed16fb..0145f51836 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/resource/RumResourceInputStreamTest.kt @@ -672,7 +672,7 @@ internal class RumResourceInputStreamTest { val fakeFirstByteNs = fakeCallStartNs + fakeDownloadStartNs val fakeLastByteNs = fakeFirstByteNs + fakeDownloadDurationNs - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) .thenReturn(fakeCallStartNs) .thenReturn(fakeFirstByteNs) .thenReturn(fakeLastByteNs) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandler.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandler.kt index e34ab9287f..5d2e5d0b02 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandler.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandler.kt @@ -56,7 +56,7 @@ internal class RecordedDataQueueHandler( recordedQueuedItemContext = rumContextData, identifier = identifier, resourceData = resourceData, - creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs(), + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNanos(), mimeType = mimeType ) @@ -74,7 +74,7 @@ internal class RecordedDataQueueHandler( val item = TouchEventRecordedDataQueueItem( recordedQueuedItemContext = rumContextData, - creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs(), + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNanos(), touchData = pointerInteractions ) @@ -91,7 +91,7 @@ internal class RecordedDataQueueHandler( val item = SnapshotRecordedDataQueueItem( recordedQueuedItemContext = rumContextData, systemInformation = systemInformation, - creationTimestampInNs = timeProvider.getDeviceElapsedTimeNs() + creationTimestampInNs = timeProvider.getDeviceElapsedTimeNanos() ) insertIntoRecordedDataQueue(item) @@ -133,7 +133,7 @@ internal class RecordedDataQueueHandler( val nextItem = recordedDataQueue.peek() if (nextItem != null) { - val nextItemAgeInNs = timeProvider.getDeviceElapsedTimeNs() - nextItem.creationTimestampInNs + val nextItemAgeInNs = timeProvider.getDeviceElapsedTimeNanos() - nextItem.creationTimestampInNs if (!nextItem.isValid()) { if (sampler.sample(Unit)) { logInvalidQueueItemException(nextItem) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt index 0d6712dc4a..65fd6adef4 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessor.kt @@ -164,8 +164,8 @@ internal class RecordedDataProcessor( } private fun isTimeForFullSnapshot(): Boolean { - return if (timeProvider.getDeviceElapsedTimeNs() - lastSnapshotTimestamp >= FULL_SNAPSHOT_INTERVAL_IN_NS) { - lastSnapshotTimestamp = timeProvider.getDeviceElapsedTimeNs() + return if (timeProvider.getDeviceElapsedTimeNanos() - lastSnapshotTimestamp >= FULL_SNAPSHOT_INTERVAL_IN_NS) { + lastSnapshotTimestamp = timeProvider.getDeviceElapsedTimeNanos() true } else { false diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RumContextDataHandler.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RumContextDataHandler.kt index 85c578bcb9..89aeb08a2d 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RumContextDataHandler.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/RumContextDataHandler.kt @@ -20,7 +20,7 @@ internal class RumContextDataHandler( @MainThread internal fun createRumContextData(): RecordedQueuedItemContext? { - val timestamp = timeProvider.getDeviceTimestamp() + val timestamp = timeProvider.getDeviceTimestampMillis() val newRumContext = rumContextProvider.getRumContext() diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt index 8a069b99ef..90451a9536 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt @@ -29,11 +29,11 @@ internal class Debouncer( // reason why we are not initializing this in the constructor is that in case the // component was initialized earlier than the first debounce request was requested // it will execute the runnable directly and will not pass through the handler. - lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNs() + lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNanos() firstRequest = false } handler.removeCallbacksAndMessages(null) - val timePassedSinceLastExecution = sdkCore.timeProvider.getDeviceElapsedTimeNs() - lastTimeRecordWasPerformed + val timePassedSinceLastExecution = sdkCore.timeProvider.getDeviceElapsedTimeNanos() - lastTimeRecordWasPerformed if (timePassedSinceLastExecution >= maxRecordDelayInNs) { executeRunnable(runnable) } else { @@ -49,14 +49,14 @@ internal class Debouncer( } else { runnable.run() } - lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNs() + lastTimeRecordWasPerformed = sdkCore.timeProvider.getDeviceElapsedTimeNanos() } private fun runInTimeBalance(block: () -> Unit) { - if (timeBank.updateAndCheck(sdkCore.timeProvider.getDeviceElapsedTimeNs())) { - val startTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNs() + if (timeBank.updateAndCheck(sdkCore.timeProvider.getDeviceElapsedTimeNanos())) { + val startTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNanos() block() - val endTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNs() + val endTimeInNano = sdkCore.timeProvider.getDeviceElapsedTimeNanos() timeBank.consume(endTimeInNano - startTimeInNano) } else { logSkippedFrame() diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt index 56334fd555..2357393fe0 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt @@ -49,7 +49,7 @@ internal class RecorderWindowCallback( private val pixelsDensity = appContext.resources.displayMetrics.density internal val pointerInteractions: MutableList = LinkedList() private var lastOnMoveUpdateTimeInNs: Long = 0L - private var lastPerformedFlushTimeInNs: Long = timeProvider.getDeviceElapsedTimeNs() + private var lastPerformedFlushTimeInNs: Long = timeProvider.getDeviceElapsedTimeNanos() private var shouldRecordMotion: Boolean = false // region Window.Callback @@ -99,19 +99,19 @@ internal class RecorderWindowCallback( when (event.action.and(MotionEvent.ACTION_MASK)) { MotionEvent.ACTION_DOWN -> { // reset the flush time to avoid flush in the next event - lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNs() + lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNanos() updatePositions(event, MobileSegment.PointerEventType.DOWN) // reset the on move update time in order to take into account the first move event lastOnMoveUpdateTimeInNs = 0 } MotionEvent.ACTION_MOVE -> { - if (timeProvider.getDeviceElapsedTimeNs() - lastOnMoveUpdateTimeInNs >= motionUpdateThresholdInNs) { + if (timeProvider.getDeviceElapsedTimeNanos() - lastOnMoveUpdateTimeInNs >= motionUpdateThresholdInNs) { updatePositions(event, MobileSegment.PointerEventType.MOVE) - lastOnMoveUpdateTimeInNs = timeProvider.getDeviceElapsedTimeNs() + lastOnMoveUpdateTimeInNs = timeProvider.getDeviceElapsedTimeNanos() } // make sure we flush from time to time to avoid glitches in the player - if (timeProvider.getDeviceElapsedTimeNs() - lastPerformedFlushTimeInNs >= + if (timeProvider.getDeviceElapsedTimeNanos() - lastPerformedFlushTimeInNs >= flushPositionBufferThresholdInNs ) { flushPositions() @@ -133,7 +133,8 @@ internal class RecorderWindowCallback( val pointerAbsoluteY = motionEventUtils.getPointerAbsoluteY(event, pointerIndex) pointerInteractions.add( MobileSegment.MobileRecord.MobileIncrementalSnapshotRecord( - timestamp = timeProvider.getDeviceTimestamp() + rumContextProvider.getRumContext().viewTimeOffsetMs, + timestamp = timeProvider.getDeviceTimestampMillis() + + rumContextProvider.getRumContext().viewTimeOffsetMs, data = MobileSegment.MobileIncrementalData.PointerInteractionData( pointerEventType = eventType, pointerType = MobileSegment.PointerType.TOUCH, @@ -161,7 +162,7 @@ internal class RecorderWindowCallback( } pointerInteractions.clear() - lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNs() + lastPerformedFlushTimeInNs = timeProvider.getDeviceElapsedTimeNanos() } private fun logOrRethrowWrappedCallbackException(e: NullPointerException) { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt index d90d4b36c7..d25e72cf9e 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManager.kt @@ -27,7 +27,7 @@ internal class ResourceDataStoreManager( ) { @Suppress("UnsafeThirdPartyFunctionCall") // map is initialized empty private val knownResources = Collections.newSetFromMap(ConcurrentHashMap()) - private val storedLastUpdateDateNs = AtomicLong(featureSdkCore.timeProvider.getDeviceElapsedTimeNs()) + private val storedLastUpdateDateNs = AtomicLong(featureSdkCore.timeProvider.getDeviceElapsedTimeNanos()) private val isInitialized = AtomicBoolean(false) // has init finished executing its async actions init { @@ -129,7 +129,7 @@ internal class ResourceDataStoreManager( ) private fun didDataStoreExpire(lastUpdateDate: Long): Boolean = - featureSdkCore.timeProvider.getDeviceElapsedTimeNs() - lastUpdateDate > DATASTORE_EXPIRATION_NS + featureSdkCore.timeProvider.getDeviceElapsedTimeNanos() - lastUpdateDate > DATASTORE_EXPIRATION_NS // endregion diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandlerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandlerTest.kt index 43a3ee0eaf..50babe6443 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandlerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/async/RecordedDataQueueHandlerTest.kt @@ -135,7 +135,7 @@ internal class RecordedDataQueueHandlerTest { fakeNodeData = forge.aList { mock() } whenever(mockRateBasedSampler.sample(any())).thenReturn(true) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenReturn(fakeCurrentTimeNs) testedHandler = RecordedDataQueueHandler( processor = mockProcessor, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt index f9290d3c86..b7706b109b 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedDataProcessorTest.kt @@ -286,7 +286,7 @@ internal class RecordedDataProcessorTest { val fakeElapsedTimeAfterIntervalNs = fakeInitialElapsedTimeNs + RecordedDataProcessor.FULL_SNAPSHOT_INTERVAL_IN_NS - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn( + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenReturn( fakeInitialElapsedTimeNs, fakeInitialElapsedTimeNs, fakeElapsedTimeAfterIntervalNs, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedQueuedItemContextHandlerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedQueuedItemContextHandlerTest.kt index e427957069..723e807a59 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedQueuedItemContextHandlerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/processor/RecordedQueuedItemContextHandlerTest.kt @@ -65,7 +65,7 @@ internal class RecordedQueuedItemContextHandlerTest { fun `M return RumContextData W createRumContextData { valid rum context }`( @LongForgery(min = 1) fakeTime: Long ) { - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeTime) + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(fakeTime) // When val rumContextData = testedHandler.createRumContextData() diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt index bc53f0b125..08c93e0177 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt @@ -70,7 +70,7 @@ internal class DebouncerTest { whenever(mockTimeBank.updateAndCheck(any())).thenReturn(true) whenever(mockSdkCore.getFeature(RUM_FEATURE_NAME)).thenReturn(mockRumFeature) whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeInitialTimeNs + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeInitialTimeNs testedDebouncer = Debouncer( mockHandler, TEST_MAX_DELAY_THRESHOLD_IN_NS, @@ -98,7 +98,7 @@ internal class DebouncerTest { testedDebouncer.debounce(fakeRunnable) val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeExpiredTime testedDebouncer.debounce(fakeSecondRunnable) // Then @@ -122,7 +122,7 @@ internal class DebouncerTest { // When testedDebouncer.debounce(fakeRunnable) val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeExpiredTime testedDebouncer.debounce(fakeSecondRunnable) // Then @@ -153,7 +153,7 @@ internal class DebouncerTest { val fakeSecondRunnable = TestRunnable() testedDebouncer.debounce(fakeRunnable) val fakeExpiredTime = fakeInitialTimeNs + TEST_MAX_DELAY_THRESHOLD_IN_NS - whenever(mockTimeProvider.getDeviceElapsedTimeNs()) doReturn fakeExpiredTime + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()) doReturn fakeExpiredTime // When testedDebouncer.debounce(fakeSecondRunnable) @@ -179,7 +179,7 @@ internal class DebouncerTest { val delayInterval = (TEST_MAX_DELAY_THRESHOLD_IN_NS / fakeDelayedRunnables.size) - 1 val fakeExecutedRunnable = TestRunnable() var currentTime = fakeInitialTimeNs - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { currentTime } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { currentTime } fakeDelayedRunnables.forEach { testedDebouncer.debounce(it) @@ -215,7 +215,7 @@ internal class DebouncerTest { val delayInterval = (TEST_MAX_DELAY_THRESHOLD_IN_NS / fakeDelayedRunnablesPack1.size) - 1 val fakeExecutedRunnable = TestRunnable() var currentTime = fakeInitialTimeNs - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { currentTime } + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { currentTime } fakeDelayedRunnablesPack1.forEach { testedDebouncer.debounce(it) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt index 0745b4e77d..390d6e8230 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt @@ -127,8 +127,8 @@ internal class RecorderWindowCallbackTest { whenever(mockRecordedDataQueueHandler.addTouchEventItem(any())) .thenReturn(fakeTouchEventRecordedDataQueueItem) whenever(mockContext.resources).thenReturn(mockResources) - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeTimestamp) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenAnswer { fakeElapsedTimeNs } + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(fakeTimestamp) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenAnswer { fakeElapsedTimeNs } whenever(mockRumContextProvider.getRumContext()).thenReturn(fakeRumContext) whenever(mockTouchPrivacyManager.shouldRecordTouch(any())) .thenReturn(true) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt index 8db1ddf1f1..b827828497 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/resources/ResourceDataStoreManagerTest.kt @@ -83,7 +83,7 @@ internal class ResourceDataStoreManagerTest { whenever(mockFeatureScope.dataStore).thenReturn(mockDataStoreHandler) whenever(mockFeatureSdkCore.timeProvider).thenReturn(mockTimeProvider) - whenever(mockTimeProvider.getDeviceElapsedTimeNs()).thenReturn(fakeCurrentTimeNs) + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).thenReturn(fakeCurrentTimeNs) setRemoveDataSuccess() } diff --git a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt index 0938305e72..1409484261 100644 --- a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt +++ b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogSpanLogger.kt @@ -77,7 +77,7 @@ internal class DatadogSpanLogger( val logStatus = fields.remove(DatadogTracingConstants.LogAttributes.STATUS) ?: Log.VERBOSE fields[LogAttributes.DD_TRACE_ID] = span.context().traceId.toHexString() fields[LogAttributes.DD_SPAN_ID] = span.context().spanId.toString() - val timestamp = sdkCore.timeProvider.getDeviceTimestamp() + val timestamp = sdkCore.timeProvider.getDeviceTimestampMillis() logsFeature.sendEvent( buildMap { put("type", "span_log") diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt index 8d115d9681..222a11a538 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/DatadogSpanLoggerTest.kt @@ -78,7 +78,7 @@ class DatadogSpanLoggerTest { } whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider - whenever(mockTimeProvider.getDeviceTimestamp()) doReturn fakeTimestamp + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp testedLogger = DatadogSpanLogger(mockSdkCore) } diff --git a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt index f13296f81f..3b599260f5 100644 --- a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt +++ b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCache.kt @@ -89,7 +89,7 @@ internal class WebViewNativeRumViewsCache( private fun purgeHistory() { var cursor = parentViewsHistoryQueue.peekLast() while (cursor != null) { - val timeSinceLastSnapshot = timeProvider.getDeviceTimestamp() - cursor.timestamp + val timeSinceLastSnapshot = timeProvider.getDeviceTimestampMillis() - cursor.timestamp if (timeSinceLastSnapshot > entriesTtlLimitInMs) { parentViewsHistoryQueue.remove(cursor) cursor = parentViewsHistoryQueue.peekLast() diff --git a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt index a330fd37a4..fc8589cf45 100644 --- a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt +++ b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/internal/rum/domain/WebViewNativeRumViewsCacheTest.kt @@ -48,13 +48,13 @@ internal class WebViewNativeRumViewsCacheTest { @BeforeEach fun `set up`(forge: Forge) { fakeIdGenerator = FakeIdGenerator(forge) - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeCurrentTimeMs) + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(fakeCurrentTimeMs) testedCache = WebViewNativeRumViewsCache(mockTimeProvider) } private fun nextTimestamp(): Long { fakeCurrentTimeMs++ - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(fakeCurrentTimeMs) + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(fakeCurrentTimeMs) return fakeCurrentTimeMs } @@ -189,7 +189,7 @@ internal class WebViewNativeRumViewsCacheTest { WebViewNativeRumViewsCache.VIEW_HAS_REPLAY_KEY to forge.aBool() ) } - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(oldEntriesTimestamp) + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(oldEntriesTimestamp) fakeOldEntries.forEach { testedCache.addToCache(it) } // When @@ -215,7 +215,7 @@ internal class WebViewNativeRumViewsCacheTest { ) } // Simulate time passing: device time is beyond TTL for old entries - whenever(mockTimeProvider.getDeviceTimestamp()).thenReturn(newEntriesTimestamp) + whenever(mockTimeProvider.getDeviceTimestampMillis()).thenReturn(newEntriesTimestamp) fakeNewEntries.forEach { testedCache.addToCache(it) } // Then diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt index 1af093058d..6023c27acf 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubSDKCore.kt @@ -161,9 +161,9 @@ class StubSDKCore( override val internalLogger: InternalLogger = StubInternalLogger() override val timeProvider = object : TimeProvider { - override fun getDeviceTimestamp(): Long = 0L - override fun getServerTimestamp(): Long = 0L - override fun getDeviceElapsedTimeNs(): Long = 0L + override fun getDeviceTimestampMillis(): Long = 0L + override fun getServerTimestampMillis(): Long = 0L + override fun getDeviceElapsedTimeNanos(): Long = 0L override fun getServerOffsetNanos(): Long = 0L override fun getServerOffsetMillis(): Long = 0L } @@ -196,7 +196,7 @@ class StubSDKCore( } override fun createScheduledExecutorService(executorContext: String): ScheduledExecutorService { - return StubScheduledExecutorService(executorContext, timeProvider::getDeviceTimestamp) + return StubScheduledExecutorService(executorContext, timeProvider::getDeviceTimestampMillis) } override fun createSingleThreadExecutorService(executorContext: String): ExecutorService { diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt index 58efc6881e..360350757d 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/reader/FpsVitalReader.kt @@ -20,11 +20,11 @@ internal class FpsVitalReader(timeProvider: TimeProvider) : VitalReader { private val frameCallback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { if (lastFrameTime == 0L) { - lastFrameTime = timeProvider.getDeviceElapsedTimeNs() + lastFrameTime = timeProvider.getDeviceElapsedTimeNanos() } frameCount++ - val currentFrameTime = timeProvider.getDeviceElapsedTimeNs() + val currentFrameTime = timeProvider.getDeviceElapsedTimeNanos() val elapsedTime: Long = currentFrameTime - lastFrameTime if (elapsedTime >= TimeUnit.MILLISECONDS.toNanos(intervalMs)) {