From 795e402440e9e35e055944cf5181b74e4f367c83 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 Dec 2021 12:33:03 +0100 Subject: [PATCH 1/9] RUMM-1765 Add tests for expected App Launch x Background Events Tracking behaviours --- .../Scopes/RUMSessionScopeTests.swift | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift index 26a2b8d5ed..9c4d020233 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -10,8 +10,6 @@ import XCTest class RUMSessionScopeTests: XCTestCase { private let parent: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123") - // MARK: - Unit Tests - func testDefaultContext() { let scope: RUMSessionScope = .mockWith(parent: parent, samplingRate: 100) @@ -235,6 +233,74 @@ class RUMSessionScopeTests: XCTestCase { XCTAssertTrue(scope.viewScopes.isEmpty, "It should not start any view scope") } + // MARK: - Background Events x Application Launch Events Tracking + + func testGivenAppInForegroundAndBETEnabledAndInitialSession_whenCommandCanStartBothApplicationLaunchAndBackgroundViews_itCreatesAppLaunchScope() { + // Given + let currentTime = Date() + let scope: RUMSessionScope = .mockWith( + isInitialSession: true, // initial session + parent: parent, + samplingRate: 100, + startTime: currentTime, + backgroundEventTrackingEnabled: true // BET enabled + ) + XCTAssertTrue(scope.viewScopes.isEmpty, "No views tracked before") + + // When + let command = RUMCommandMock(time: currentTime, canStartBackgroundView: true, canStartApplicationLaunchView: true) + XCTAssertTrue(scope.process(command: command)) + + // Then + XCTAssertEqual(scope.viewScopes.count, 1, "It should start application launch view scope") + XCTAssertEqual(scope.viewScopes[0].viewStartTime, currentTime) + XCTAssertEqual(scope.viewScopes[0].viewName, RUMSessionScope.Constants.applicationLaunchViewName) + XCTAssertEqual(scope.viewScopes[0].viewPath, RUMSessionScope.Constants.applicationLaunchViewURL) + } + + func testGivenAppInBackgroundAndBETEnabledAndInitialSession_whenCommandCanStartBothApplicationLaunchAndBackgroundViews_itCreatesBackgroundScope() { + // Given + let currentTime = Date() + let scope: RUMSessionScope = .mockWith( + isInitialSession: true, // initial session + parent: parent, + samplingRate: 100, + startTime: currentTime, + backgroundEventTrackingEnabled: true // BET enabled + ) + XCTAssertTrue(scope.viewScopes.isEmpty, "No views tracked before") + + // When + let command = RUMCommandMock(time: currentTime, canStartBackgroundView: true, canStartApplicationLaunchView: true) + XCTAssertTrue(scope.process(command: command)) + + // Then + XCTAssertEqual(scope.viewScopes.count, 1, "It should start background view scope") + XCTAssertEqual(scope.viewScopes[0].viewStartTime, currentTime) + XCTAssertEqual(scope.viewScopes[0].viewName, RUMSessionScope.Constants.backgroundViewName) + XCTAssertEqual(scope.viewScopes[0].viewPath, RUMSessionScope.Constants.backgroundViewURL) + } + + func testGivenAppInBackgroundAndBETDisabledAndInitialSession_whenCommandCanStartBothApplicationLaunchAndBackgroundViews_itDoesNotCreateAnyScope() { + // Given + let currentTime = Date() + let scope: RUMSessionScope = .mockWith( + isInitialSession: true, // initial session + parent: parent, + samplingRate: 100, + startTime: currentTime, + backgroundEventTrackingEnabled: false // BET disabled + ) + XCTAssertTrue(scope.viewScopes.isEmpty, "No views tracked before") + + // When + let command = RUMCommandMock(time: currentTime, canStartBackgroundView: true, canStartApplicationLaunchView: true) + XCTAssertTrue(scope.process(command: command)) + + // Then + XCTAssertTrue(scope.viewScopes.isEmpty, "It should not start any view scope (event should be ignored)") + } + // MARK: - Sampling func testWhenSessionIsSampled_itDoesNotCreateViewScopes() { From b1609bb8cacd92c11574a9fb3aed160623d7817f Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 Dec 2021 12:43:39 +0100 Subject: [PATCH 2/9] RUMM-1765 Adjust naming in `AppStateHistory` to better fit the new use case --- .../Core/System/AppStateListener.swift | 31 ++++++++++++------- .../Interception/AppStateListenerTests.swift | 24 +++++++------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/Sources/Datadog/Core/System/AppStateListener.swift b/Sources/Datadog/Core/System/AppStateListener.swift index c1081b4610..850a53f31d 100644 --- a/Sources/Datadog/Core/System/AppStateListener.swift +++ b/Sources/Datadog/Core/System/AppStateListener.swift @@ -11,17 +11,23 @@ import class UIKit.UIApplication internal struct AppStateHistory: Equatable { /// Snapshot of the app state at `date` struct Snapshot: Equatable { + /// If the app is running in the foreground and currently receiving events. let isActive: Bool + /// Date of recording this snapshot. let date: Date } - var initialState: Snapshot - var changes = [Snapshot]() - var finalDate: Date - var finalState: Snapshot { + fileprivate(set) var initialState: Snapshot + fileprivate(set) var changes = [Snapshot]() + + /// Date of last the update to `AppStateHistory`. + fileprivate(set) var recentDate: Date + + /// The most recent app state `Snapshot`. + var currentState: Snapshot { return Snapshot( isActive: (changes.last ?? initialState).isActive, - date: finalDate + date: recentDate ) } @@ -37,7 +43,7 @@ internal struct AppStateHistory: Equatable { date: range.lowerBound ) // move final state to upperBound - taken.finalDate = range.upperBound + taken.recentDate = range.upperBound // filter changes outside of the range taken.changes = taken.changes.filter { range.contains($0.date) } return taken @@ -46,7 +52,7 @@ internal struct AppStateHistory: Equatable { var foregroundDuration: TimeInterval { var duration: TimeInterval = 0.0 var lastActiveStartDate: Date? - let allEvents = [initialState] + changes + [finalState] + let allEvents = [initialState] + changes + [currentState] for event in allEvents { if let startDate = lastActiveStartDate { duration += event.date.timeIntervalSince(startDate) @@ -61,16 +67,16 @@ internal struct AppStateHistory: Equatable { } var didRunInBackground: Bool { - return !initialState.isActive || !finalState.isActive + return !initialState.isActive || !currentState.isActive } private func isActive(at date: Date) -> Bool { if date <= initialState.date { // we assume there was no change before initial state return initialState.isActive - } else if finalState.date <= date { + } else if currentState.date <= date { // and no change after final state - return finalState.isActive + return currentState.isActive } var active = initialState for change in changes { @@ -83,6 +89,7 @@ internal struct AppStateHistory: Equatable { } } +/// Provides history of app foreground / background states. internal protocol AppStateListening: AnyObject { var history: AppStateHistory { get } } @@ -95,7 +102,7 @@ internal class AppStateListener: AppStateListening { var history: AppStateHistory { var current = publisher.currentValue - current.finalDate = dateProvider.currentDate() + current.recentDate = dateProvider.currentDate() return current } @@ -115,7 +122,7 @@ internal class AppStateListener: AppStateListening { self.publisher = ValuePublisher( initialValue: AppStateHistory( initialState: currentState, - finalDate: currentState.date + recentDate: currentState.date ) ) diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift index 7af74b7cbd..ab228f8e61 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift @@ -13,7 +13,7 @@ class AppStateHistoryTests: XCTestCase { let history = AppStateHistory( initialState: .init(isActive: true, date: startDate), changes: [], - finalDate: startDate + 1.0 + recentDate: startDate + 1.0 ) XCTAssertEqual(history.foregroundDuration, 1.0) @@ -29,7 +29,7 @@ class AppStateHistoryTests: XCTestCase { .init(isActive: false, date: startDate + 3.0), .init(isActive: true, date: startDate + 4.0) ], - finalDate: startDate + 5.0 + recentDate: startDate + 5.0 ) XCTAssertEqual(history.foregroundDuration, 3.0) @@ -43,7 +43,7 @@ class AppStateHistoryTests: XCTestCase { .init(isActive: false, date: startDate + 1.0), .init(isActive: false, date: startDate + 3.0) ], - finalDate: startDate + 5.0 + recentDate: startDate + 5.0 ) XCTAssertEqual(history.foregroundDuration, 1.0) @@ -54,7 +54,7 @@ class AppStateHistoryTests: XCTestCase { let history = AppStateHistory( initialState: .init(isActive: true, date: startDate), changes: [], - finalDate: startDate + 5.0 + recentDate: startDate + 5.0 ) let extrapolatedHistory = history.take( between: (startDate - 5.0)...(startDate + 15.0) @@ -63,7 +63,7 @@ class AppStateHistoryTests: XCTestCase { let expectedHistory = AppStateHistory( initialState: .init(isActive: true, date: startDate - 5.0), changes: [], - finalDate: startDate + 15.0 + recentDate: startDate + 15.0 ) XCTAssertEqual(extrapolatedHistory, expectedHistory) } @@ -73,7 +73,7 @@ class AppStateHistoryTests: XCTestCase { let history = AppStateHistory( initialState: .init(isActive: true, date: startDate), changes: [], - finalDate: startDate + 20.0 + recentDate: startDate + 20.0 ) let limitedHistory = history.take( between: (startDate + 5.0)...(startDate + 10.0) @@ -82,7 +82,7 @@ class AppStateHistoryTests: XCTestCase { let expectedHistory = AppStateHistory( initialState: .init(isActive: true, date: startDate + 5.0), changes: [], - finalDate: startDate + 10.0 + recentDate: startDate + 10.0 ) XCTAssertEqual(limitedHistory, expectedHistory) } @@ -108,7 +108,7 @@ class AppStateHistoryTests: XCTestCase { let history = AppStateHistory( initialState: .init(isActive: true, date: startDate), changes: allChanges, - finalDate: startDate + 4_000 + recentDate: startDate + 4_000 ) let limitedHistory = history.take( @@ -118,7 +118,7 @@ class AppStateHistoryTests: XCTestCase { let expectedHistory = AppStateHistory( initialState: .init(isActive: true, date: startDate + 1_250), changes: [.init(isActive: false, date: startDate + 1_500)], - finalDate: startDate + 1_750 + recentDate: startDate + 1_750 ) XCTAssertEqual(limitedHistory, expectedHistory) } @@ -143,7 +143,7 @@ class AppStateListenerTests: XCTestCase { .init(isActive: false, date: startDate + 1.0), .init(isActive: true, date: startDate + 2.0) ], - finalDate: startDate + 3.0 + recentDate: startDate + 3.0 ) XCTAssertEqual(listener.history, expected) } @@ -163,7 +163,7 @@ class AppStateListenerTests: XCTestCase { .init(isActive: true, date: startDate + 1.0), .init(isActive: false, date: startDate + 2.0) ], - finalDate: startDate + 3.0 + recentDate: startDate + 3.0 ) XCTAssertEqual(listener.history, expected) } @@ -177,7 +177,7 @@ class AppStateListenerTests: XCTestCase { let history1 = listener.history let history2 = listener.history - XCTAssertEqual(history2.finalState.date.timeIntervalSince(history1.finalState.date), 1.0) + XCTAssertEqual(history2.currentState.date.timeIntervalSince(history1.currentState.date), 1.0) } func testWhenAppStateListenerIsCalledFromDifferentThreads_thenItWorks() { From d3646f72858290ebe0ffc094162edd497cb336eb Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 Dec 2021 13:15:30 +0100 Subject: [PATCH 3/9] RUMM-1765 Inject `AppStateListener` to RUM scopes with ensuring that only one listener is created for the entire SDK. --- Sources/Datadog/Core/Feature.swift | 1 + Sources/Datadog/Datadog.swift | 6 ++-- Sources/Datadog/RUM/RUMFeature.swift | 2 ++ .../Scopes/RUMApplicationScope.swift | 1 + Sources/Datadog/RUMMonitor.swift | 1 + .../URLSessionAutoInstrumentation.swift | 7 ++-- .../Datadog/Mocks/CoreMocks.swift | 35 +++++++++++++------ .../Datadog/Mocks/RUMFeatureMocks.swift | 4 +++ .../URLSessionTracingHandlerTests.swift | 16 ++++----- .../URLSessionInterceptorTests.swift | 6 ++-- .../URLSessionAutoInstrumentationTests.swift | 8 ++--- 11 files changed, 53 insertions(+), 34 deletions(-) diff --git a/Sources/Datadog/Core/Feature.swift b/Sources/Datadog/Core/Feature.swift index 577158aae0..3b68ddbb13 100644 --- a/Sources/Datadog/Core/Feature.swift +++ b/Sources/Datadog/Core/Feature.swift @@ -29,6 +29,7 @@ internal struct FeaturesCommonDependencies { let networkConnectionInfoProvider: NetworkConnectionInfoProviderType let carrierInfoProvider: CarrierInfoProviderType let launchTimeProvider: LaunchTimeProviderType + let appStateListener: AppStateListening } internal struct FeatureStorage { diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 0fe6d96e6d..ee3a70d1e8 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -217,7 +217,8 @@ public class Datadog { userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider, - launchTimeProvider: launchTimeProvider + launchTimeProvider: launchTimeProvider, + appStateListener: AppStateListener(dateProvider: dateProvider) ) if let internalMonitoringConfiguration = configuration.internalMonitoring { @@ -273,8 +274,7 @@ public class Datadog { if let urlSessionAutoInstrumentationConfiguration = configuration.urlSessionAutoInstrumentation { urlSessionAutoInstrumentation = URLSessionAutoInstrumentation( configuration: urlSessionAutoInstrumentationConfiguration, - dateProvider: dateProvider, - appStateListener: AppStateListener(dateProvider: dateProvider) + commonDependencies: commonDependencies ) } diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index ef21445f43..649c12eb74 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -32,6 +32,7 @@ internal final class RUMFeature { let dateProvider: DateProvider let dateCorrector: DateCorrectorType + let appStateListener: AppStateListening let userInfoProvider: UserInfoProvider let networkConnectionInfoProvider: NetworkConnectionInfoProviderType let carrierInfoProvider: CarrierInfoProviderType @@ -169,6 +170,7 @@ internal final class RUMFeature { // Bundle dependencies self.dateProvider = commonDependencies.dateProvider self.dateCorrector = commonDependencies.dateCorrector + self.appStateListener = commonDependencies.appStateListener self.userInfoProvider = commonDependencies.userInfoProvider self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider self.carrierInfoProvider = commonDependencies.carrierInfoProvider diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift index 5cc0610a86..bfa60e80c0 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -10,6 +10,7 @@ internal typealias RUMSessionListener = (String, Bool) -> Void /// Injection container for common dependencies used by all `RUMScopes`. internal struct RUMScopeDependencies { + let appStateListener: AppStateListening let userInfoProvider: RUMUserInfoProvider let launchTimeProvider: LaunchTimeProviderType let connectivityInfoProvider: RUMConnectivityInfoProvider diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index 13510f9cae..4a083505d5 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -178,6 +178,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { applicationScope: RUMApplicationScope( rumApplicationID: rumFeature.configuration.applicationID, dependencies: RUMScopeDependencies( + appStateListener: rumFeature.appStateListener, userInfoProvider: RUMUserInfoProvider(userInfoProvider: rumFeature.userInfoProvider), launchTimeProvider: rumFeature.launchTimeProvider, connectivityInfoProvider: RUMConnectivityInfoProvider( diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentation.swift b/Sources/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentation.swift index 6d2a7ff661..ba06f212ee 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentation.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentation.swift @@ -15,16 +15,15 @@ internal final class URLSessionAutoInstrumentation: RUMCommandPublisher { convenience init?( configuration: FeaturesConfiguration.URLSessionAutoInstrumentation, - dateProvider: DateProvider, - appStateListener: AppStateListening + commonDependencies: FeaturesCommonDependencies ) { do { self.init( swizzler: try URLSessionSwizzler(), interceptor: URLSessionInterceptor( configuration: configuration, - dateProvider: dateProvider, - appStateListener: appStateListener + dateProvider: commonDependencies.dateProvider, + appStateListener: commonDependencies.appStateListener ) ) } catch { diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 5d278c2f88..c05c4336cb 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -468,7 +468,8 @@ extension FeaturesCommonDependencies { ) ), carrierInfoProvider: CarrierInfoProviderType = CarrierInfoProviderMock.mockAny(), - launchTimeProvider: LaunchTimeProviderType = LaunchTimeProviderMock() + launchTimeProvider: LaunchTimeProviderType = LaunchTimeProviderMock(), + appStateListener: AppStateListening = AppStateListenerMock.mockAny() ) -> FeaturesCommonDependencies { let httpClient: HTTPClient @@ -501,7 +502,8 @@ extension FeaturesCommonDependencies { userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider, - launchTimeProvider: launchTimeProvider + launchTimeProvider: launchTimeProvider, + appStateListener: appStateListener ) } @@ -516,7 +518,8 @@ extension FeaturesCommonDependencies { userInfoProvider: UserInfoProvider? = nil, networkConnectionInfoProvider: NetworkConnectionInfoProviderType? = nil, carrierInfoProvider: CarrierInfoProviderType? = nil, - launchTimeProvider: LaunchTimeProviderType? = nil + launchTimeProvider: LaunchTimeProviderType? = nil, + appStateListener: AppStateListening? = nil ) -> FeaturesCommonDependencies { return FeaturesCommonDependencies( consentProvider: consentProvider ?? self.consentProvider, @@ -528,7 +531,8 @@ extension FeaturesCommonDependencies { userInfoProvider: userInfoProvider ?? self.userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider ?? self.networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider ?? self.carrierInfoProvider, - launchTimeProvider: launchTimeProvider ?? self.launchTimeProvider + launchTimeProvider: launchTimeProvider ?? self.launchTimeProvider, + appStateListener: appStateListener ?? self.appStateListener ) } } @@ -655,6 +659,23 @@ struct LaunchTimeProviderMock: LaunchTimeProviderType { var launchTime: TimeInterval = 0 } +class AppStateListenerMock: AppStateListening, AnyMockable { + let history: AppStateHistory + + required init(history: AppStateHistory) { + self.history = history + } + + static func mockAny() -> Self { + return .init( + history: .init( + initialState: .init(isActive: true, date: .mockDecember15th2019At10AMUTC()), + recentDate: .mockDecember15th2019At10AMUTC() + ) + ) + } +} + extension UserInfo: AnyMockable, RandomMockable { static func mockAny() -> UserInfo { return mockEmpty() @@ -991,12 +1012,6 @@ class CarrierInfoProviderMock: CarrierInfoProviderType { } } -extension AppStateListener { - static func mockAny() -> AppStateListener { - return AppStateListener(dateProvider: SystemDateProvider()) - } -} - extension CodableValue { static func mockAny() -> CodableValue { return CodableValue(String.mockAny()) diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index b857c0e874..a0ddf3b6d9 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -596,6 +596,7 @@ extension RUMScopeDependencies { } static func mockWith( + appStateListener: AppStateListening = AppStateListenerMock.mockAny(), userInfoProvider: RUMUserInfoProvider = RUMUserInfoProvider(userInfoProvider: .mockAny()), launchTimeProvider: LaunchTimeProviderType = LaunchTimeProviderMock(), connectivityInfoProvider: RUMConnectivityInfoProvider = RUMConnectivityInfoProvider( @@ -609,6 +610,7 @@ extension RUMScopeDependencies { onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner() ) -> RUMScopeDependencies { return RUMScopeDependencies( + appStateListener: appStateListener, userInfoProvider: userInfoProvider, launchTimeProvider: launchTimeProvider, connectivityInfoProvider: connectivityInfoProvider, @@ -625,6 +627,7 @@ extension RUMScopeDependencies { /// Creates new instance of `RUMScopeDependencies` by replacing individual dependencies. func replacing( + appStateListener: AppStateListening? = nil, userInfoProvider: RUMUserInfoProvider? = nil, launchTimeProvider: LaunchTimeProviderType? = nil, connectivityInfoProvider: RUMConnectivityInfoProvider? = nil, @@ -635,6 +638,7 @@ extension RUMScopeDependencies { onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner() ) -> RUMScopeDependencies { return RUMScopeDependencies( + appStateListener: appStateListener ?? self.appStateListener, userInfoProvider: userInfoProvider ?? self.userInfoProvider, launchTimeProvider: launchTimeProvider ?? self.launchTimeProvider, connectivityInfoProvider: connectivityInfoProvider ?? self.connectivityInfoProvider, diff --git a/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift index a6a8ff6b94..2c4242ed67 100644 --- a/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift +++ b/Tests/DatadogTests/Datadog/Tracing/Autoinstrumentation/URLSessionTracingHandlerTests.swift @@ -7,17 +7,17 @@ import XCTest @testable import Datadog -private class MockAppStateListener: AppStateListening { - let history = AppStateHistory( - initialState: .init(isActive: true, date: .mockDecember15th2019At10AMUTC()), - finalDate: .mockDecember15th2019At10AMUTC() + 10 - ) -} - class URLSessionTracingHandlerTests: XCTestCase { private let spanOutput = SpanOutputMock() private let logOutput = LogOutputMock() - private let handler = URLSessionTracingHandler(appStateListener: MockAppStateListener()) + private let handler = URLSessionTracingHandler( + appStateListener: AppStateListenerMock( + history: .init( + initialState: .init(isActive: true, date: .mockDecember15th2019At10AMUTC()), + recentDate: .mockDecember15th2019At10AMUTC() + 10 + ) + ) + ) override func setUp() { Global.sharedTracer = Tracer.mockWith( diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift index 358847550c..94db9ad04b 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/URLSessionInterceptorTests.swift @@ -26,7 +26,7 @@ class URLSessionInterceptorTests: XCTestCase { let instrumentRUM = false // When - let appStateListener = AppStateListener.mockAny() + let appStateListener = AppStateListenerMock.mockAny() let interceptor = URLSessionInterceptor( configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM), dateProvider: SystemDateProvider(), @@ -55,7 +55,7 @@ class URLSessionInterceptorTests: XCTestCase { let interceptor = URLSessionInterceptor( configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM), dateProvider: SystemDateProvider(), - appStateListener: AppStateListener.mockAny() + appStateListener: AppStateListenerMock.mockAny() ) // Then @@ -79,7 +79,7 @@ class URLSessionInterceptorTests: XCTestCase { let interceptor = URLSessionInterceptor( configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM), dateProvider: SystemDateProvider(), - appStateListener: AppStateListener.mockAny() + appStateListener: AppStateListenerMock.mockAny() ) // Then diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentationTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentationTests.swift index d9834416b3..33ff6038e7 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentationTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/URLSessionAutoInstrumentationTests.swift @@ -24,11 +24,9 @@ class URLSessionAutoInstrumentationTests: XCTestCase { // When URLSessionAutoInstrumentation.instance = URLSessionAutoInstrumentation( configuration: .mockAny(), - dateProvider: SystemDateProvider(), - appStateListener: AppStateListener.mockAny() + commonDependencies: .mockAny() ) defer { - URLSessionAutoInstrumentation.instance?.swizzler.unswizzle() URLSessionAutoInstrumentation.instance?.deinitialize() } @@ -43,11 +41,9 @@ class URLSessionAutoInstrumentationTests: XCTestCase { URLSessionAutoInstrumentation.instance = URLSessionAutoInstrumentation( configuration: .mockAny(), - dateProvider: SystemDateProvider(), - appStateListener: AppStateListener.mockAny() + commonDependencies: .mockAny() ) defer { - URLSessionAutoInstrumentation.instance?.swizzler.unswizzle() URLSessionAutoInstrumentation.instance?.deinitialize() } From 4c9338b2391ca643cb278b31eeaca8476be45f97 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 Dec 2021 13:26:03 +0100 Subject: [PATCH 4/9] RUMM-1765 Make App Launch x Background Events Tracking tests green by ensuring that app starts "ApplicationLaunch" view only if launched in foreground and it starts "Background" view when launched in background with "Background Events Tracking" option enabled. --- .../RUM/RUMMonitor/Scopes/RUMSessionScope.swift | 11 ++++++++--- .../RUMMonitor/Scopes/RUMSessionScopeTests.swift | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift index 299a44c742..27803a4ce1 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift @@ -129,9 +129,14 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Consider starting an active view, "ApplicationLaunch" view or "Background" view if let startViewCommand = command as? RUMStartViewCommand { startView(on: startViewCommand) - } else if isInitialSession && !hasTrackedAnyView && command.canStartApplicationLaunchView { - startApplicationLaunchView(on: command) - } else if backgroundEventTrackingEnabled && !hasActiveView && command.canStartBackgroundView { + } else if isInitialSession && !hasTrackedAnyView { // if initial session with no views history + let appInForeground = dependencies.appStateListener.history.currentState.isActive + if appInForeground && command.canStartApplicationLaunchView { // when app is in foreground, start "ApplicationLaunch" view + startApplicationLaunchView(on: command) + } else if backgroundEventTrackingEnabled && command.canStartBackgroundView { // when app is in background and BET is enabled, start "Background" view + startBackgroundView(on: command) + } + } else if backgroundEventTrackingEnabled && !hasActiveView && command.canStartBackgroundView { // if existing session with views history and BET is enabled startBackgroundView(on: command) } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift index 9c4d020233..8789d63f4e 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -241,6 +241,11 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( isInitialSession: true, // initial session parent: parent, + dependencies: .mockWith( + appStateListener: AppStateListenerMock( + history: .init(initialState: .init(isActive: true, date: currentTime), recentDate: currentTime) // app in foreground + ) + ), samplingRate: 100, startTime: currentTime, backgroundEventTrackingEnabled: true // BET enabled @@ -264,6 +269,11 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( isInitialSession: true, // initial session parent: parent, + dependencies: .mockWith( + appStateListener: AppStateListenerMock( + history: .init(initialState: .init(isActive: false, date: currentTime), recentDate: currentTime) // app in background + ) + ), samplingRate: 100, startTime: currentTime, backgroundEventTrackingEnabled: true // BET enabled @@ -287,6 +297,11 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( isInitialSession: true, // initial session parent: parent, + dependencies: .mockWith( + appStateListener: AppStateListenerMock( + history: .init(initialState: .init(isActive: false, date: currentTime), recentDate: currentTime) // app in background + ) + ), samplingRate: 100, startTime: currentTime, backgroundEventTrackingEnabled: false // BET disabled From 591708aad3fac8bc6b3dd3255296bb82f6f10de6 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 10 Dec 2021 13:53:47 +0100 Subject: [PATCH 5/9] RUMM-1765 Fix `Benchmarks` build --- Tests/DatadogBenchmarkTests/BenchmarkMocks.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift index c594c007eb..3a09fa1a80 100644 --- a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift +++ b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift @@ -28,7 +28,8 @@ extension FeaturesCommonDependencies { userInfoProvider: UserInfoProvider(), networkConnectionInfoProvider: NetworkConnectionInfoProvider(), carrierInfoProvider: CarrierInfoProvider(), - launchTimeProvider: LaunchTimeProvider() + launchTimeProvider: LaunchTimeProvider(), + appStateListener: AppStateListener(dateProvider: SystemDateProvider()) ) } } From a92066eec289b892071ba29ecf9a7428ba693042 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 13 Dec 2021 11:39:29 +0100 Subject: [PATCH 6/9] RUMM-1765 Fix a bug with custom actions not being sent if tracked as a very first event in the view --- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 9 ++- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index b1f1b0dfec..155c9dfc5f 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -171,9 +171,14 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { } case let command as RUMAddUserActionCommand where isActiveView: if userActionScope == nil { - addDiscreteUserAction(on: command) + if command.actionType == .custom { + // send it instantly without waiting for child events (e.g. resource associated to this action) + sendDiscreteCustomUserAction(on: command) + } else { + addDiscreteUserAction(on: command) + } } else if command.actionType == .custom { - // still let it go, just instantly without any dependencies + // still let it go, just instantly without waiting for child events (e.g. resource associated to this action) sendDiscreteCustomUserAction(on: command) } else { reportActionDropped(type: command.actionType, name: command.name) diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index e6fb5cadb7..b1feb8de7d 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -548,6 +548,74 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.model.view.action.count, 2, "View should record 2 actions: non-custom + instant custom") } + func testGivenViewWithPendingAction_whenCustomActionIsAdded_itSendsItInstantly() throws { + var currentTime = Date() + let scope = RUMViewScope( + isInitialView: false, + parent: parent, + dependencies: dependencies, + identity: mockView, + path: .mockAny(), + name: .mockAny(), + attributes: [:], + customTimings: [:], + startTime: currentTime + ) + _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) + + // Given + currentTime.addTimeInterval(0.5) + + let pendingActionName: String = .mockRandom() + _ = scope.process(command: RUMAddUserActionCommand.mockWith(time: currentTime, actionType: .tap, name: pendingActionName)) + XCTAssertEqual(scope.userActionScope?.name, pendingActionName) + + // When + let customActionName: String = .mockRandom() + _ = scope.process(command: RUMAddUserActionCommand.mockWith(time: currentTime, actionType: .custom, name: customActionName)) + + // Then + XCTAssertEqual(scope.userActionScope?.name, pendingActionName, "It should not alter pending action") + + let lastViewEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) + let firstActionEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).first) + XCTAssertEqual(lastViewEvent.model.view.action.count, 1, "View should record 1 only custom action (pending action is not yet finished)") + XCTAssertEqual(firstActionEvent.model.action.target?.name, customActionName) + } + + func testGivenViewWithNoPendingAction_whenCustomActionIsAdded_itSendsItInstantly() throws { + var currentTime = Date() + let scope = RUMViewScope( + isInitialView: false, + parent: parent, + dependencies: dependencies, + identity: mockView, + path: .mockAny(), + name: .mockAny(), + attributes: [:], + customTimings: [:], + startTime: currentTime + ) + _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) + + // Given + currentTime.addTimeInterval(0.5) + + XCTAssertNil(scope.userActionScope) + + // When + let customActionName: String = .mockRandom() + _ = scope.process(command: RUMAddUserActionCommand.mockWith(time: currentTime, actionType: .custom, name: customActionName)) + + // Then + XCTAssertNil(scope.userActionScope, "It should not count custom action as pending") + + let lastViewEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) + let firstActionEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).first) + XCTAssertEqual(lastViewEvent.model.view.action.count, 1, "View should record custom action") + XCTAssertEqual(firstActionEvent.model.action.target?.name, customActionName) + } + // MARK: - Error Tracking func testWhenViewErrorIsAdded_itSendsErrorEventAndViewUpdateEvent() throws { From 44a15a8c5ba6b67acd29bc0f006aebd82ef6da91 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 13 Dec 2021 13:18:23 +0100 Subject: [PATCH 7/9] RUMM-1765 Inject SDK init time to RUM application scope --- Sources/Datadog/Core/Feature.swift | 2 ++ Sources/Datadog/Datadog.swift | 1 + Sources/Datadog/RUM/RUMFeature.swift | 2 ++ .../Scopes/RUMApplicationScope.swift | 5 +++ Sources/Datadog/RUMMonitor.swift | 1 + .../Datadog/Mocks/CoreMocks.swift | 4 +++ .../Datadog/Mocks/RUMFeatureMocks.swift | 4 ++- .../RUM/Debugging/RUMDebuggingTests.swift | 14 ++------ .../RUMContext/RUMCurrentContextTests.swift | 12 +++---- .../Scopes/RUMApplicationScopeTests.swift | 35 ++++++++++++++----- 10 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Sources/Datadog/Core/Feature.swift b/Sources/Datadog/Core/Feature.swift index 3b68ddbb13..6ea0194a3d 100644 --- a/Sources/Datadog/Core/Feature.swift +++ b/Sources/Datadog/Core/Feature.swift @@ -23,6 +23,8 @@ internal struct FeaturesCommonDependencies { let performance: PerformancePreset let httpClient: HTTPClient let mobileDevice: MobileDevice + /// Time of SDK initialization, measured in device date. + let sdkInitDate: Date let dateProvider: DateProvider let dateCorrector: DateCorrectorType let userInfoProvider: UserInfoProvider diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index ee3a70d1e8..0c32a83d37 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -212,6 +212,7 @@ public class Datadog { performance: configuration.common.performance, httpClient: HTTPClient(proxyConfiguration: configuration.common.proxyConfiguration), mobileDevice: MobileDevice(), + sdkInitDate: dateProvider.currentDate(), dateProvider: dateProvider, dateCorrector: dateCorrector, userInfoProvider: userInfoProvider, diff --git a/Sources/Datadog/RUM/RUMFeature.swift b/Sources/Datadog/RUM/RUMFeature.swift index 649c12eb74..e802d4562a 100644 --- a/Sources/Datadog/RUM/RUMFeature.swift +++ b/Sources/Datadog/RUM/RUMFeature.swift @@ -30,6 +30,7 @@ internal final class RUMFeature { // MARK: - Dependencies + let sdkInitDate: Date let dateProvider: DateProvider let dateCorrector: DateCorrectorType let appStateListener: AppStateListening @@ -168,6 +169,7 @@ internal final class RUMFeature { self.configuration = configuration // Bundle dependencies + self.sdkInitDate = commonDependencies.sdkInitDate self.dateProvider = commonDependencies.dateProvider self.dateCorrector = commonDependencies.dateCorrector self.appStateListener = commonDependencies.appStateListener diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift index bfa60e80c0..b2e13f4582 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -37,6 +37,9 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { /// RUM Sessions sampling rate. internal let samplingRate: Float + /// Time of SDK initialization, measured in device date. + internal let sdkInitDate: Date + /// Automatically detect background events internal let backgroundEventTrackingEnabled: Bool @@ -48,10 +51,12 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { rumApplicationID: String, dependencies: RUMScopeDependencies, samplingRate: Float, + sdkInitDate: Date, backgroundEventTrackingEnabled: Bool ) { self.dependencies = dependencies self.samplingRate = samplingRate + self.sdkInitDate = sdkInitDate self.backgroundEventTrackingEnabled = backgroundEventTrackingEnabled self.context = RUMContext( rumApplicationID: rumApplicationID, diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index 4a083505d5..8e581f2169 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -199,6 +199,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { onSessionStart: rumFeature.onSessionStart ), samplingRate: rumFeature.configuration.sessionSamplingRate, + sdkInitDate: rumFeature.sdkInitDate, backgroundEventTrackingEnabled: rumFeature.configuration.backgroundEventTrackingEnabled ), dateProvider: rumFeature.dateProvider diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index c05c4336cb..29b7cf4d85 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -454,6 +454,7 @@ extension FeaturesCommonDependencies { return BatteryStatus(state: .full, level: 1, isLowPowerModeEnabled: false) } ), + sdkInitDate: Date = Date(), dateProvider: DateProvider = SystemDateProvider(), dateCorrector: DateCorrectorType = DateCorrectorMock(), userInfoProvider: UserInfoProvider = .mockAny(), @@ -497,6 +498,7 @@ extension FeaturesCommonDependencies { performance: performance, httpClient: httpClient, mobileDevice: mobileDevice, + sdkInitDate: sdkInitDate, dateProvider: dateProvider, dateCorrector: dateCorrector, userInfoProvider: userInfoProvider, @@ -513,6 +515,7 @@ extension FeaturesCommonDependencies { performance: PerformancePreset? = nil, httpClient: HTTPClient? = nil, mobileDevice: MobileDevice? = nil, + sdkInitDate: Date? = nil, dateProvider: DateProvider? = nil, dateCorrector: DateCorrectorType? = nil, userInfoProvider: UserInfoProvider? = nil, @@ -526,6 +529,7 @@ extension FeaturesCommonDependencies { performance: performance ?? self.performance, httpClient: httpClient ?? self.httpClient, mobileDevice: mobileDevice ?? self.mobileDevice, + sdkInitDate: sdkInitDate ?? self.sdkInitDate, dateProvider: dateProvider ?? self.dateProvider, dateCorrector: dateCorrector ?? self.dateCorrector, userInfoProvider: userInfoProvider ?? self.userInfoProvider, diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index a0ddf3b6d9..9eb9c2970e 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -662,13 +662,15 @@ extension RUMApplicationScope { static func mockWith( rumApplicationID: String = .mockAny(), dependencies: RUMScopeDependencies = .mockAny(), - samplingRate: Float = 100, + samplingRate: Float = .mockAny(), + sdkInitDate: Date = .mockAny(), backgroundEventTrackingEnabled: Bool = .mockAny() ) -> RUMApplicationScope { return RUMApplicationScope( rumApplicationID: rumApplicationID, dependencies: dependencies, samplingRate: samplingRate, + sdkInitDate: sdkInitDate, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled ) } diff --git a/Tests/DatadogTests/Datadog/RUM/Debugging/RUMDebuggingTests.swift b/Tests/DatadogTests/Datadog/RUM/Debugging/RUMDebuggingTests.swift index 9fcd133eb8..149a7870e8 100644 --- a/Tests/DatadogTests/Datadog/RUM/Debugging/RUMDebuggingTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/Debugging/RUMDebuggingTests.swift @@ -13,12 +13,7 @@ class RUMDebuggingTests: XCTestCase { let expectation = self.expectation(description: "Render RUMDebugging") // when - let applicationScope = RUMApplicationScope( - rumApplicationID: "abc-123", - dependencies: .mockAny(), - samplingRate: 100, - backgroundEventTrackingEnabled: .mockAny() - ) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) _ = applicationScope.process( command: RUMStartViewCommand.mockWith(identity: mockView, name: "FirstView") ) @@ -45,12 +40,7 @@ class RUMDebuggingTests: XCTestCase { let expectation = self.expectation(description: "Render RUMDebugging") // when - let applicationScope = RUMApplicationScope( - rumApplicationID: "abc-123", - dependencies: .mockAny(), - samplingRate: 100, - backgroundEventTrackingEnabled: .mockAny() - ) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) _ = applicationScope.process( command: RUMStartViewCommand.mockWith(identity: mockView, name: "FirstView") ) diff --git a/Tests/DatadogTests/Datadog/RUM/RUMContext/RUMCurrentContextTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMContext/RUMCurrentContextTests.swift index e1da601b29..4050df60ac 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMContext/RUMCurrentContextTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMContext/RUMCurrentContextTests.swift @@ -13,7 +13,7 @@ class RUMCurrentContextTests: XCTestCase { private let queue = DispatchQueue(label: "\(#file)") func testContextAfterInitializingTheApplication() { - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: .mockAny(), backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123") let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) XCTAssertEqual( @@ -30,7 +30,7 @@ class RUMCurrentContextTests: XCTestCase { } func testContextAfterStartingView() throws { - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) _ = applicationScope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) @@ -49,7 +49,7 @@ class RUMCurrentContextTests: XCTestCase { } func testContextWhilePendingUserAction() throws { - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) _ = applicationScope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) @@ -69,7 +69,7 @@ class RUMCurrentContextTests: XCTestCase { } func testContextChangeWhenNavigatingBetweenViews() throws { - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) let firstView = createMockViewInWindow() @@ -97,7 +97,7 @@ class RUMCurrentContextTests: XCTestCase { func testContextChangeWhenSessionIsRenewed() throws { var currentTime = Date() - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 100) let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) let view = createMockViewInWindow() @@ -140,7 +140,7 @@ class RUMCurrentContextTests: XCTestCase { } func testContextWhenSessionIsSampled() throws { - let applicationScope = RUMApplicationScope(rumApplicationID: "rum-123", dependencies: .mockAny(), samplingRate: 0, backgroundEventTrackingEnabled: .mockAny()) + let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123", samplingRate: 0) let provider = RUMCurrentContext(applicationScope: applicationScope, queue: queue) _ = applicationScope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index e580766d2f..90449f5e48 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -13,6 +13,7 @@ class RUMApplicationScopeTests: XCTestCase { rumApplicationID: "abc-123", dependencies: .mockAny(), samplingRate: .mockAny(), + sdkInitDate: .mockAny(), backgroundEventTrackingEnabled: .mockAny() ) @@ -33,10 +34,9 @@ class RUMApplicationScopeTests: XCTestCase { let scope = RUMApplicationScope( rumApplicationID: .mockAny(), - dependencies: .mockWith( - onSessionStart: onSessionStart - ), + dependencies: .mockWith(onSessionStart: onSessionStart), samplingRate: 0, + sdkInitDate: .mockAny(), backgroundEventTrackingEnabled: .mockRandom() ) @@ -62,10 +62,9 @@ class RUMApplicationScopeTests: XCTestCase { // Given let scope = RUMApplicationScope( rumApplicationID: .mockAny(), - dependencies: .mockWith( - onSessionStart: onSessionStart - ), + dependencies: .mockWith(onSessionStart: onSessionStart), samplingRate: 100, + sdkInitDate: .mockAny(), backgroundEventTrackingEnabled: .mockAny() ) @@ -101,7 +100,13 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) - let scope = RUMApplicationScope(rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 100, backgroundEventTrackingEnabled: .mockAny()) + let scope = RUMApplicationScope( + rumApplicationID: .mockAny(), + dependencies: dependencies, + samplingRate: 100, + sdkInitDate: .mockAny(), + backgroundEventTrackingEnabled: .mockAny() + ) _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) _ = scope.process(command: RUMStopViewCommand.mockWith(identity: mockView)) @@ -113,7 +118,13 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) - let scope = RUMApplicationScope(rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 0, backgroundEventTrackingEnabled: .mockAny()) + let scope = RUMApplicationScope( + rumApplicationID: .mockAny(), + dependencies: dependencies, + samplingRate: 0, + sdkInitDate: .mockAny(), + backgroundEventTrackingEnabled: .mockAny() + ) _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) @@ -125,7 +136,13 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) - let scope = RUMApplicationScope(rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 50, backgroundEventTrackingEnabled: .mockAny()) + let scope = RUMApplicationScope( + rumApplicationID: .mockAny(), + dependencies: dependencies, + samplingRate: 50, + sdkInitDate: .mockAny(), + backgroundEventTrackingEnabled: .mockAny() + ) var currentTime = Date() let simulatedSessionsCount = 200 From 9cc6cd418a1146ad84ac91475507831e84b118bb Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 13 Dec 2021 14:59:33 +0100 Subject: [PATCH 8/9] RUMM-1765 Start 'ApplicationLaunch' view using the time of SDK init --- .../Scopes/RUMApplicationScope.swift | 14 ++--- .../RUMMonitor/Scopes/RUMSessionScope.swift | 4 +- Sources/Datadog/RUMMonitor.swift | 2 +- .../Datadog/Mocks/RUMFeatureMocks.swift | 13 +++- .../Scopes/RUMApplicationScopeTests.swift | 36 ++++++----- .../Scopes/RUMSessionScopeTests.swift | 59 +++++++++++-------- .../Datadog/RUMMonitorTests.swift | 10 +++- 7 files changed, 84 insertions(+), 54 deletions(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift index b2e13f4582..2fa181b983 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -37,8 +37,8 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { /// RUM Sessions sampling rate. internal let samplingRate: Float - /// Time of SDK initialization, measured in device date. - internal let sdkInitDate: Date + /// The start time of the application, measured in device date. It equals the time of SDK init. + private let applicationStartTime: Date /// Automatically detect background events internal let backgroundEventTrackingEnabled: Bool @@ -51,12 +51,12 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { rumApplicationID: String, dependencies: RUMScopeDependencies, samplingRate: Float, - sdkInitDate: Date, + applicationStartTime: Date, backgroundEventTrackingEnabled: Bool ) { self.dependencies = dependencies self.samplingRate = samplingRate - self.sdkInitDate = sdkInitDate + self.applicationStartTime = applicationStartTime self.backgroundEventTrackingEnabled = backgroundEventTrackingEnabled self.context = RUMContext( rumApplicationID: rumApplicationID, @@ -76,7 +76,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { func process(command: RUMCommand) -> Bool { if sessionScope == nil { - startInitialSession(on: command) + startInitialSession() } if let currentSession = sessionScope { @@ -99,13 +99,13 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { _ = refreshedSession.process(command: command) } - private func startInitialSession(on command: RUMCommand) { + private func startInitialSession() { let initialSession = RUMSessionScope( isInitialSession: true, parent: self, dependencies: dependencies, samplingRate: samplingRate, - startTime: command.time, + startTime: applicationStartTime, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled ) sessionScope = initialSession diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift index 27803a4ce1..2e6e29d2d1 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift @@ -49,7 +49,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { let isInitialSession: Bool /// RUM Session sampling rate. private let samplingRate: Float - /// The start time of this Session. + /// The start time of this Session, measured in device date. In initial session this is the time of SDK init. private let sessionStartTime: Date /// Time of the last RUM interaction noticed by this Session. private var lastInteractionTime: Date @@ -191,7 +191,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { name: Constants.applicationLaunchViewName, attributes: command.attributes, customTimings: [:], - startTime: command.time + startTime: sessionStartTime ) ) } diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index 8e581f2169..f4824f8198 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -199,7 +199,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { onSessionStart: rumFeature.onSessionStart ), samplingRate: rumFeature.configuration.sessionSamplingRate, - sdkInitDate: rumFeature.sdkInitDate, + applicationStartTime: rumFeature.sdkInitDate, backgroundEventTrackingEnabled: rumFeature.configuration.backgroundEventTrackingEnabled ), dateProvider: rumFeature.dateProvider diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 9eb9c2970e..c4304ec7af 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -178,6 +178,15 @@ func mockRandomRUMCommand(where predicate: (RUMCommand) -> Bool = { _ in true }) return allCommands.filter(predicate).randomElement()! } +extension RUMCommand { + func replacing(time: Date? = nil, attributes: [AttributeKey: AttributeValue]? = nil) -> RUMCommand { + var command = self + command.time = time ?? command.time + command.attributes = attributes ?? command.attributes + return command + } +} + extension RUMStartViewCommand: AnyMockable, RandomMockable { static func mockAny() -> RUMStartViewCommand { mockWith() } @@ -663,14 +672,14 @@ extension RUMApplicationScope { rumApplicationID: String = .mockAny(), dependencies: RUMScopeDependencies = .mockAny(), samplingRate: Float = .mockAny(), - sdkInitDate: Date = .mockAny(), + applicationStartTime: Date = .mockAny(), backgroundEventTrackingEnabled: Bool = .mockAny() ) -> RUMApplicationScope { return RUMApplicationScope( rumApplicationID: rumApplicationID, dependencies: dependencies, samplingRate: samplingRate, - sdkInitDate: sdkInitDate, + applicationStartTime: applicationStartTime, backgroundEventTrackingEnabled: backgroundEventTrackingEnabled ) } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 90449f5e48..1446b42d4c 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -13,7 +13,7 @@ class RUMApplicationScopeTests: XCTestCase { rumApplicationID: "abc-123", dependencies: .mockAny(), samplingRate: .mockAny(), - sdkInitDate: .mockAny(), + applicationStartTime: .mockAny(), backgroundEventTrackingEnabled: .mockAny() ) @@ -32,18 +32,23 @@ class RUMApplicationScopeTests: XCTestCase { expectation.fulfill() } + // Given + let currentTime = Date() let scope = RUMApplicationScope( rumApplicationID: .mockAny(), dependencies: .mockWith(onSessionStart: onSessionStart), samplingRate: 0, - sdkInitDate: .mockAny(), + applicationStartTime: currentTime, backgroundEventTrackingEnabled: .mockRandom() ) - XCTAssertNil(scope.sessionScope) - XCTAssertTrue(scope.process(command: mockRandomRUMCommand())) + + // When + let command = mockRandomRUMCommand().replacing(time: currentTime.addingTimeInterval(1)) + XCTAssertTrue(scope.process(command: command)) waitForExpectations(timeout: 0.5) + // Then let sessionScope = try XCTUnwrap(scope.sessionScope) XCTAssertEqual(sessionScope.backgroundEventTrackingEnabled, scope.backgroundEventTrackingEnabled) XCTAssertTrue(sessionScope.isInitialSession, "Starting the very first view in application must create initial session") @@ -60,16 +65,15 @@ class RUMApplicationScopeTests: XCTestCase { } // Given + var currentTime = Date() let scope = RUMApplicationScope( rumApplicationID: .mockAny(), dependencies: .mockWith(onSessionStart: onSessionStart), samplingRate: 100, - sdkInitDate: .mockAny(), + applicationStartTime: currentTime, backgroundEventTrackingEnabled: .mockAny() ) - var currentTime = Date() - let view = createMockViewInWindow() _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: view)) @@ -100,16 +104,17 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + let currentTime = Date() let scope = RUMApplicationScope( rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 100, - sdkInitDate: .mockAny(), + applicationStartTime: currentTime, backgroundEventTrackingEnabled: .mockAny() ) - _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) - _ = scope.process(command: RUMStopViewCommand.mockWith(identity: mockView)) + _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) + _ = scope.process(command: RUMStopViewCommand.mockWith(time: currentTime, identity: mockView)) XCTAssertEqual(try output.recordedEvents(ofType: RUMEvent.self).count, 2) } @@ -118,16 +123,17 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + let currentTime = Date() let scope = RUMApplicationScope( rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 0, - sdkInitDate: .mockAny(), + applicationStartTime: currentTime, backgroundEventTrackingEnabled: .mockAny() ) - _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) - _ = scope.process(command: RUMStartViewCommand.mockWith(identity: mockView)) + _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) + _ = scope.process(command: RUMStartViewCommand.mockWith(time: currentTime, identity: mockView)) XCTAssertEqual(try output.recordedEvents(ofType: RUMEvent.self).count, 0) } @@ -136,15 +142,15 @@ class RUMApplicationScopeTests: XCTestCase { let output = RUMEventOutputMock() let dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + var currentTime = Date() let scope = RUMApplicationScope( rumApplicationID: .mockAny(), dependencies: dependencies, samplingRate: 50, - sdkInitDate: .mockAny(), + applicationStartTime: currentTime, backgroundEventTrackingEnabled: .mockAny() ) - var currentTime = Date() let simulatedSessionsCount = 200 (0.. Date: Mon, 13 Dec 2021 15:09:04 +0100 Subject: [PATCH 9/9] RUMM-1765 Fix `Benchmarks` build --- Tests/DatadogBenchmarkTests/BenchmarkMocks.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift index 3a09fa1a80..cacfefc72d 100644 --- a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift +++ b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift @@ -23,6 +23,7 @@ extension FeaturesCommonDependencies { performance: .benchmarksPreset, httpClient: HTTPClient(), mobileDevice: MobileDevice(), + sdkInitDate: Date(), dateProvider: SystemDateProvider(), dateCorrector: DateCorrectorMock(), userInfoProvider: UserInfoProvider(),