From 3c74766203ecc2536a7d218ad6a60676a4051f1b Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 12:46:30 +0100 Subject: [PATCH 1/8] RUM-3461 Refactor RUM to depend on `FeatureScope` by removing abitrary `core` reference passing --- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 6 +- .../RUM/RUMMonitorConfigurationTests.swift | 2 +- .../Sources/DatadogCoreProtocol.swift | 4 + DatadogRUM/Sources/Feature/RUMFeature.swift | 5 +- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 12 +- .../Scopes/RUMScopeDependencies.swift | 7 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 6 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 2 +- .../ErrorMessageReceiverTests.swift | 59 ++++---- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 6 +- .../Tests/RUMMonitor/MonitorTests.swift | 32 ++--- .../Scopes/RUMSessionScopeTests.swift | 129 ++++++------------ .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 35 ++--- .../RUMMonitorProtocol+ConvenienceTests.swift | 2 +- .../RUMMonitorProtocol+InternalTests.swift | 2 +- TestUtilities/Mocks/FeatureMessageMocks.swift | 5 + 16 files changed, 128 insertions(+), 186 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 2b08b9da73..397cd4d2b9 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -697,7 +697,7 @@ extension RUMScopeDependencies { } static func mockWith( - core: DatadogCoreProtocol = NOPDatadogCore(), + scope: FeatureScope = NOPFeatureScope(), rumApplicationID: String = .mockAny(), sessionSampler: Sampler = .mockKeepAll(), trackBackgroundEvents: Bool = .mockAny(), @@ -712,7 +712,7 @@ extension RUMScopeDependencies { viewCache: ViewCache = ViewCache() ) -> RUMScopeDependencies { return RUMScopeDependencies( - core: core, + scope: scope, rumApplicationID: rumApplicationID, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, @@ -744,7 +744,7 @@ extension RUMScopeDependencies { viewCache: ViewCache? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( - core: self.core, + scope: self.scope, rumApplicationID: rumApplicationID ?? self.rumApplicationID, sessionSampler: sessionSampler ?? self.sessionSampler, trackBackgroundEvents: trackBackgroundEvents ?? self.trackBackgroundEvents, diff --git a/DatadogCore/Tests/Datadog/RUM/RUMMonitorConfigurationTests.swift b/DatadogCore/Tests/Datadog/RUM/RUMMonitorConfigurationTests.swift index c6b7f28dc6..152704ce1e 100644 --- a/DatadogCore/Tests/Datadog/RUM/RUMMonitorConfigurationTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/RUMMonitorConfigurationTests.swift @@ -41,7 +41,7 @@ class RUMMonitorConfigurationTests: XCTestCase { let monitor = RUMMonitor.shared(in: core).dd let dependencies = monitor.scopes.dependencies - monitor.core?.scope(for: RUMFeature.self).eventWriteContext { context, _ in + monitor.featureScope.eventWriteContext { context, _ in DDAssertReflectionEqual(context.userInfo, self.userIno) XCTAssertEqual(context.networkConnectionInfo, self.networkConnectionInfo) XCTAssertEqual(context.carrierInfo, self.carrierInfo) diff --git a/DatadogInternal/Sources/DatadogCoreProtocol.swift b/DatadogInternal/Sources/DatadogCoreProtocol.swift index 43f519a0d6..358e30b29d 100644 --- a/DatadogInternal/Sources/DatadogCoreProtocol.swift +++ b/DatadogInternal/Sources/DatadogCoreProtocol.swift @@ -12,6 +12,10 @@ import Foundation /// Any reference to `DatadogCoreProtocol` must be captured as `weak` within a Feature. This is to avoid /// retain cycle of core holding the Feature and vice-versa. public protocol DatadogCoreProtocol: AnyObject, MessageSending, BaggageSharing { + // TODO: RUM-3717 + // Remove `DatadogCoreProtocol` conformance to `MessageSending` and `BaggageSharing` once + // all features are migrated to depend on `FeatureScope` interface. + /// Registers a Feature instance. /// /// Feature can interact with the core and other Feature through the message bus. Some specific Features diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 3a0e5e6f4d..4a58cb3cf2 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -26,8 +26,9 @@ internal final class RUMFeature: DatadogRemoteFeature { ) throws { self.configuration = configuration + let featureScope = core.scope(for: RUMFeature.self) let dependencies = RUMScopeDependencies( - core: core, + scope: featureScope, rumApplicationID: configuration.applicationID, sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate), trackBackgroundEvents: configuration.trackBackgroundEvents, @@ -72,7 +73,7 @@ internal final class RUMFeature: DatadogRemoteFeature { ) self.monitor = Monitor( - core: core, + featureScope: featureScope, dependencies: dependencies, dateProvider: configuration.dateProvider ) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index ae54020133..f7993a8692 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -110,6 +110,7 @@ internal enum RUMInternalErrorSource: String, Decodable { internal typealias RUMErrorCategory = RUMErrorEvent.Error.Category internal class Monitor: RUMCommandSubscriber { + let featureScope: FeatureScope let scopes: RUMApplicationScope let dateProvider: DateProvider let queue = DispatchQueue( @@ -117,24 +118,23 @@ internal class Monitor: RUMCommandSubscriber { target: .global(qos: .userInteractive) ) - private(set) weak var core: DatadogCoreProtocol? private(set) var debugging: RUMDebugging? = nil private var attributes: [AttributeKey: AttributeValue] = [:] init( - core: DatadogCoreProtocol, + featureScope: FeatureScope, dependencies: RUMScopeDependencies, dateProvider: DateProvider ) { - self.core = core + self.featureScope = featureScope self.scopes = RUMApplicationScope(dependencies: dependencies) self.dateProvider = dateProvider } func process(command: RUMCommand) { // process command in event context - core?.scope(for: RUMFeature.self).eventWriteContext { context, writer in + featureScope.eventWriteContext { context, writer in self.queue.sync { let transformedCommand = self.transform(command: command) @@ -147,7 +147,7 @@ internal class Monitor: RUMCommandSubscriber { } // update the core context with rum context - core?.set( + featureScope.set( baggage: { self.queue.sync { () -> RUMCoreContext? in let context = self.scopes.activeSession?.viewScopes.last?.context ?? @@ -218,7 +218,7 @@ extension Monitor: RUMMonitorProtocol { // Even though we're not writing anything, need to get the write context // to make sure we're returning the correct sessionId after all other // events have processed. - core?.scope(for: RUMFeature.self).eventWriteContext { _, _ in + featureScope.eventWriteContext { _, _ in self.queue.sync { guard let sessionId = self.scopes.activeSession?.sessionUUID else { completion(nil) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index 30b30cedb7..45d611c848 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -27,7 +27,8 @@ internal struct VitalsReaders { /// Dependency container for injecting components to `RUMScopes` hierarchy. internal struct RUMScopeDependencies { - weak var core: DatadogCoreProtocol? + /// The RUM feature scope to interact with core. + let scope: FeatureScope let rumApplicationID: String let sessionSampler: Sampler let trackBackgroundEvents: Bool @@ -42,9 +43,7 @@ internal struct RUMScopeDependencies { let onSessionStart: RUM.SessionListener? let viewCache: ViewCache - var telemetry: Telemetry { - core?.telemetry ?? NOPTelemetry() - } + var telemetry: Telemetry { scope.telemetry } var sessionType: RUMSessionType { if ciTest != nil { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index 3eba6ffd05..9937848fe6 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -46,7 +46,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { /// Information about this session state, shared with `CrashContext`. private var state: RUMSessionState { didSet { - dependencies.core?.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) + dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) } } @@ -118,7 +118,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { } // Update `CrashContext` with recent RUM session state: - dependencies.core?.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) + dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) // Notify Synthetics if needed if dependencies.syntheticsTest != nil && sessionUUID != .nullUUID { @@ -219,7 +219,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // We also want to send this as a session is being stopped. // It means that with Background Events Tracking disabled, eventual off-view crashes will be dropped // similar to how we drop other events. - dependencies.core?.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) + dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) } return isActive || !viewScopes.isEmpty diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 507afc5921..063c3e4b05 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -542,7 +542,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // Update `CrashContext` with recent RUM view (no matter sampling - we want to always // have recent information if process is interrupted by crash): - dependencies.core?.send( + dependencies.scope.send( message: .baggage( key: RUMBaggageKeys.viewEvent, value: event diff --git a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift index 988bfe2440..dde67ee65a 100644 --- a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift @@ -11,15 +11,13 @@ import DatadogInternal @testable import DatadogRUM class ErrorMessageReceiverTests: XCTestCase { - var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional + private let featureScope = FeatureScopeMock() + private var receiver: ErrorMessageReceiver! // swiftlint:disable:this implicitly_unwrapped_optional override func setUp() { - super.setUp() - - core = PassthroughCoreMock() - core.messageReceiver = ErrorMessageReceiver( + receiver = ErrorMessageReceiver( monitor: Monitor( - core: core, + featureScope: featureScope, dependencies: .mockAny(), dateProvider: SystemDateProvider() ) @@ -27,49 +25,42 @@ class ErrorMessageReceiverTests: XCTestCase { } override func tearDown() { - core = nil - super.tearDown() + receiver = nil } func testReceiveIncompleteError() throws { - let expectation = expectation(description: "Don't send error fallback") - // When - core.send( - message: .baggage(key: "error", value: ["message": "message-test"]), - else: { expectation.fulfill() } + let message: FeatureMessage = .baggage( + key: ErrorMessageReceiver.ErrorMessage.key, + value: ["message": "message-test"] ) - - let errorEvents = core.events(ofType: RUMErrorEvent.self) + let result = receiver.receive(message: message, from: NOPDatadogCore()) // Then - waitForExpectations(timeout: 0.5, handler: nil) - XCTAssertTrue(errorEvents.isEmpty) + XCTAssertFalse(result, "It must reject the message") + let events: [RUMErrorEvent] = featureScope.eventsWritten() + XCTAssertTrue(events.isEmpty, "It should not send error") } func testReceivePartialError() throws { - core.expectation = expectation(description: "Send Error") - // When - core.send( - message: .baggage(key: "error", value: [ + let message: FeatureMessage = .baggage( + key: ErrorMessageReceiver.ErrorMessage.key, + value: [ "message": "message-test", "source": "custom" - ]) + ] ) + let result = receiver.receive(message: message, from: NOPDatadogCore()) // Then - waitForExpectations(timeout: 0.5, handler: nil) - - let event: RUMErrorEvent = try XCTUnwrap(core.events().last, "It should send log") + XCTAssertTrue(result, "It must accept the message") + let event: RUMErrorEvent = try XCTUnwrap(featureScope.eventsWritten().last, "It should send error") XCTAssertEqual(event.error.message, "message-test") XCTAssertEqual(event.error.source, .custom) } func testReceiveCompleteError() throws { - core.expectation = expectation(description: "Send Error") - - // When let mockAttribute: String = .mockRandom() let baggage: [String: Any] = [ "message": "message-test", @@ -80,14 +71,14 @@ class ErrorMessageReceiverTests: XCTestCase { "any-key": mockAttribute ] ] - core.send( - message: .baggage(key: "error", value: AnyEncodable(baggage)) - ) - // Then - waitForExpectations(timeout: 0.5, handler: nil) + // When + let message: FeatureMessage = .baggage(key: ErrorMessageReceiver.ErrorMessage.key, value: AnyEncodable(baggage)) + let result = receiver.receive(message: message, from: NOPDatadogCore()) - let event: RUMErrorEvent = try XCTUnwrap(core.events().last, "It should send log") + // Then + XCTAssertTrue(result, "It must accept the message") + let event: RUMErrorEvent = try XCTUnwrap(featureScope.eventsWritten().last, "It should send error") XCTAssertEqual(event.error.message, "message-test") XCTAssertEqual(event.error.type, "type-test") XCTAssertEqual(event.error.stack, "stack-test") diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 27fadbec8e..64a8f3e332 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -743,7 +743,7 @@ extension RUMScopeDependencies { } static func mockWith( - core: DatadogCoreProtocol = NOPDatadogCore(), + scope: FeatureScope = NOPFeatureScope(), rumApplicationID: String = .mockAny(), sessionSampler: Sampler = .mockKeepAll(), trackBackgroundEvents: Bool = .mockAny(), @@ -758,7 +758,7 @@ extension RUMScopeDependencies { viewCache: ViewCache = ViewCache() ) -> RUMScopeDependencies { return RUMScopeDependencies( - core: core, + scope: scope, rumApplicationID: rumApplicationID, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, @@ -790,7 +790,7 @@ extension RUMScopeDependencies { viewCache: ViewCache? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( - core: self.core, + scope: self.scope, rumApplicationID: rumApplicationID ?? self.rumApplicationID, sessionSampler: sessionSampler ?? self.sessionSampler, trackBackgroundEvents: trackBackgroundEvents ?? self.trackBackgroundEvents, diff --git a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift index c27c2bd32e..261a4a2fb3 100644 --- a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift @@ -10,17 +10,7 @@ import DatadogInternal @testable import DatadogRUM class MonitorTests: XCTestCase { - private var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional - - override func setUp() { - super.setUp() - core = PassthroughCoreMock() - } - - override func tearDown() { - core = nil - super.tearDown() - } + private let scope = FeatureScopeMock() func testWhenSessionIsSampled_itSetsRUMContextInCore() throws { // Given @@ -28,8 +18,8 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - core: core, - dependencies: .mockWith(core: core, sessionSampler: sampler), + featureScope: scope, + dependencies: .mockWith(scope: scope, sessionSampler: sampler), dateProvider: DateProviderMock() ) monitor.startView(key: "foo") @@ -37,7 +27,7 @@ class MonitorTests: XCTestCase { // Then let expectedContext = monitor.currentRUMContext - let rumBaggage = try XCTUnwrap(core.context.baggages[RUMFeature.name]) + let rumBaggage = try XCTUnwrap(scope.contextMock.baggages[RUMFeature.name]) let rumContext = try rumBaggage.decode(type: RUMCoreContext.self) XCTAssertEqual(rumContext.applicationID, expectedContext.rumApplicationID) XCTAssertEqual(rumContext.sessionID, expectedContext.sessionID.toRUMDataFormat) @@ -50,15 +40,15 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - core: core, - dependencies: .mockWith(core: core, sessionSampler: sampler), + featureScope: scope, + dependencies: .mockWith(scope: scope, sessionSampler: sampler), dateProvider: DateProviderMock() ) monitor.startView(key: "foo") monitor.flush() // Then - XCTAssertNil(core.context.baggages[RUMFeature.name]) + XCTAssertNil(scope.contextMock.baggages[RUMFeature.name]) } func testStartView_withViewController_itUsesClassNameAsViewName() throws { @@ -67,8 +57,8 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - core: core, - dependencies: .mockWith(core: core, sessionSampler: .mockKeepAll()), + featureScope: scope, + dependencies: .mockWith(scope: scope, sessionSampler: .mockKeepAll()), dateProvider: DateProviderMock() ) monitor.startView(viewController: vc) @@ -85,8 +75,8 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - core: core, - dependencies: .mockWith(core: core, sessionSampler: .mockKeepAll()), + featureScope: scope, + dependencies: .mockWith(scope: scope, sessionSampler: .mockKeepAll()), dateProvider: DateProviderMock() ) monitor.startView(viewController: vc, name: "Some View") diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift index e5e88c90a1..e62e1d446c 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -336,19 +336,10 @@ class RUMSessionScopeTests: XCTestCase { XCTAssertEqual(scope.viewScopes.count, 0) } - // MARK: Integration with Crash Context - - func testWhenSessionScopeIsCreated_thenItUpdatesLastRUMSessionStateInCrashContext() throws { - var sessionState: RUMSessionState? = nil - - let messageReciever = FeatureMessageReceiverMock { message in - sessionState = try? message.baggage(forKey: RUMBaggageKeys.sessionState) - } - - let core = PassthroughCoreMock( - messageReceiver: messageReciever - ) + // MARK: - Sending Messages Over Message Bus + func testWhenSessionScopeIsCreated_itSendsSessionStateMessage() throws { + let featureScope = FeatureScopeMock() let randomIsInitialSession: Bool = .mockRandom() let randomIsReplayBeingRecorded: Bool? = .mockRandom() @@ -357,7 +348,7 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, dependencies: .mockWith( - core: core, + scope: featureScope, sessionSampler: .mockRandom() // no matter if sampled or not ), hasReplay: randomIsReplayBeingRecorded @@ -370,21 +361,13 @@ class RUMSessionScopeTests: XCTestCase { hasTrackedAnyView: false, didStartWithReplay: randomIsReplayBeingRecorded ) - XCTAssertEqual(sessionState, expectedSessionState, "It must inject expected session state to crash context") + let baggageSent = try XCTUnwrap(featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.sessionState)) + let sessionStateSent: RUMSessionState = try baggageSent.decode() + XCTAssertEqual(sessionStateSent, expectedSessionState, "It must send 'session state' message") } - func testWhenSessionScopeStartsAnyView_thenItUpdatesLastRUMSessionStateInCrashContext() throws { - var sessionState: RUMSessionState? = nil - let messageReciever = FeatureMessageReceiverMock { message in - if case let .baggage(label, baggage) = message, label == RUMBaggageKeys.sessionState { - sessionState = try? baggage.decode() - } - } - - let core = PassthroughCoreMock( - messageReceiver: messageReciever - ) - + func testWhenSessionScopeStartsAnyView_itSendsSessionStateMessage() throws { + let featureScope = FeatureScopeMock() let randomIsInitialSession: Bool = .mockRandom() let randomIsReplayBeingRecorded: Bool? = .mockRandom() @@ -394,14 +377,12 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(core: core), + dependencies: .mockWith(scope: featureScope), hasReplay: randomIsReplayBeingRecorded ) // When - core.eventWriteContext { context, writer in - _ = scope.process(command: RUMStartViewCommand.mockWith(time: sessionStartTime), context: context, writer: writer) - } + _ = scope.process(command: RUMStartViewCommand.mockWith(time: sessionStartTime), context: context, writer: writer) XCTAssertFalse(scope.viewScopes.isEmpty, "Session started some view") @@ -412,45 +393,40 @@ class RUMSessionScopeTests: XCTestCase { hasTrackedAnyView: true, didStartWithReplay: randomIsReplayBeingRecorded ) - - XCTAssertEqual(sessionState, expectedSessionState, "It must inject expected session state to crash context") + let baggageSent = try XCTUnwrap(featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.sessionState)) + let sessionStateSent: RUMSessionState = try baggageSent.decode() + XCTAssertEqual(sessionStateSent, expectedSessionState, "It must send 'session state' message") } - func testWhenSessionScopeHasNoActiveView_thenItUpdatesLastRUMViewEventInCrashContext() throws { - var viewEvent: RUMViewEvent? = nil - let messageReciever = FeatureMessageReceiverMock { message in - if let event: RUMViewEvent = try? message.baggage(forKey: RUMBaggageKeys.viewEvent) { - viewEvent = event - } else if let reset: Bool = try? message.baggage(forKey: RUMBaggageKeys.viewReset), reset { - viewEvent = nil - } - } - - let core = PassthroughCoreMock( - context: context, - messageReceiver: messageReciever - ) + func testWhenSessionScopeHasNoActiveView_itSendsViewEventMessages() throws { + let featureScope = FeatureScopeMock() // Given let sessionStartTime = Date() let scope: RUMSessionScope = .mockWith( parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(core: core) + dependencies: .mockWith(scope: featureScope) ) // When - let command = RUMStartViewCommand.mockWith(time: sessionStartTime, identity: .mockViewIdentifier()) + let command = RUMStartViewCommand.mockWith(time: sessionStartTime, identity: .mockViewIdentifier(), name: "ActiveView") _ = scope.process(command: command, context: context, writer: writer) // Then - XCTAssertNotNil(viewEvent, "Crash context must be include rum view event, because there is an active view") + let viewEventBaggage: RUMViewEvent = try XCTUnwrap( + featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewEvent)?.decode() + ) + XCTAssertEqual(viewEventBaggage.view.name, "ActiveView", "It must send 'view event' message") // When _ = scope.process(command: RUMStopViewCommand.mockWith(time: sessionStartTime.addingTimeInterval(1), identity: .mockViewIdentifier()), context: context, writer: writer) // Then - XCTAssertNil(viewEvent, "Crash context must not include rum view event, because there is no active view") + let viewResetBaggage: Bool = try XCTUnwrap( + featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewReset)?.decode() + ) + XCTAssertTrue(viewResetBaggage, "It must send 'view reset' message") } // MARK: - Stopping Sessions @@ -581,59 +557,43 @@ class RUMSessionScopeTests: XCTestCase { XCTAssertFalse(result) } - func testWhenScopeEnded_itUpdatesContext() { - // Given - var viewEvent: RUMViewEvent? = nil - let messageReciever = FeatureMessageReceiverMock { message in - if case let .baggage(label, baggage) = message, label == RUMBaggageKeys.viewEvent { - viewEvent = try? baggage.decode() - } else if case let .baggage(label, _) = message, label == RUMBaggageKeys.viewReset { - viewEvent = nil - } - } - - let core = PassthroughCoreMock( - context: context, - messageReceiver: messageReciever - ) + func testWhenScopeEnded_itSendsViewEventMessages() throws { + let featureScope = FeatureScopeMock() + // Given let scope: RUMSessionScope = .mockWith( parent: parent, startTime: Date(), - dependencies: .mockWith(core: core) + dependencies: .mockWith(scope: featureScope) ) - let command = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier()) + let command = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier(), name: "ActiveView") // When _ = scope.process(command: command, context: context, writer: writer) // Then - XCTAssertNotNil(viewEvent) + let viewEventBaggage: RUMViewEvent = try XCTUnwrap( + featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewEvent)?.decode() + ) + XCTAssertEqual(viewEventBaggage.view.name, "ActiveView", "It must send 'view event' message") // When _ = scope.process(command: RUMStopSessionCommand.mockWith(time: Date()), context: context, writer: writer) // Then - XCTAssertNil(viewEvent) + let viewResetBaggage: Bool = try XCTUnwrap( + featureScope.messagesSent().lastBaggage(withKey: RUMBaggageKeys.viewReset)?.decode() + ) + XCTAssertTrue(viewResetBaggage, "It must send 'view reset' message") } - func testWhenScopeEnded_itDoesNotResetContextNextUpdate() { - // Given - var viewResetCallCount = 0 - let messageReciever = FeatureMessageReceiverMock { message in - if case let .baggage(label, _) = message, label == RUMBaggageKeys.viewReset { - viewResetCallCount += 1 - } - } - - let core = PassthroughCoreMock( - context: context, - messageReceiver: messageReciever - ) + func testWhenScopeEnded_itDoesNotSendViewResetMessage() { + let featureScope = FeatureScopeMock() + // Given let scope: RUMSessionScope = .mockWith( parent: parent, startTime: Date(), - dependencies: .mockWith(core: core) + dependencies: .mockWith(scope: featureScope) ) let startViewCommand = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier()) @@ -647,7 +607,8 @@ class RUMSessionScopeTests: XCTestCase { _ = scope.process(command: stopResourceCommand, context: context, writer: writer) // Then - XCTAssertEqual(viewResetCallCount, 1) + let viewResetMessages = featureScope.messagesSent().filter { $0.asBaggage?.key == RUMBaggageKeys.viewReset } + XCTAssertEqual(viewResetMessages.count, 1, "It must send only one 'view reset' message") } // MARK: - Usage diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index 596b6d2294..c6a98ad9aa 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -2439,25 +2439,16 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.dd.documentVersion, 1, "It should record only one view update") } - // MARK: Integration with Crash Context - - func testWhenViewIsStarted_thenItUpdatesLastRUMViewEventInCrashContext() throws { - var viewEvent: RUMViewEvent? = nil - let messageReciever = FeatureMessageReceiverMock { message in - if case let .baggage(label, baggage) = message, label == RUMBaggageKeys.viewEvent { - viewEvent = try? baggage.decode() - } - } + // MARK: - Sending Messages Over Message Bus - let core = PassthroughCoreMock( - messageReceiver: messageReciever - ) + func testWhenViewIsStarted_itSendsViewEventMessages() throws { + let featureScope = FeatureScopeMock() // Given let scope = RUMViewScope( isInitialView: .mockRandom(), parent: parent, - dependencies: .mockWith(core: core), + dependencies: .mockWith(scope: featureScope), identity: .mockViewIdentifier(), path: "UIViewController", name: "ViewController", @@ -2468,18 +2459,18 @@ class RUMViewScopeTests: XCTestCase { ) // When - core.eventWriteContext { context, writer in - XCTAssertTrue( - scope.process( - command: RUMStartViewCommand.mockWith(identity: .mockViewIdentifier()), - context: context, - writer: writer - ) + featureScope.eventWriteContext { context, writer in + _ = scope.process( + command: RUMStartViewCommand.mockWith(identity: .mockViewIdentifier()), + context: context, + writer: writer ) } // Then - let rumViewSent = try XCTUnwrap(core.events(ofType: RUMViewEvent.self).last, "It should send view event") - DDAssertReflectionEqual(viewEvent, rumViewSent, "It must inject sent event to crash context") + let rumViewWritten = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).last, "It should send view event") + let baggageSent = try XCTUnwrap(featureScope.messagesSent().firstBaggage(withKey: RUMBaggageKeys.viewEvent)) + let rumViewSent: RUMViewEvent = try baggageSent.decode() + DDAssertReflectionEqual(rumViewSent, rumViewWritten, "It must sent written event over message bus") } } diff --git a/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift b/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift index dbededa34b..660e7e6b39 100644 --- a/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift +++ b/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift @@ -17,7 +17,7 @@ class RUMMonitorProtocol_ConvenienceTests: XCTestCase { func testCallingExtensionMethodsIsSafe() { // Given let monitor = Monitor( - core: PassthroughCoreMock(), + featureScope: NOPFeatureScope(), dependencies: .mockAny(), dateProvider: SystemDateProvider() ) diff --git a/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift b/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift index 713db72cd8..fd6a9d7c85 100644 --- a/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift +++ b/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift @@ -15,7 +15,7 @@ class RUMMonitorProtocol_InternalTests: XCTestCase { // When monitor = Monitor( - core: PassthroughCoreMock(), + featureScope: NOPFeatureScope(), dependencies: .mockAny(), dateProvider: SystemDateProvider() ) diff --git a/TestUtilities/Mocks/FeatureMessageMocks.swift b/TestUtilities/Mocks/FeatureMessageMocks.swift index 9fb5074462..2c9b0f3437 100644 --- a/TestUtilities/Mocks/FeatureMessageMocks.swift +++ b/TestUtilities/Mocks/FeatureMessageMocks.swift @@ -13,6 +13,11 @@ public extension Array where Element == FeatureMessage { return compactMap({ $0.asBaggage }).filter({ $0.key == key }).first?.baggage } + /// Unpacks the last "baggage message" with given key in this array. + func lastBaggage(withKey key: String) -> FeatureBaggage? { + return compactMap({ $0.asBaggage }).filter({ $0.key == key }).last?.baggage + } + /// Unpacks the first "baggage message" with given key in this array. var firstWebViewMessage: WebViewMessage? { return lazy.compactMap { $0.asWebViewMessage }.first From 41ef5288face07c8dbeeeb6c1f8e62e8321a8ca4 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 13:05:39 +0100 Subject: [PATCH 2/8] RUM-3461 Refactor `WebViewEventReceiver` to depend on `FeatureScope` --- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 2 + DatadogRUM/Sources/Feature/RUMFeature.swift | 1 + .../Integrations/WebViewEventReceiver.swift | 12 ++-- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 1 + .../WebViewEventReceiverTests.swift | 59 ++++++++++--------- .../Mocks/CoreMocks/FeatureScopeMock.swift | 5 +- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 397cd4d2b9..1ad4db1990 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -38,11 +38,13 @@ extension WebViewEventReceiver: AnyMockable { } static func mockWith( + featureScope: FeatureScope = NOPFeatureScope(), dateProvider: DateProvider = SystemDateProvider(), commandSubscriber: RUMCommandSubscriber = RUMCommandSubscriberMock(), viewCache: ViewCache = ViewCache() ) -> Self { .init( + featureScope: featureScope, dateProvider: dateProvider, commandSubscriber: commandSubscriber, viewCache: viewCache diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 4a58cb3cf2..ccb3fd17ab 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -102,6 +102,7 @@ internal final class RUMFeature: DatadogRemoteFeature { ), ErrorMessageReceiver(monitor: monitor), WebViewEventReceiver( + featureScope: featureScope, dateProvider: configuration.dateProvider, commandSubscriber: monitor, viewCache: dependencies.viewCache diff --git a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift index 0fb8f1e3e6..e909d865a1 100644 --- a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift +++ b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift @@ -11,6 +11,8 @@ internal typealias JSON = [String: Any] /// Receiver to consume a RUM event coming from Browser SDK. internal final class WebViewEventReceiver: FeatureMessageReceiver { + /// RUM feature scope. + let featureScope: FeatureScope /// Subscriber that can process a `RUMKeepSessionAliveCommand`. let commandSubscriber: RUMCommandSubscriber @@ -26,10 +28,12 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { /// - dateProvider: The date provider. /// - commandSubscriber: Subscriber that can process a `RUMKeepSessionAliveCommand`. init( + featureScope: FeatureScope, dateProvider: DateProvider, commandSubscriber: RUMCommandSubscriber, viewCache: ViewCache ) { + self.featureScope = featureScope self.commandSubscriber = commandSubscriber self.dateProvider = dateProvider self.viewCache = viewCache @@ -40,7 +44,7 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { return false } - write(event: event, to: core) + write(event: event) return true } @@ -51,7 +55,7 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { /// - Parameters: /// - event: The Browser RUM event. /// - core: The core to write the event. - private func write(event: JSON, to core: DatadogCoreProtocol) { + private func write(event: JSON) { commandSubscriber.process( command: RUMKeepSessionAliveCommand( time: dateProvider.now, @@ -59,7 +63,7 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { ) ) - core.scope(for: RUMFeature.self).eventWriteContext { [weak core] context, writer in + featureScope.eventWriteContext { [featureScope] context, writer in guard let rumBaggage = context.baggages[RUMFeature.name] else { return // Drop event if RUM is not enabled or RUM session is not sampled } @@ -102,7 +106,7 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { writer.write(value: AnyEncodable(event)) } catch { - core?.telemetry.error("Failed to decode `RUMCoreContext`", error: error) + featureScope.telemetry.error("Failed to decode `RUMCoreContext`", error: error) } } } diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index f7993a8692..3cafcd7aa7 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -110,6 +110,7 @@ internal enum RUMInternalErrorSource: String, Decodable { internal typealias RUMErrorCategory = RUMErrorEvent.Error.Category internal class Monitor: RUMCommandSubscriber { + /// RUM feature scope. let featureScope: FeatureScope let scopes: RUMApplicationScope let dateProvider: DateProvider diff --git a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift index 11365e5b16..6b23a6d120 100644 --- a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift @@ -10,6 +10,7 @@ import DatadogInternal @testable import DatadogRUM class WebViewEventReceiverTests: XCTestCase { + private let featureScope = FeatureScopeMock() /// Creates random RUM Browser event. /// Both mobile and browser events conform to the same schema, so we can consider mobile events browser-compatible. private func randomWebEvent() -> JSON { try! randomRUMEvent().toJSONObject() } @@ -123,11 +124,11 @@ class WebViewEventReceiverTests: XCTestCase { } func testWhenReceivingWebViewTrackingMessageWithValidEvent_itAcknowledgesTheMessageAndKeepsRUMSessionAlive() throws { - let core = PassthroughCoreMock() let commandsSubscriberMock = RUMCommandSubscriberMock() // Given let receiver = WebViewEventReceiver( + featureScope: featureScope, dateProvider: DateProviderMock(now: .mockDecember15th2019At10AMUTC()), commandSubscriber: commandsSubscriberMock, viewCache: ViewCache() @@ -135,7 +136,7 @@ class WebViewEventReceiverTests: XCTestCase { // When let message = webViewTrackingMessage(with: randomWebEvent()) - let result = receiver.receive(message: message, from: core) + let result = receiver.receive(message: message, from: NOPDatadogCore()) // Then XCTAssertTrue(result, "It must acknowledge the message") @@ -144,10 +145,9 @@ class WebViewEventReceiverTests: XCTestCase { } func testWhenReceivingOtherMessage_itRejectsIt() throws { - let core = PassthroughCoreMock() - // Given let receiver = WebViewEventReceiver( + featureScope: featureScope, dateProvider: DateProviderMock(), commandSubscriber: RUMCommandSubscriberMock(), viewCache: ViewCache() @@ -155,7 +155,7 @@ class WebViewEventReceiverTests: XCTestCase { // When let otherMessage: FeatureMessage = .baggage(key: "message to other receiver", value: String.mockRandom()) - let result = receiver.receive(message: otherMessage, from: core) + let result = receiver.receive(message: otherMessage, from: NOPDatadogCore()) // Then XCTAssertFalse(result, "It must reject messages addressed to other receivers") @@ -164,19 +164,19 @@ class WebViewEventReceiverTests: XCTestCase { // MARK: - Modifying Web Events func testGivenRUMContextAvailable_whenReceivingWebEvent_itGetsEnrichedWithOtherMobileContextAndWritten() throws { - let core = PassthroughCoreMock( - context: .mockWith( - source: "react-native", - serverTimeOffset: .mockRandom(min: -10, max: 10).rounded() - ) - ) - // Given - let rumContext: RUMCoreContext = .mockRandom() - core.set(baggage: rumContext, forKey: RUMFeature.name) let dateProvider = RelativeDateProvider() + let rumContext: RUMCoreContext = .mockRandom() + featureScope.contextMock = .mockWith( + source: "react-native", + serverTimeOffset: .mockRandom(min: -10, max: 10).rounded(), + baggages: [ + RUMFeature.name: FeatureBaggage(rumContext) + ] + ) let receiver = WebViewEventReceiver( + featureScope: featureScope, dateProvider: DateProviderMock(), commandSubscriber: RUMCommandSubscriberMock(), viewCache: ViewCache(dateProvider: dateProvider) @@ -203,7 +203,7 @@ class WebViewEventReceiverTests: XCTestCase { // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: core) + let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) // Then let expectedWebEventWritten: JSON = [ @@ -219,58 +219,59 @@ class WebViewEventReceiverTests: XCTestCase { "application": ["id": rumContext.applicationID], "session": ["id": rumContext.sessionID], "view": ["id": "00000000-aaaa-0000-aaaa-000000000000"], - "date": date + core.context.serverTimeOffset.toInt64Milliseconds, + "date": date + featureScope.contextMock.serverTimeOffset.toInt64Milliseconds, ].merging(random, uniquingKeysWith: { old, _ in old }) XCTAssertTrue(result, "It must accept the message") - XCTAssertEqual(core.events.count, 1, "It must write web event to core") - let actualWebEventWritten = try XCTUnwrap(core.events.first) + XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must write web event to core") + let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) DDAssertJSONEqual(AnyCodable(actualWebEventWritten), AnyCodable(expectedWebEventWritten)) } func testGivenRUMContextNotAvailable_whenReceivingWebEvent_itIsDropped() throws { - let core = PassthroughCoreMock() - // Given - XCTAssertNil(core.context.baggages[RUMFeature.name]) + XCTAssertNil(featureScope.contextMock.baggages[RUMFeature.name]) let receiver = WebViewEventReceiver( + featureScope: featureScope, dateProvider: DateProviderMock(), commandSubscriber: RUMCommandSubscriberMock(), viewCache: ViewCache() ) // When - let result = receiver.receive(message: webViewTrackingMessage(with: randomWebEvent()), from: core) + let result = receiver.receive(message: webViewTrackingMessage(with: randomWebEvent()), from: NOPDatadogCore()) // Then XCTAssertTrue(result, "It must accept the message") - XCTAssertTrue(core.events.isEmpty, "The event must be dropped") + XCTAssertTrue(featureScope.eventsWritten.isEmpty, "The event must be dropped") } func testGivenInvalidRUMContext_whenReceivingEvent_itSendsErrorTelemetry() throws { struct InvalidRUMContext: Codable { var foo = "bar" } - let telemetryReceiver = TelemetryReceiverMock() - let core = PassthroughCoreMock(messageReceiver: telemetryReceiver) // Given - core.set(baggage: InvalidRUMContext(), forKey: RUMFeature.name) - XCTAssertNotNil(core.context.baggages[RUMFeature.name]) + featureScope.contextMock = .mockWith( + baggages: [ + RUMFeature.name: FeatureBaggage(InvalidRUMContext()) + ] + ) let receiver = WebViewEventReceiver( + featureScope: featureScope, dateProvider: DateProviderMock(), commandSubscriber: RUMCommandSubscriberMock(), viewCache: ViewCache() ) // When - let result = receiver.receive(message: webViewTrackingMessage(with: randomWebEvent()), from: core) + let result = receiver.receive(message: webViewTrackingMessage(with: randomWebEvent()), from: NOPDatadogCore()) // Then XCTAssertTrue(result, "It should accept the message") - let errorTelemetry = try XCTUnwrap(telemetryReceiver.messages.firstError(), "It must send error telemetry") + let errorTelemetry = try XCTUnwrap(featureScope.telemetryMock.messages.firstError(), "It must send error telemetry") XCTAssertTrue(errorTelemetry.message.hasPrefix("Failed to decode `RUMCoreContext`")) } } diff --git a/TestUtilities/Mocks/CoreMocks/FeatureScopeMock.swift b/TestUtilities/Mocks/CoreMocks/FeatureScopeMock.swift index eae1e7793d..cc4c318292 100644 --- a/TestUtilities/Mocks/CoreMocks/FeatureScopeMock.swift +++ b/TestUtilities/Mocks/CoreMocks/FeatureScopeMock.swift @@ -49,7 +49,10 @@ public class FeatureScopeMock: FeatureScope { // MARK: - Side Effects Observation - /// Retrieve events written through Even Write Context API. + /// Retrieve anonymous events written through Even Write Context API. + public var eventsWritten: [Encodable] { events.map { $0.event } } + + /// Retrieve typed events written through Even Write Context API. public func eventsWritten(ofType type: T.Type = T.self) -> [T] where T: Encodable { return events.compactMap { $0.event as? T } } From a1b941dd0c0539000c2db9bd23d4079c652931fd Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 13:54:38 +0100 Subject: [PATCH 3/8] RUM-3461 Refactor `CrashReportReceiver` to depend on `FeatureScope` --- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 5 +- .../CrashReportReceiverTests.swift | 212 ++++++++---------- DatadogRUM/Sources/Feature/RUMFeature.swift | 4 +- .../Integrations/CrashReportReceiver.swift | 46 ++-- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 8 +- 5 files changed, 121 insertions(+), 154 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 1ad4db1990..fa968805d1 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -58,6 +58,7 @@ extension CrashReportReceiver: AnyMockable { } static func mockWith( + featureScope: FeatureScope = NOPFeatureScope(), applicationID: String = .mockAny(), dateProvider: DateProvider = SystemDateProvider(), sessionSampler: Sampler = .mockKeepAll(), @@ -68,14 +69,14 @@ extension CrashReportReceiver: AnyMockable { telemetry: Telemetry = NOPTelemetry() ) -> Self { .init( + featureScope: featureScope, applicationID: applicationID, dateProvider: dateProvider, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, uuidGenerator: uuidGenerator, ciTest: ciTest, - syntheticsTest: syntheticsTest, - telemetry: telemetry + syntheticsTest: syntheticsTest ) } } diff --git a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift index 1c95a58844..582d34494d 100644 --- a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift @@ -12,67 +12,48 @@ import DatadogInternal @testable import DatadogCore class CrashReportReceiverTests: XCTestCase { - private var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional - - override func setUp() { - super.setUp() - core = PassthroughCoreMock() - } - - override func tearDown() { - core = nil - super.tearDown() - } + private let featureScope = FeatureScopeMock() func testReceiveCrashEvent() throws { // Given - let core = PassthroughCoreMock( - bypassConsentExpectation: expectation(description: "Send Event Bypass Consent"), - messageReceiver: CrashReportReceiver.mockAny() - ) + let receiver: CrashReportReceiver = .mockWith(featureScope: featureScope) // When - core.send( - message: .baggage( - key: CrashReportReceiver.MessageKeys.crash, - value: MessageBusSender.Crash( - report: DDCrashReport.mockAny(), - context: CrashContext.mockWith(lastRUMViewEvent: nil) - ) + let message: FeatureMessage = .baggage( + key: CrashReportReceiver.MessageKeys.crash, + value: MessageBusSender.Crash( + report: DDCrashReport.mockAny(), + context: CrashContext.mockWith(lastRUMViewEvent: nil) ) ) + let result = receiver.receive(message: message, from: NOPDatadogCore()) // Then - waitForExpectations(timeout: 0.5, handler: nil) - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1, "It should send error event") + XCTAssertTrue(result, "It must accept the message") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1, "It should send error event") } func testReceiveCrashAndViewEvent() throws { // Given - let core = PassthroughCoreMock( - bypassConsentExpectation: expectation(description: "Send Event Bypass Consent"), - messageReceiver: CrashReportReceiver.mockAny() - ) - + let receiver: CrashReportReceiver = .mockWith(featureScope: featureScope) let lastRUMViewEvent: RUMViewEvent = .mockRandom() // When - core.send( - message: .baggage( - key: CrashReportReceiver.MessageKeys.crash, - value: MessageBusSender.Crash( - report: DDCrashReport.mockWith(date: Date()), - context: CrashContext.mockWith( - lastRUMViewEvent: AnyCodable(lastRUMViewEvent) - ) + let message: FeatureMessage = .baggage( + key: CrashReportReceiver.MessageKeys.crash, + value: MessageBusSender.Crash( + report: DDCrashReport.mockWith(date: Date()), + context: CrashContext.mockWith( + lastRUMViewEvent: AnyCodable(lastRUMViewEvent) ) ) ) + let result = receiver.receive(message: message, from: NOPDatadogCore()) // Then - waitForExpectations(timeout: 0.5, handler: nil) - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1, "It should send error event") - XCTAssertEqual(core.events(ofType: RUMViewEvent.self).count, 1, "It should send view event") + XCTAssertTrue(result, "It must accept the message") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1, "It should send error event") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMViewEvent.self).count, 1, "It should send view event") } // MARK: - Testing Conditional Uploads @@ -92,6 +73,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: .mockRandom() // no matter BET @@ -102,13 +84,13 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 2, "It must send both RUM error and RUM view") - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1) - XCTAssertEqual(core.events(ofType: RUMViewEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten.count, 2, "It must send both RUM error and RUM view") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMViewEvent.self).count, 1) } func testGivenCrashDuringRUMSessionWithActiveViewCollectedLessThan4HoursAgoWithPreviousCrash_whenSending_itSendsNothing() throws { @@ -126,6 +108,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: .mockRandom() // no matter BET @@ -136,11 +119,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 0, "It must send no message") + XCTAssertEqual(featureScope.eventsWritten.count, 0, "It must send no message") } func testGivenCrashDuringRUMSessionWithActiveViewCollectedMoreThan4HoursAgo_whenSending_itSendsOnlyRUMError() throws { @@ -158,6 +141,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: .mockRandom() // no matter BET @@ -168,12 +152,12 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 1, "It must send only RUM error") - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must send only RUM error") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1) } func testGivenCrashDuringBackgroundRUMSessionWithNoActiveView_whenSending_itSendsBothRUMErrorAndRUMViewEvent() throws { @@ -191,6 +175,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: true // BET enabled @@ -201,13 +186,13 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 2, "It must send both RUM error and RUM view") - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1) - XCTAssertEqual(core.events(ofType: RUMViewEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten.count, 2, "It must send both RUM error and RUM view") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMViewEvent.self).count, 1) } func testGivenCrashDuringApplicationLaunch_whenSending_itSendsBothRUMErrorAndRUMViewEvent() throws { @@ -226,6 +211,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), trackBackgroundEvents: true // BET enabled ) @@ -235,13 +221,13 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 2, "It must send both RUM error and RUM view") - XCTAssertEqual(core.events(ofType: RUMCrashEvent.self).count, 1) - XCTAssertEqual(core.events(ofType: RUMViewEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten.count, 2, "It must send both RUM error and RUM view") + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMCrashEvent.self).count, 1) + XCTAssertEqual(featureScope.eventsWritten(ofType: RUMViewEvent.self).count, 1) } func testGivenCrashDuringAppLaunchAndNoSampling_whenSending_itIsDropped() throws { @@ -258,6 +244,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: .mockRejectAll() // no sampling (no session should be sent) ) @@ -267,11 +254,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 0, "Crash must not be send as it is rejected by sampler") + XCTAssertEqual(featureScope.eventsWritten.count, 0, "Crash must not be send as it is rejected by sampler") } func testGivenCrashDuringAppLaunchInBackgroundAndBETDisabled_whenSending_itIsDropped() throws { @@ -288,6 +275,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: crashDate), trackBackgroundEvents: false // BET disabled ) @@ -297,11 +285,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 0, "Crash must not be send as it happened in background and BET is disabled") + XCTAssertEqual(featureScope.eventsWritten.count, 0, "Crash must not be send as it happened in background and BET is disabled") } func testGivenCrashDuringSampledRUMSession_whenSending_itIsDropped() throws { @@ -325,6 +313,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: currentDate), sessionSampler: .mockRandom(), // no matter current session sampling trackBackgroundEvents: .mockRandom() @@ -335,11 +324,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - XCTAssertEqual(core.events.count, 0, "Crash must not be send as it the session was rejected by sampler") + XCTAssertEqual(featureScope.eventsWritten.count, 0, "Crash must not be send as it the session was rejected by sampler") } // MARK: - Testing Uploaded Data - Crashes During RUM Session With Active View @@ -358,6 +347,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: crashDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: .mockRandom() // no matter BET @@ -368,11 +358,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sendRUMViewEvent = core.events(ofType: RUMViewEvent.self)[0] + let sendRUMViewEvent = featureScope.eventsWritten(ofType: RUMViewEvent.self)[0] XCTAssertTrue( sendRUMViewEvent.application.id == lastRUMViewEvent.application.id @@ -455,6 +445,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider( using: crashDate.addingTimeInterval( .mockRandom(min: 10, max: 2 * CrashReportReceiver.Constants.viewEventAvailabilityThreshold) // simulate restarting app from 10s to 8h later @@ -469,11 +460,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sendRUMErrorEvent = core.events(ofType: RUMCrashEvent.self)[0] + let sendRUMErrorEvent = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] XCTAssertTrue( sendRUMErrorEvent.model.application.id == lastRUMViewEvent.application.id @@ -527,9 +518,7 @@ class CrashReportReceiverTests: XCTestCase { } func testGivenCrashDuringRUMSessionWithActiveViewAndOverridenSourceType_whenSendingRUMViewEvent_itSendsOverrideSourceType() throws { - core = PassthroughCoreMock( - context: .mockWith(nativeSourceOverride: "ios+il2cpp") - ) + featureScope.contextMock = .mockWith(nativeSourceOverride: "ios+il2cpp") let lastRUMViewEvent: RUMViewEvent = .mockRandomWith(crashCount: 0) // Given @@ -543,6 +532,7 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider(using: crashDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) trackBackgroundEvents: .mockRandom() // no matter BET @@ -553,11 +543,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sendRUMErrorEvent = core.events(ofType: RUMCrashEvent.self)[0] + let sendRUMErrorEvent = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] XCTAssertEqual(sendRUMErrorEvent.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") } @@ -581,14 +571,7 @@ class CrashReportReceiverTests: XCTestCase { expectViewName expectedViewName: String, expectViewURL expectedViewURL: String ) throws { - let core = PassthroughCoreMock( - messageReceiver: CrashReportReceiver.mockWith( - applicationID: randomRUMAppID, - sessionSampler: .mockKeepAll(), - trackBackgroundEvents: backgroundEventsTrackingEnabled, - uuidGenerator: DefaultRUMUUIDGenerator() - ) - ) + let featureScope = FeatureScopeMock() // Given let crashDate: Date = .mockDecember15th2019At10AMUTC() @@ -613,10 +596,12 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, applicationID: randomRUMAppID, dateProvider: RelativeDateProvider(using: crashDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) - trackBackgroundEvents: backgroundEventsTrackingEnabled + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() ) // When @@ -624,12 +609,12 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sentRUMView = core.events(ofType: RUMViewEvent.self)[0] - let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + let sentRUMView = featureScope.eventsWritten(ofType: RUMViewEvent.self)[0] + let sentRUMError = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] // Assert RUM view properties XCTAssertTrue( @@ -722,15 +707,8 @@ class CrashReportReceiverTests: XCTestCase { backgroundEventsTrackingEnabled: Bool ) throws { let mockApplicationId: String = .mockRandom(among: .alphanumerics) - let core = PassthroughCoreMock( - context: .mockWith(nativeSourceOverride: "ios+il2cpp"), - messageReceiver: CrashReportReceiver.mockWith( - applicationID: mockApplicationId, - sessionSampler: .mockKeepAll(), - trackBackgroundEvents: backgroundEventsTrackingEnabled, - uuidGenerator: DefaultRUMUUIDGenerator() - ) - ) + let featureScope = FeatureScopeMock() + featureScope.contextMock = .mockWith(nativeSourceOverride: "ios+il2cpp") // Given let crashDate: Date = .mockDecember15th2019At10AMUTC() @@ -755,10 +733,12 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( - applicationID: .mockRandom(among: .alphanumerics), + featureScope: featureScope, + applicationID: mockApplicationId, dateProvider: RelativeDateProvider(using: crashDate), sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter sampling (as previous session was sampled) - trackBackgroundEvents: backgroundEventsTrackingEnabled + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() ) // When @@ -766,11 +746,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + let sentRUMError = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] XCTAssertEqual(sentRUMError.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") } @@ -802,14 +782,7 @@ class CrashReportReceiverTests: XCTestCase { expectViewName expectedViewName: String, expectViewURL expectedViewURL: String ) throws { - let core = PassthroughCoreMock( - messageReceiver: CrashReportReceiver.mockWith( - applicationID: randomRUMAppID, - sessionSampler: .mockKeepAll(), - trackBackgroundEvents: backgroundEventsTrackingEnabled, - uuidGenerator: DefaultRUMUUIDGenerator() - ) - ) + let featureScope = FeatureScopeMock() // Given let crashDate: Date = .mockDecember15th2019At10AMUTC() @@ -831,9 +804,11 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, applicationID: randomRUMAppID, dateProvider: RelativeDateProvider(using: crashDate), - trackBackgroundEvents: backgroundEventsTrackingEnabled + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() ) // When @@ -841,12 +816,12 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sentRUMView = core.events(ofType: RUMViewEvent.self)[0] - let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + let sentRUMView = featureScope.eventsWritten(ofType: RUMViewEvent.self)[0] + let sentRUMError = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] // Assert RUM view properties XCTAssertTrue( @@ -930,15 +905,8 @@ class CrashReportReceiverTests: XCTestCase { expectViewName expectedViewName: String, expectViewURL expectedViewURL: String ) throws { - let core = PassthroughCoreMock( - context: .mockWith(nativeSourceOverride: "ios+il2cpp"), - messageReceiver: CrashReportReceiver.mockWith( - applicationID: .mockRandom(among: .alphanumerics), - sessionSampler: .mockKeepAll(), - trackBackgroundEvents: backgroundEventsTrackingEnabled, - uuidGenerator: DefaultRUMUUIDGenerator() - ) - ) + let featureScope = FeatureScopeMock() + featureScope.contextMock = .mockWith(nativeSourceOverride: "ios+il2cpp") // Given let crashDate: Date = .mockDecember15th2019At10AMUTC() @@ -960,9 +928,11 @@ class CrashReportReceiverTests: XCTestCase { ) let receiver: CrashReportReceiver = .mockWith( + featureScope: featureScope, applicationID: .mockRandom(), dateProvider: RelativeDateProvider(using: crashDate), - trackBackgroundEvents: backgroundEventsTrackingEnabled + trackBackgroundEvents: backgroundEventsTrackingEnabled, + uuidGenerator: DefaultRUMUUIDGenerator() ) // When @@ -970,11 +940,11 @@ class CrashReportReceiverTests: XCTestCase { receiver.receive(message: .baggage( key: MessageBusSender.MessageKeys.crash, value: MessageBusSender.Crash(report: crashReport, context: crashContext) - ), from: core) + ), from: NOPDatadogCore()) ) // Then - let sentRUMError = core.events(ofType: RUMCrashEvent.self)[0] + let sentRUMError = featureScope.eventsWritten(ofType: RUMCrashEvent.self)[0] XCTAssertEqual(sentRUMError.model.error.sourceType, .iosIl2cpp, "Must send overridden sourceType") } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index ccb3fd17ab..bf1e462ce9 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -108,6 +108,7 @@ internal final class RUMFeature: DatadogRemoteFeature { viewCache: dependencies.viewCache ), CrashReportReceiver( + featureScope: featureScope, applicationID: configuration.applicationID, dateProvider: configuration.dateProvider, sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate), @@ -120,8 +121,7 @@ internal final class RUMFeature: DatadogRemoteFeature { } else { return nil } - }(), - telemetry: core.telemetry + }() ) ) diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 373c80be26..d0a08700a6 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -100,6 +100,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let realDateNow: Date } + /// RUM feature scope. + let featureScope: FeatureScope let applicationID: String let dateProvider: DateProvider let sessionSampler: Sampler @@ -109,21 +111,20 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let ciTest: RUMCITest? /// Integration with Synthetics tests. It contains the Synthetics test context when active. let syntheticsTest: RUMSyntheticsTest? - /// Telemetry interface. - let telemetry: Telemetry // MARK: - Initialization init( + featureScope: FeatureScope, applicationID: String, dateProvider: DateProvider, sessionSampler: Sampler, trackBackgroundEvents: Bool, uuidGenerator: RUMUUIDGenerator, ciTest: RUMCITest?, - syntheticsTest: RUMSyntheticsTest?, - telemetry: Telemetry + syntheticsTest: RUMSyntheticsTest? ) { + self.featureScope = featureScope self.applicationID = applicationID self.dateProvider = dateProvider self.sessionSampler = sessionSampler @@ -131,7 +132,6 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { self.uuidGenerator = uuidGenerator self.ciTest = ciTest self.syntheticsTest = syntheticsTest - self.telemetry = telemetry } func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { @@ -140,16 +140,16 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { return false } - return send(report: crash.report, with: crash.context, to: core) + return send(report: crash.report, with: crash.context) } catch { - core.telemetry + featureScope.telemetry .error("Fails to decode crash from RUM", error: error) } return false } - private func send(report: CrashReport, with context: CrashContext, to core: DatadogCoreProtocol) -> Bool { + private func send(report: CrashReport, with context: CrashContext) -> Bool { // The `crashReport.crashDate` uses system `Date` collected at the moment of crash, so we need to adjust it // to the server time before processing. Following use of the current correction is not ideal (it's not the correction // from the moment of crash), but this is the best approximation we can get. @@ -168,17 +168,16 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { sendCrashReportLinkedToLastViewInPreviousSession( report, lastRUMViewEventInPreviousSession: lastRUMViewEvent, - using: adjustedCrashTimings, - to: core + using: adjustedCrashTimings ) } else { DD.logger.debug("There was a crash in previous session, but it is ignored due to another crash already present in the last view.") return false } } else if let lastRUMSessionState = context.lastRUMSessionState { - sendCrashReportToPreviousSession(report, crashContext: context, lastRUMSessionStateInPreviousSession: lastRUMSessionState, using: adjustedCrashTimings, to: core) + sendCrashReportToPreviousSession(report, crashContext: context, lastRUMSessionStateInPreviousSession: lastRUMSessionState, using: adjustedCrashTimings) } else if sessionSampler.sample() { // before producing a new RUM session, we must consider sampling - sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings, to: core) + sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings) } else { DD.logger.debug("There was a crash in previous session, but it is ignored due to sampling.") return false @@ -192,16 +191,15 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { private func sendCrashReportLinkedToLastViewInPreviousSession( _ crashReport: CrashReport, lastRUMViewEventInPreviousSession lastRUMViewEvent: RUMViewEvent, - using crashTimings: AdjustedCrashTimings, - to core: DatadogCoreProtocol + using crashTimings: AdjustedCrashTimings ) { if crashTimings.realDateNow.timeIntervalSince(crashTimings.realCrashDate) < Constants.viewEventAvailabilityThreshold { - send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings.realCrashDate, to: core) + send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings.realCrashDate) } else { // We know it is too late for sending RUM view to previous RUM session as it is now stale on backend. // To avoid inconsistency, we only send the RUM error. DD.logger.debug("Sending crash as RUM error.") - core.scope(for: RUMFeature.self).eventWriteContext(bypassConsent: true) { context, writer in + featureScope.eventWriteContext(bypassConsent: true) { context, writer in let rumError = createRUMError(from: crashReport, and: lastRUMViewEvent, crashDate: crashTimings.realCrashDate, sourceType: context.nativeSourceOverride) writer.write(value: rumError) } @@ -215,8 +213,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { _ crashReport: CrashReport, crashContext: CrashContext, lastRUMSessionStateInPreviousSession lastRUMSessionState: RUMSessionState, - using crashTimings: AdjustedCrashTimings, - to core: DatadogCoreProtocol + using crashTimings: AdjustedCrashTimings ) { let handlingRule = RUMOffViewEventsHandlingRule( sessionState: lastRUMSessionState, @@ -256,7 +253,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } if let newRUMView = newRUMView { - send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate, to: core) + send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate) } } @@ -265,8 +262,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { private func sendCrashReportToNewSession( _ crashReport: CrashReport, crashContext: CrashContext, - using crashTimings: AdjustedCrashTimings, - to core: DatadogCoreProtocol + using crashTimings: AdjustedCrashTimings ) { // We can ignore `sessionState` for building the rule as we can assume there was no session sent - otherwise, // the `lastRUMSessionState` would have been set in `CrashContext` and we could be sending the crash to previous session @@ -310,18 +306,18 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } if let newRUMView = newRUMView { - send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate, to: core) + send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate) } } /// Sends given `CrashReport` by linking it to given `rumView` and updating view counts accordingly. - private func send(crashReport: CrashReport, to rumView: RUMViewEvent, using realCrashDate: Date, to core: DatadogCoreProtocol) { + private func send(crashReport: CrashReport, to rumView: RUMViewEvent, using realCrashDate: Date) { DD.logger.debug("Updating RUM view with crash report.") let updatedRUMView = updateRUMViewWithNewError(rumView, crashDate: realCrashDate) // crash reporting is considering the user consent from previous session, if an event reached // the message bus it means that consent was granted and we can safely bypass current consent. - core.scope(for: RUMFeature.self).eventWriteContext(bypassConsent: true) { context, writer in + featureScope.eventWriteContext(bypassConsent: true) { context, writer in let rumError = createRUMError(from: crashReport, and: updatedRUMView, crashDate: realCrashDate, sourceType: context.nativeSourceOverride) writer.write(value: rumError) @@ -537,7 +533,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { container: nil, context: nil, date: startDate.timeIntervalSince1970.toInt64Milliseconds, - device: .init(device: context.device, telemetry: telemetry), + device: .init(device: context.device, telemetry: featureScope.telemetry), display: nil, // RUMM-2197: In very rare cases, the OS info computed below might not be exactly the one // that the app crashed on. This would correspond to a scenario when the device OS was upgraded diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 64a8f3e332..e4007d4a0f 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -31,24 +31,24 @@ extension CrashReportReceiver: AnyMockable { } static func mockWith( + featureScope: FeatureScope = NOPFeatureScope(), applicationID: String = .mockAny(), dateProvider: DateProvider = SystemDateProvider(), sessionSampler: Sampler = .mockKeepAll(), trackBackgroundEvents: Bool = true, uuidGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), ciTest: RUMCITest? = nil, - syntheticsTest: RUMSyntheticsTest? = nil, - telemetry: Telemetry = NOPTelemetry() + syntheticsTest: RUMSyntheticsTest? = nil ) -> Self { .init( + featureScope: featureScope, applicationID: applicationID, dateProvider: dateProvider, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, uuidGenerator: uuidGenerator, ciTest: ciTest, - syntheticsTest: syntheticsTest, - telemetry: telemetry + syntheticsTest: syntheticsTest ) } } From 4989babbbf278dc4fe61cf4aea46550f314b8e72 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 13:57:18 +0100 Subject: [PATCH 4/8] RUM-3461 Refactor `ErrorMessageReceiver` to depend on `FeatureScope` --- DatadogRUM/Sources/Feature/RUMFeature.swift | 5 ++++- DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift | 4 +++- .../Tests/Integrations/ErrorMessageReceiverTests.swift | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index bf1e462ce9..475c601df6 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -100,7 +100,10 @@ internal final class RUMFeature: DatadogRemoteFeature { configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate), metricsExtraSampler: Sampler(samplingRate: configuration.metricsTelemetrySampleRate) ), - ErrorMessageReceiver(monitor: monitor), + ErrorMessageReceiver( + featureScope: featureScope, + monitor: monitor + ), WebViewEventReceiver( featureScope: featureScope, dateProvider: configuration.dateProvider, diff --git a/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift b/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift index ccc166da38..5f0f1c782e 100644 --- a/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift +++ b/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift @@ -25,6 +25,8 @@ internal struct ErrorMessageReceiver: FeatureMessageReceiver { let attributes: [String: AnyCodable]? } + /// RUM feature scope. + let featureScope: FeatureScope let monitor: Monitor /// Adds RUM Error with given message and stack to current RUM View. @@ -46,7 +48,7 @@ internal struct ErrorMessageReceiver: FeatureMessageReceiver { return true } catch { - core.telemetry + featureScope.telemetry .error("Fails to decode error message", error: error) return false } diff --git a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift index dde67ee65a..f9d588aa04 100644 --- a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift @@ -16,6 +16,7 @@ class ErrorMessageReceiverTests: XCTestCase { override func setUp() { receiver = ErrorMessageReceiver( + featureScope: featureScope, monitor: Monitor( featureScope: featureScope, dependencies: .mockAny(), From c83a719c97b66c3ce4d30ae636db24e57f9d0686 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 17:15:50 +0100 Subject: [PATCH 5/8] RUM-3461 Refactor `TelemetryReceiver` to depend on `FeatureScope` --- DatadogRUM/Sources/Feature/RUMFeature.swift | 1 + .../Integrations/TelemetryReceiver.swift | 37 +- .../Integrations/TelemetryReceiverTests.swift | 339 +++++++++--------- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 2 + 4 files changed, 186 insertions(+), 193 deletions(-) diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 475c601df6..db58524093 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -95,6 +95,7 @@ internal final class RUMFeature: DatadogRemoteFeature { ) self.messageReceiver = CombinedFeatureMessageReceiver( TelemetryReceiver( + featureScope: featureScope, dateProvider: configuration.dateProvider, sampler: Sampler(samplingRate: configuration.telemetrySampleRate), configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate), diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 057ebf9cba..d0710b18a4 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -11,6 +11,8 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// Maximum number of telemetry events allowed per RUM sessions. static let maxEventsPerSessions: Int = 100 + /// RUM feature scope. + let featureScope: FeatureScope let dateProvider: DateProvider let sampler: Sampler @@ -33,16 +35,19 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// Creates a RUM Telemetry instance. /// /// - Parameters: + /// - featureScope: RUM feature scope. /// - dateProvider: Current device time provider. /// - sampler: Telemetry events sampler. /// - configurationExtraSampler: Extra sampler for configuration events (applied on top of `sampler`). /// - metricsExtraSampler: Extra sampler for metric events (applied on top of `sampler`). init( + featureScope: FeatureScope, dateProvider: DateProvider, sampler: Sampler, configurationExtraSampler: Sampler, metricsExtraSampler: Sampler ) { + self.featureScope = featureScope self.dateProvider = dateProvider self.sampler = sampler self.configurationExtraSampler = configurationExtraSampler @@ -62,23 +67,23 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { return false } - return receive(telemetry: telemetry, from: core) + return receive(telemetry: telemetry) } /// Receives a Telemetry message from the bus. /// /// - Parameter telemetry: The telemetry message to consume. /// - Returns: Always `true`. - func receive(telemetry: TelemetryMessage, from core: DatadogCoreProtocol) -> Bool { + private func receive(telemetry: TelemetryMessage) -> Bool { switch telemetry { case let .debug(id, message, attributes): - debug(id: id, message: message, attributes: attributes, in: core) + debug(id: id, message: message, attributes: attributes) case let .error(id, message, kind, stack): - error(id: id, message: message, kind: kind, stack: stack, in: core) + error(id: id, message: message, kind: kind, stack: stack) case .configuration(let configuration): - send(configuration: configuration, in: core) + send(configuration: configuration) case let .metric(name, attributes): - metric(name: name, attributes: attributes, in: core) + metric(name: name, attributes: attributes) } return true @@ -94,10 +99,10 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - id: Identity of the debug log, this can be used to prevent duplicates. /// - message: The debug message. /// - attributes: Custom attributes attached to the log (optional). - private func debug(id: String, message: String, attributes: [String: Encodable]?, in core: DatadogCoreProtocol) { + private func debug(id: String, message: String, attributes: [String: Encodable]?) { let date = dateProvider.now - record(event: id, in: core) { context, writer in + record(event: id) { context, writer in let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) let event = TelemetryDebugEvent( @@ -132,10 +137,10 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - message: Body of the log /// - kind: The error type or kind (or code in some cases). /// - stack: The stack trace or the complementary information about the error. - private func error(id: String, message: String, kind: String?, stack: String?, in core: DatadogCoreProtocol) { + private func error(id: String, message: String, kind: String?, stack: String?) { let date = dateProvider.now - record(event: id, in: core) { context, writer in + record(event: id) { context, writer in let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) let event = TelemetryErrorEvent( @@ -162,14 +167,14 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// configuration for lazy initialization of the SDK. /// /// - Parameter configuration: The SDK configuration. - private func send(configuration: DatadogInternal.ConfigurationTelemetry, in core: DatadogCoreProtocol) { + private func send(configuration: DatadogInternal.ConfigurationTelemetry) { guard configurationExtraSampler.sample() else { return } let date = dateProvider.now - self.record(event: "_dd.configuration", in: core) { context, writer in + self.record(event: "_dd.configuration") { context, writer in let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) let event = TelemetryConfigurationEvent( @@ -190,14 +195,14 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { } } - private func metric(name: String, attributes: [String: Encodable], in core: DatadogCoreProtocol) { + private func metric(name: String, attributes: [String: Encodable]) { guard metricsExtraSampler.sample() else { return } let date = dateProvider.now - record(event: nil, in: core) { context, writer in + record(event: nil) { context, writer in let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) let event = TelemetryDebugEvent( @@ -221,12 +226,12 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { } } - private func record(event id: String?, in core: DatadogCoreProtocol, operation: @escaping (DatadogContext, Writer) -> Void) { + private func record(event id: String?, operation: @escaping (DatadogContext, Writer) -> Void) { guard sampler.sample() else { return } - core.scope(for: RUMFeature.self).eventWriteContext { context, writer in + featureScope.eventWriteContext { context, writer in // reset recorded events on session renewal let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index abeba5f9ea..6318580053 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -7,68 +7,79 @@ import XCTest import TestUtilities import DatadogInternal - @testable import DatadogRUM -class TelemetryReceiverTests: XCTestCase { - private var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional - - override func setUp() { - super.setUp() - core = PassthroughCoreMock( - context: .mockWith( - version: .mockRandom(), - source: .mockAnySource(), - sdkVersion: .mockRandom() - ) - ) +/// Utility to access the convenience methods defined in `Telemetry` protocol extension while sending telemetry to tested receiver. +private struct TelemetryMock: Telemetry { + let receiver: TelemetryReceiver + + init(with receiver: TelemetryReceiver) { + self.receiver = receiver } - override func tearDown() { - core = nil - super.tearDown() + func send(telemetry: TelemetryMessage) { + let result = receiver.receive(message: .telemetry(telemetry), from: NOPDatadogCore()) + XCTAssertTrue(result, "It must accept every message") } +} + +class TelemetryReceiverTests: XCTestCase { + private let featureScope = FeatureScopeMock() // MARK: - Sending Telemetry events func testSendTelemetryDebug() { + featureScope.contextMock = .mockWith( + version: "app-version", + source: "flutter", + sdkVersion: "sdk-version" + ) + // Given - core.messageReceiver = TelemetryReceiver.mockWith( + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider( using: .init(timeIntervalSince1970: 0) ) ) // When - core.telemetry.debug("Hello world!", attributes: ["foo": 42]) + TelemetryMock(with: receiver).debug("Hello world!", attributes: ["foo": 42]) // Then - let event = core.events(ofType: TelemetryDebugEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertEqual(event?.date, 0) - XCTAssertEqual(event?.version, core.context.sdkVersion) + XCTAssertEqual(event?.version, "sdk-version") XCTAssertEqual(event?.service, "dd-sdk-ios") - XCTAssertEqual(event?.source.rawValue, core.context.source) + XCTAssertEqual(event?.source, .flutter) XCTAssertEqual(event?.telemetry.message, "Hello world!") XCTAssertEqual(event?.telemetry.telemetryInfo as? [String: Int], ["foo": 42]) } func testSendTelemetryError() { + featureScope.contextMock = .mockWith( + version: "app-version", + source: "ios", + sdkVersion: "sdk-version" + ) + // Given - core.messageReceiver = TelemetryReceiver.mockWith( + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, dateProvider: RelativeDateProvider( using: .init(timeIntervalSince1970: 0) ) ) // When - core.telemetry.error("Oops", kind: "OutOfMemory", stack: "a\nhay\nneedle\nstack") + TelemetryMock(with: receiver).error("Oops", kind: "OutOfMemory", stack: "a\nhay\nneedle\nstack") // Then - let event = core.events(ofType: TelemetryErrorEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).first XCTAssertEqual(event?.date, 0) - XCTAssertEqual(event?.version, core.context.sdkVersion) + XCTAssertEqual(event?.version, "sdk-version") XCTAssertEqual(event?.service, "dd-sdk-ios") - XCTAssertEqual(event?.source.rawValue, core.context.source) + XCTAssertEqual(event?.source, .ios) XCTAssertEqual(event?.telemetry.message, "Oops") XCTAssertEqual(event?.telemetry.error?.kind, "OutOfMemory") XCTAssertEqual(event?.telemetry.error?.stack, "a\nhay\nneedle\nstack") @@ -76,93 +87,68 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetryDebug_withRUMContext() { // Given - core.messageReceiver = TelemetryReceiver.mockAny() - let applicationId: String = .mockRandom() - let sessionId: String = .mockRandom() - let viewId: String = .mockRandom() - let actionId: String = .mockRandom() - - core.set( - baggage: RUMCoreContext( - applicationID: applicationId, - sessionID: sessionId, - viewID: viewId, - userActionID: actionId, - viewServerTimeOffset: .mockRandom() - ), - forKey: "rum" - ) + let rumContext: RUMCoreContext = .mockRandom() + featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - core.telemetry.debug("telemetry debug", attributes: ["foo": 42]) + TelemetryMock(with: receiver).debug("telemetry debug", attributes: ["foo": 42]) // Then - let event = core.events(ofType: TelemetryDebugEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertEqual(event?.telemetry.message, "telemetry debug") - XCTAssertEqual(event?.application?.id, applicationId) - XCTAssertEqual(event?.session?.id, sessionId) - XCTAssertEqual(event?.view?.id, viewId) - XCTAssertEqual(event?.action?.id, actionId) + XCTAssertEqual(event?.application?.id, rumContext.applicationID) + XCTAssertEqual(event?.session?.id, rumContext.sessionID) + XCTAssertEqual(event?.view?.id, rumContext.viewID) + XCTAssertEqual(event?.action?.id, rumContext.userActionID) XCTAssertEqual(event?.telemetry.telemetryInfo as? [String: Int], ["foo": 42]) } func testSendTelemetryError_withRUMContext() throws { // Given - core.messageReceiver = TelemetryReceiver.mockAny() - let applicationId: String = .mockRandom() - let sessionId: String = .mockRandom() - let viewId: String = .mockRandom() - let actionId: String = .mockRandom() - - core.set( - baggage: RUMCoreContext( - applicationID: applicationId, - sessionID: sessionId, - viewID: viewId, - userActionID: actionId, - viewServerTimeOffset: .mockRandom() - ), - forKey: "rum" - ) + let rumContext: RUMCoreContext = .mockRandom() + featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - core.telemetry.error("telemetry error") + TelemetryMock(with: receiver).error("telemetry error") // Then - let event = core.events(ofType: TelemetryErrorEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).first XCTAssertEqual(event?.telemetry.message, "telemetry error") - XCTAssertEqual(event?.application?.id, applicationId) - XCTAssertEqual(event?.session?.id, sessionId) - XCTAssertEqual(event?.view?.id, viewId) - XCTAssertEqual(event?.action?.id, actionId) + XCTAssertEqual(event?.application?.id, rumContext.applicationID) + XCTAssertEqual(event?.session?.id, rumContext.sessionID) + XCTAssertEqual(event?.view?.id, rumContext.viewID) + XCTAssertEqual(event?.action?.id, rumContext.userActionID) } func testSendTelemetry_discardDuplicates() throws { // Given - core.messageReceiver = TelemetryReceiver.mockAny() + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let telemetry = TelemetryMock(with: receiver) // When - core.telemetry.debug(id: "0", message: "telemetry debug 0") - core.telemetry.error(id: "0", message: "telemetry debug 1", kind: nil, stack: nil) - core.telemetry.debug(id: "0", message: "telemetry debug 2") - core.telemetry.debug(id: "1", message: "telemetry debug 3") + telemetry.debug(id: "0", message: "telemetry debug 0") + telemetry.error(id: "0", message: "telemetry debug 1", kind: nil, stack: nil) + telemetry.debug(id: "0", message: "telemetry debug 2") + telemetry.debug(id: "1", message: "telemetry debug 3") for _ in 0...10 { // telemetry id is composed of the file, line number, and message - core.telemetry.debug("telemetry debug 4") + telemetry.debug("telemetry debug 4") } for index in 5...10 { // telemetry id is composed of the file, line number, and message - core.telemetry.debug("telemetry debug \(index)") + telemetry.debug("telemetry debug \(index)") } - core.telemetry.debug("telemetry debug 11") + telemetry.debug("telemetry debug 11") // Then - let events = core.events(ofType: TelemetryDebugEvent.self) + let events = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self) XCTAssertEqual(events.count, 10) - XCTAssertTrue(core.events(ofType: TelemetryErrorEvent.self).isEmpty) + XCTAssertTrue(featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).isEmpty) XCTAssertEqual(events[0].telemetry.message, "telemetry debug 0") XCTAssertEqual(events[1].telemetry.message, "telemetry debug 3") XCTAssertEqual(events[2].telemetry.message, "telemetry debug 4") @@ -172,124 +158,135 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetry_toSessionLimit() throws { // Given - core.messageReceiver = TelemetryReceiver.mockWith(sampler: .mockKeepAll()) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sampler: .mockKeepAll()) + let telemetry = TelemetryMock(with: receiver) // When // sends 101 telemetry events for index in 0..<(TelemetryReceiver.maxEventsPerSessions * 2) { // swiftlint:disable opening_brace oneOf([ - { self.core.telemetry.debug(id: "\(index)", message: .mockAny()) }, - { self.core.telemetry.error(id: "\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) }, - { self.core.telemetry.metric(name: .mockAny(), attributes: [:]) } + { telemetry.debug(id: "\(index)", message: .mockAny()) }, + { telemetry.error(id: "\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) }, + { telemetry.metric(name: .mockAny(), attributes: [:]) } ]) // swiftlint:enable opening_brace } // Then - let debugEvents = core.events(ofType: TelemetryDebugEvent.self) - let errorEvents = core.events(ofType: TelemetryErrorEvent.self) + let debugEvents = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self) + let errorEvents = featureScope.eventsWritten(ofType: TelemetryErrorEvent.self) XCTAssertEqual(debugEvents.count + errorEvents.count, 100) } func testSampledTelemetry_rejectAll() throws { // Given - core.messageReceiver = TelemetryReceiver.mockWith(sampler: .mockRejectAll()) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sampler: .mockRejectAll()) + let telemetry = TelemetryMock(with: receiver) // When // sends 10 telemetry events for index in 0..<10 { // swiftlint:disable opening_brace oneOf([ - { self.core.telemetry.debug(id: "debug-\(index)", message: .mockAny()) }, - { self.core.telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) }, - { self.core.telemetry.configuration(batchSize: .mockAny()) }, - { self.core.telemetry.metric(name: .mockAny(), attributes: [:]) } + { telemetry.debug(id: "debug-\(index)", message: .mockAny()) }, + { telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) }, + { telemetry.configuration(batchSize: .mockAny()) }, + { telemetry.metric(name: .mockAny(), attributes: [:]) } ]) // swiftlint:enable opening_brace } // Then - let events = core.events(ofType: TelemetryDebugEvent.self) + let events = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self) XCTAssertEqual(events.count, 0) } func testSampledTelemetry_rejectAllConfiguration() throws { // Given - core.messageReceiver = TelemetryReceiver.mockWith( + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, sampler: .mockKeepAll(), configurationExtraSampler: .mockRejectAll() ) + let telemetry = TelemetryMock(with: receiver) // When for index in 0..<10 { - core.telemetry.debug(id: "debug-\(index)", message: .mockAny()) - core.telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) - core.telemetry.metric(name: .mockAny(), attributes: [:]) - core.telemetry.configuration(batchSize: .mockAny()) + telemetry.debug(id: "debug-\(index)", message: .mockAny()) + telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) + telemetry.metric(name: .mockAny(), attributes: [:]) + telemetry.configuration(batchSize: .mockAny()) } // Then - XCTAssertEqual(core.events(ofType: TelemetryDebugEvent.self).count, 20, "It should keep 10 debug events and 10 metrics") - XCTAssertEqual(core.events(ofType: TelemetryErrorEvent.self).count, 10, "It should keep 10 error events") - XCTAssertTrue(core.events(ofType: TelemetryConfigurationEvent.self).isEmpty, "It should reject all configuration events") + XCTAssertEqual(featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).count, 20, "It should keep 10 debug events and 10 metrics") + XCTAssertEqual(featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).count, 10, "It should keep 10 error events") + XCTAssertTrue(featureScope.eventsWritten(ofType: TelemetryConfigurationEvent.self).isEmpty, "It should reject all configuration events") } func testSampledTelemetry_rejectAllMetrics() throws { // Given - core.messageReceiver = TelemetryReceiver.mockWith( + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, sampler: .mockKeepAll(), metricsExtraSampler: .mockRejectAll() ) + let telemetry = TelemetryMock(with: receiver) // When for index in 0..<10 { - core.telemetry.debug(id: "debug-\(index)", message: .mockAny()) - core.telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) - core.telemetry.metric(name: .mockAny(), attributes: [:]) - core.telemetry.configuration(batchSize: .mockAny()) + telemetry.debug(id: "debug-\(index)", message: .mockAny()) + telemetry.error(id: "error-\(index)", message: .mockAny(), kind: .mockAny(), stack: .mockAny()) + telemetry.metric(name: .mockAny(), attributes: [:]) + telemetry.configuration(batchSize: .mockAny()) } // Then - XCTAssertEqual(core.events(ofType: TelemetryDebugEvent.self).count, 10, "It should keep 10 debug events but no metrics") - XCTAssertEqual(core.events(ofType: TelemetryErrorEvent.self).count, 10, "It should keep 10 error events") - XCTAssertEqual(core.events(ofType: TelemetryConfigurationEvent.self).count, 1, "It should keep 1 configuration event") + XCTAssertEqual(featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).count, 10, "It should keep 10 debug events but no metrics") + XCTAssertEqual(featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).count, 10, "It should keep 10 error events") + XCTAssertEqual(featureScope.eventsWritten(ofType: TelemetryConfigurationEvent.self).count, 1, "It should keep 1 configuration event") } func testSendTelemetry_resetAfterSessionExpire() throws { // Given - core.messageReceiver = TelemetryReceiver.mockAny() + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let telemetry = TelemetryMock(with: receiver) let applicationId: String = .mockRandom() - core.set(baggage: [ + featureScope.contextMock.baggages[RUMFeature.name] = FeatureBaggage([ RUMContextAttributes.IDs.applicationID: applicationId, RUMContextAttributes.IDs.sessionID: String.mockRandom() - ], forKey: "rum") + ]) + telemetry.debug(id: "0", message: "telemetry debug") // When - core.telemetry.debug(id: "0", message: "telemetry debug") - - core.set(baggage: [ + featureScope.contextMock.baggages[RUMFeature.name] = FeatureBaggage([ RUMContextAttributes.IDs.applicationID: applicationId, RUMContextAttributes.IDs.sessionID: String.mockRandom() - ], forKey: "rum") - - core.telemetry.debug(id: "0", message: "telemetry debug") + ]) + telemetry.debug(id: "0", message: "telemetry debug") // Then - let events = core.events(ofType: TelemetryDebugEvent.self) - XCTAssertEqual(events.count, 2) + let events = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self) + XCTAssertEqual(events.count, 2, "It should record two telemetries of the same ID as they belong to different session ID") } // MARK: - Configuration Telemetry Events func testSendTelemetryConfiguration() { + featureScope.contextMock = .mockWith( + version: "app-version", + source: "unity", + sdkVersion: "sdk-version" + ) + // Given - core.messageReceiver = TelemetryReceiver.mockWith( - dateProvider: RelativeDateProvider( - using: .init(timeIntervalSince1970: 0) - ) + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, + dateProvider: RelativeDateProvider(using: .init(timeIntervalSince1970: 0)) ) + let telemetry = TelemetryMock(with: receiver) let backgroundTasksEnabled: Bool? = .mockRandom() let batchProcessingLevel: Int64? = .mockRandom() @@ -318,7 +315,7 @@ class TelemetryReceiverTests: XCTestCase { let useTracing: Bool? = .mockRandom() // When - core.telemetry.configuration( + telemetry.configuration( backgroundTasksEnabled: backgroundTasksEnabled, batchProcessingLevel: batchProcessingLevel, batchSize: batchSize, @@ -347,11 +344,11 @@ class TelemetryReceiverTests: XCTestCase { ) // Then - let event = core.events(ofType: TelemetryConfigurationEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryConfigurationEvent.self).first XCTAssertEqual(event?.date, 0) - XCTAssertEqual(event?.version, core.context.sdkVersion) + XCTAssertEqual(event?.version, "sdk-version") XCTAssertEqual(event?.service, "dd-sdk-ios") - XCTAssertEqual(event?.source.rawValue, core.context.source) + XCTAssertEqual(event?.source, .unity) XCTAssertEqual(event?.telemetry.configuration.backgroundTasksEnabled, backgroundTasksEnabled) XCTAssertEqual(event?.telemetry.configuration.batchProcessingLevel, batchProcessingLevel) XCTAssertEqual(event?.telemetry.configuration.batchSize, batchSize) @@ -382,82 +379,68 @@ class TelemetryReceiverTests: XCTestCase { // MARK: - Metrics Telemetry Events func testSendTelemetryMetric() { + featureScope.contextMock = .mockWith( + version: "app-version", + source: "react-native", + sdkVersion: "sdk-version" + ) + // Given - core.messageReceiver = TelemetryReceiver.mockWith( - dateProvider: RelativeDateProvider( - using: .init(timeIntervalSince1970: 0) - ) + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, + dateProvider: RelativeDateProvider(using: .init(timeIntervalSince1970: 0)) ) // When let randomName: String = .mockRandom() let randomAttributes = mockRandomAttributes() - core.telemetry.metric(name: randomName, attributes: randomAttributes) + TelemetryMock(with: receiver).metric(name: randomName, attributes: randomAttributes) // Then - let event = core.events(ofType: TelemetryDebugEvent.self).first + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertEqual(event?.date, 0) - XCTAssertEqual(event?.version, core.context.sdkVersion) + XCTAssertEqual(event?.version, "sdk-version") XCTAssertEqual(event?.service, "dd-sdk-ios") - XCTAssertEqual(event?.source.rawValue, core.context.source) + XCTAssertEqual(event?.source, .reactNative) XCTAssertEqual(event?.telemetry.message, "[Mobile Metric] \(randomName)") DDAssertReflectionEqual(event?.telemetry.telemetryInfo, randomAttributes) } func testSendTelemetryMetricWithRUMContext() { // Given - core.messageReceiver = TelemetryReceiver.mockWith( - dateProvider: RelativeDateProvider( - using: .init(timeIntervalSince1970: 0) - ) - ) - - let applicationId: String = .mockRandom() - let sessionId: String = .mockRandom() - let viewId: String = .mockRandom() - let actionId: String = .mockRandom() - - core.set( - baggage: RUMCoreContext( - applicationID: applicationId, - sessionID: sessionId, - viewID: viewId, - userActionID: actionId, - viewServerTimeOffset: .mockRandom() - ), - forKey: "rum" - ) + let rumContext: RUMCoreContext = .mockRandom() + featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - core.telemetry.metric(name: .mockRandom(), attributes: mockRandomAttributes()) + TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: mockRandomAttributes()) // Then - let event = core.events(ofType: TelemetryDebugEvent.self).first - XCTAssertEqual(event?.application?.id, applicationId) - XCTAssertEqual(event?.session?.id, sessionId) - XCTAssertEqual(event?.view?.id, viewId) - XCTAssertEqual(event?.action?.id, actionId) + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first + XCTAssertEqual(event?.application?.id, rumContext.applicationID) + XCTAssertEqual(event?.session?.id, rumContext.sessionID) + XCTAssertEqual(event?.view?.id, rumContext.viewID) + XCTAssertEqual(event?.action?.id, rumContext.userActionID) } func testMethodCallTelemetryPropagetsAllData() throws { // Given - core.messageReceiver = TelemetryReceiver.mockWith( - dateProvider: RelativeDateProvider( - using: .init(timeIntervalSince1970: 0) - ) - ) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let telemetry = TelemetryMock(with: receiver) + // When let operationName = String.mockRandom() let callerClass = String.mockRandom() let isSuccessful = Bool.random() - let trace = core.telemetry.startMethodCalled( + let trace = telemetry.startMethodCalled( operationName: operationName, callerClass: callerClass, samplingRate: 100 ) - core.telemetry.stopMethodCalled(trace, isSuccessful: isSuccessful) + telemetry.stopMethodCalled(trace, isSuccessful: isSuccessful) - let event = core.events(ofType: TelemetryDebugEvent.self).first + // Then + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertEqual(event?.telemetry.message, "[Mobile Metric] Method Called") XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[BasicMetric.typeKey] as? String), MethodCalledMetric.typeValue) XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.operationName] as? String), operationName) @@ -476,20 +459,22 @@ class TelemetryReceiverTests: XCTestCase { func testMethodCallTelemetryDroppedWhenSampledOut() { // Given - core.messageReceiver = TelemetryReceiver.mockWith( - dateProvider: RelativeDateProvider( - using: .init(timeIntervalSince1970: 0) - ) + let receiver = TelemetryReceiver.mockWith( + featureScope: featureScope, + dateProvider: RelativeDateProvider(using: .init(timeIntervalSince1970: 0)) ) + let telemetry = TelemetryMock(with: receiver) - let trace = core.telemetry.startMethodCalled( + // When + let trace = telemetry.startMethodCalled( operationName: .mockAny(), callerClass: .mockAny(), samplingRate: 0 ) - core.telemetry.stopMethodCalled(trace, isSuccessful: true) + telemetry.stopMethodCalled(trace, isSuccessful: true) - let event = core.events(ofType: TelemetryDebugEvent.self).first + // Then + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first XCTAssertNil(event) } } diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index e4007d4a0f..5865165e2f 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -59,12 +59,14 @@ extension TelemetryReceiver: AnyMockable { public static func mockAny() -> Self { .mockWith() } static func mockWith( + featureScope: FeatureScope = NOPFeatureScope(), dateProvider: DateProvider = SystemDateProvider(), sampler: Sampler = .mockKeepAll(), configurationExtraSampler: Sampler = .mockKeepAll(), metricsExtraSampler: Sampler = .mockKeepAll() ) -> Self { .init( + featureScope: featureScope, dateProvider: dateProvider, sampler: sampler, configurationExtraSampler: configurationExtraSampler, From 1aae88f5250607ba33def9fb0f83e36d4cba34c1 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 26 Mar 2024 17:22:21 +0100 Subject: [PATCH 6/8] RUM-3461 Cleanup --- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 6 +++--- .../MessageBus/FeatureMessageReceiver.swift | 3 +++ DatadogRUM/Sources/Feature/RUMFeature.swift | 3 +-- DatadogRUM/Sources/RUMMonitor/Monitor.swift | 3 +-- .../Scopes/RUMScopeDependencies.swift | 4 ++-- .../RUMMonitor/Scopes/RUMSessionScope.swift | 6 +++--- .../RUMMonitor/Scopes/RUMViewScope.swift | 2 +- .../ErrorMessageReceiverTests.swift | 3 +-- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 6 +++--- DatadogRUM/Tests/RUMMonitor/MonitorTests.swift | 18 +++++++----------- .../Scopes/RUMSessionScopeTests.swift | 10 +++++----- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 2 +- .../RUMMonitorProtocol+ConvenienceTests.swift | 1 - .../RUMMonitorProtocol+InternalTests.swift | 1 - 14 files changed, 31 insertions(+), 37 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index fa968805d1..c119c2ec1f 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -700,7 +700,7 @@ extension RUMScopeDependencies { } static func mockWith( - scope: FeatureScope = NOPFeatureScope(), + featureScope: FeatureScope = NOPFeatureScope(), rumApplicationID: String = .mockAny(), sessionSampler: Sampler = .mockKeepAll(), trackBackgroundEvents: Bool = .mockAny(), @@ -715,7 +715,7 @@ extension RUMScopeDependencies { viewCache: ViewCache = ViewCache() ) -> RUMScopeDependencies { return RUMScopeDependencies( - scope: scope, + featureScope: featureScope, rumApplicationID: rumApplicationID, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, @@ -747,7 +747,7 @@ extension RUMScopeDependencies { viewCache: ViewCache? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( - scope: self.scope, + featureScope: self.featureScope, rumApplicationID: rumApplicationID ?? self.rumApplicationID, sessionSampler: sessionSampler ?? self.sessionSampler, trackBackgroundEvents: trackBackgroundEvents ?? self.trackBackgroundEvents, diff --git a/DatadogInternal/Sources/MessageBus/FeatureMessageReceiver.swift b/DatadogInternal/Sources/MessageBus/FeatureMessageReceiver.swift index aa414484cd..9e95de1bf2 100644 --- a/DatadogInternal/Sources/MessageBus/FeatureMessageReceiver.swift +++ b/DatadogInternal/Sources/MessageBus/FeatureMessageReceiver.swift @@ -27,6 +27,9 @@ public protocol FeatureMessageReceiver { /// - Returns: `true` if the message was processed by the receiver;`false` if it was ignored. @discardableResult func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool + // ^ TODO: RUM-3717 + // Remove `core:` parameter from this API once all features are migrated to depend on `FeatureScope` interface + // instead of depending on directly on `core`. } public struct NOPFeatureMessageReceiver: FeatureMessageReceiver { diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index db58524093..f207260e88 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -28,7 +28,7 @@ internal final class RUMFeature: DatadogRemoteFeature { let featureScope = core.scope(for: RUMFeature.self) let dependencies = RUMScopeDependencies( - scope: featureScope, + featureScope: featureScope, rumApplicationID: configuration.applicationID, sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate), trackBackgroundEvents: configuration.trackBackgroundEvents, @@ -73,7 +73,6 @@ internal final class RUMFeature: DatadogRemoteFeature { ) self.monitor = Monitor( - featureScope: featureScope, dependencies: dependencies, dateProvider: configuration.dateProvider ) diff --git a/DatadogRUM/Sources/RUMMonitor/Monitor.swift b/DatadogRUM/Sources/RUMMonitor/Monitor.swift index 3cafcd7aa7..049f74b273 100644 --- a/DatadogRUM/Sources/RUMMonitor/Monitor.swift +++ b/DatadogRUM/Sources/RUMMonitor/Monitor.swift @@ -124,11 +124,10 @@ internal class Monitor: RUMCommandSubscriber { private var attributes: [AttributeKey: AttributeValue] = [:] init( - featureScope: FeatureScope, dependencies: RUMScopeDependencies, dateProvider: DateProvider ) { - self.featureScope = featureScope + self.featureScope = dependencies.featureScope self.scopes = RUMApplicationScope(dependencies: dependencies) self.dateProvider = dateProvider } diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index 45d611c848..8fcfe7dcdd 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -28,7 +28,7 @@ internal struct VitalsReaders { /// Dependency container for injecting components to `RUMScopes` hierarchy. internal struct RUMScopeDependencies { /// The RUM feature scope to interact with core. - let scope: FeatureScope + let featureScope: FeatureScope let rumApplicationID: String let sessionSampler: Sampler let trackBackgroundEvents: Bool @@ -43,7 +43,7 @@ internal struct RUMScopeDependencies { let onSessionStart: RUM.SessionListener? let viewCache: ViewCache - var telemetry: Telemetry { scope.telemetry } + var telemetry: Telemetry { featureScope.telemetry } var sessionType: RUMSessionType { if ciTest != nil { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index 9937848fe6..82a690315f 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -46,7 +46,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { /// Information about this session state, shared with `CrashContext`. private var state: RUMSessionState { didSet { - dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) + dependencies.featureScope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) } } @@ -118,7 +118,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { } // Update `CrashContext` with recent RUM session state: - dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) + dependencies.featureScope.send(message: .baggage(key: RUMBaggageKeys.sessionState, value: state)) // Notify Synthetics if needed if dependencies.syntheticsTest != nil && sessionUUID != .nullUUID { @@ -219,7 +219,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // We also want to send this as a session is being stopped. // It means that with Background Events Tracking disabled, eventual off-view crashes will be dropped // similar to how we drop other events. - dependencies.scope.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) + dependencies.featureScope.send(message: .baggage(key: RUMBaggageKeys.viewReset, value: true)) } return isActive || !viewScopes.isEmpty diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 063c3e4b05..b61c221529 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -542,7 +542,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // Update `CrashContext` with recent RUM view (no matter sampling - we want to always // have recent information if process is interrupted by crash): - dependencies.scope.send( + dependencies.featureScope.send( message: .baggage( key: RUMBaggageKeys.viewEvent, value: event diff --git a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift index f9d588aa04..81bb407504 100644 --- a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift @@ -18,8 +18,7 @@ class ErrorMessageReceiverTests: XCTestCase { receiver = ErrorMessageReceiver( featureScope: featureScope, monitor: Monitor( - featureScope: featureScope, - dependencies: .mockAny(), + dependencies: .mockWith(featureScope: featureScope), dateProvider: SystemDateProvider() ) ) diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 5865165e2f..5c637bef6e 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -745,7 +745,7 @@ extension RUMScopeDependencies { } static func mockWith( - scope: FeatureScope = NOPFeatureScope(), + featureScope: FeatureScope = NOPFeatureScope(), rumApplicationID: String = .mockAny(), sessionSampler: Sampler = .mockKeepAll(), trackBackgroundEvents: Bool = .mockAny(), @@ -760,7 +760,7 @@ extension RUMScopeDependencies { viewCache: ViewCache = ViewCache() ) -> RUMScopeDependencies { return RUMScopeDependencies( - scope: scope, + featureScope: featureScope, rumApplicationID: rumApplicationID, sessionSampler: sessionSampler, trackBackgroundEvents: trackBackgroundEvents, @@ -792,7 +792,7 @@ extension RUMScopeDependencies { viewCache: ViewCache? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( - scope: self.scope, + featureScope: self.featureScope, rumApplicationID: rumApplicationID ?? self.rumApplicationID, sessionSampler: sessionSampler ?? self.sessionSampler, trackBackgroundEvents: trackBackgroundEvents ?? self.trackBackgroundEvents, diff --git a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift index 261a4a2fb3..d0cefe3f7f 100644 --- a/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/MonitorTests.swift @@ -10,7 +10,7 @@ import DatadogInternal @testable import DatadogRUM class MonitorTests: XCTestCase { - private let scope = FeatureScopeMock() + private let featureScope = FeatureScopeMock() func testWhenSessionIsSampled_itSetsRUMContextInCore() throws { // Given @@ -18,8 +18,7 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - featureScope: scope, - dependencies: .mockWith(scope: scope, sessionSampler: sampler), + dependencies: .mockWith(featureScope: featureScope, sessionSampler: sampler), dateProvider: DateProviderMock() ) monitor.startView(key: "foo") @@ -27,7 +26,7 @@ class MonitorTests: XCTestCase { // Then let expectedContext = monitor.currentRUMContext - let rumBaggage = try XCTUnwrap(scope.contextMock.baggages[RUMFeature.name]) + let rumBaggage = try XCTUnwrap(featureScope.contextMock.baggages[RUMFeature.name]) let rumContext = try rumBaggage.decode(type: RUMCoreContext.self) XCTAssertEqual(rumContext.applicationID, expectedContext.rumApplicationID) XCTAssertEqual(rumContext.sessionID, expectedContext.sessionID.toRUMDataFormat) @@ -40,15 +39,14 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - featureScope: scope, - dependencies: .mockWith(scope: scope, sessionSampler: sampler), + dependencies: .mockWith(featureScope: featureScope, sessionSampler: sampler), dateProvider: DateProviderMock() ) monitor.startView(key: "foo") monitor.flush() // Then - XCTAssertNil(scope.contextMock.baggages[RUMFeature.name]) + XCTAssertNil(featureScope.contextMock.baggages[RUMFeature.name]) } func testStartView_withViewController_itUsesClassNameAsViewName() throws { @@ -57,8 +55,7 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - featureScope: scope, - dependencies: .mockWith(scope: scope, sessionSampler: .mockKeepAll()), + dependencies: .mockWith(featureScope: featureScope, sessionSampler: .mockKeepAll()), dateProvider: DateProviderMock() ) monitor.startView(viewController: vc) @@ -75,8 +72,7 @@ class MonitorTests: XCTestCase { // When let monitor = Monitor( - featureScope: scope, - dependencies: .mockWith(scope: scope, sessionSampler: .mockKeepAll()), + dependencies: .mockWith(featureScope: featureScope, sessionSampler: .mockKeepAll()), dateProvider: DateProviderMock() ) monitor.startView(viewController: vc, name: "Some View") diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift index e62e1d446c..2a461b5c4b 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -348,7 +348,7 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, dependencies: .mockWith( - scope: featureScope, + featureScope: featureScope, sessionSampler: .mockRandom() // no matter if sampled or not ), hasReplay: randomIsReplayBeingRecorded @@ -377,7 +377,7 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(scope: featureScope), + dependencies: .mockWith(featureScope: featureScope), hasReplay: randomIsReplayBeingRecorded ) @@ -406,7 +406,7 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( parent: parent, startTime: sessionStartTime, - dependencies: .mockWith(scope: featureScope) + dependencies: .mockWith(featureScope: featureScope) ) // When @@ -564,7 +564,7 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( parent: parent, startTime: Date(), - dependencies: .mockWith(scope: featureScope) + dependencies: .mockWith(featureScope: featureScope) ) let command = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier(), name: "ActiveView") @@ -593,7 +593,7 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( parent: parent, startTime: Date(), - dependencies: .mockWith(scope: featureScope) + dependencies: .mockWith(featureScope: featureScope) ) let startViewCommand = RUMStartViewCommand.mockWith(time: Date(), identity: .mockViewIdentifier()) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift index c6a98ad9aa..0634533f61 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -2448,7 +2448,7 @@ class RUMViewScopeTests: XCTestCase { let scope = RUMViewScope( isInitialView: .mockRandom(), parent: parent, - dependencies: .mockWith(scope: featureScope), + dependencies: .mockWith(featureScope: featureScope), identity: .mockViewIdentifier(), path: "UIViewController", name: "ViewController", diff --git a/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift b/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift index 660e7e6b39..0d87f25e6f 100644 --- a/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift +++ b/DatadogRUM/Tests/RUMMonitorProtocol+ConvenienceTests.swift @@ -17,7 +17,6 @@ class RUMMonitorProtocol_ConvenienceTests: XCTestCase { func testCallingExtensionMethodsIsSafe() { // Given let monitor = Monitor( - featureScope: NOPFeatureScope(), dependencies: .mockAny(), dateProvider: SystemDateProvider() ) diff --git a/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift b/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift index fd6a9d7c85..76ffc47eb3 100644 --- a/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift +++ b/DatadogRUM/Tests/RUMMonitorProtocol+InternalTests.swift @@ -15,7 +15,6 @@ class RUMMonitorProtocol_InternalTests: XCTestCase { // When monitor = Monitor( - featureScope: NOPFeatureScope(), dependencies: .mockAny(), dateProvider: SystemDateProvider() ) From eca743ce5030ff563de19b2a94d1c839151b680d Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 27 Mar 2024 12:13:52 +0100 Subject: [PATCH 7/8] Follow baseline from new segment payload --- .../SessionReplay/SRMultipleViewsRecordingScenarioTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift index 066b8abb20..997dfd9813 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift @@ -43,7 +43,7 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, static let minWireframesInFullSnapshot = 5 /// Total number of "incremental snapshot" records that send "wireframe mutation" data. - static let totalWireframeMutationRecords = 7 + static let totalWireframeMutationRecords = 5 /// Total number of "incremental snapshot" records that send "pointer interaction" data. static let totalTouchDataRecords = 10 } @@ -78,7 +78,7 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, }) let rawSRRequests = try srEndpoint.pullRecordedRequests(timeout: dataDeliveryTimeout, until: { - try SRSegmentMatcher.segmentsCount(from: $0) == Baseline.totalSegmentsCount + return try SRSegmentMatcher.segmentsCount(from: $0) >= Baseline.totalSegmentsCount }) assertRUM(requests: rawRUMRequests) From e97498a69655eb62a3b0abcc7a937f468dd2b782 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 27 Mar 2024 15:02:07 +0100 Subject: [PATCH 8/8] Add print for CI visibility --- .../SessionReplay/SRMultipleViewsRecordingScenarioTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift index 997dfd9813..cfb520eced 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/SessionReplay/SRMultipleViewsRecordingScenarioTests.swift @@ -78,7 +78,9 @@ class SRMultipleViewsRecordingScenarioTests: IntegrationTests, RUMCommonAsserts, }) let rawSRRequests = try srEndpoint.pullRecordedRequests(timeout: dataDeliveryTimeout, until: { - return try SRSegmentMatcher.segmentsCount(from: $0) >= Baseline.totalSegmentsCount + let segmentsCount = try SRSegmentMatcher.segmentsCount(from: $0) + sendCIAppLog("Pulled \(segmentsCount) segments") + return segmentsCount >= Baseline.totalSegmentsCount }) assertRUM(requests: rawRUMRequests)