From c2710e2a6448cf6eb85bf5af31eaa62b645e5ce8 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:35 +0200 Subject: [PATCH 1/6] [chain-pr 3/10] DatadogCore: CoreMessageBus implementation and rename --- Datadog/Datadog.xcodeproj/project.pbxproj | 44 +-- ...{MessageBus.swift => CoreMessageBus.swift} | 96 ++++++- DatadogCore/Sources/Core/DatadogCore.swift | 11 +- .../ContextSharingTransformer.swift | 18 +- .../Extensions/CrossPlatformExtension.swift | 4 +- .../DatadogCore/CoreMessageBusTests.swift | 266 ++++++++++++++++++ .../ContextSharingTransformerTests.swift | 24 +- .../DatadogURLSessionHandler.swift | 9 +- .../NetworkContextProvider.swift | 9 +- .../NetworkInstrumentationFeatureTests.swift | 14 +- .../Tests/Telemetry/TelemetryTests.swift | 15 +- 11 files changed, 422 insertions(+), 88 deletions(-) rename DatadogCore/Sources/Core/{MessageBus.swift => CoreMessageBus.swift} (52%) create mode 100644 DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index a7dbc98c48..1e48501fab 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -644,7 +644,6 @@ 61BB2B1B244A185D009F3F56 /* PerformancePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */; }; 61BBD19724ED50040023E65F /* DatadogConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BBD19624ED50040023E65F /* DatadogConfigurationTests.swift */; }; 61C363802436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */; }; - 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */; }; 61C5A89624509BF600DA608C /* TracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A89524509BF600DA608C /* TracerTests.swift */; }; 61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A02A3B78F900FA735A /* RUMMonitorProtocol.swift */; }; 61C713A52A3B78F900FA735A /* RUMMonitorProtocol+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A12A3B78F900FA735A /* RUMMonitorProtocol+Internal.swift */; }; @@ -681,7 +680,6 @@ 61DA8CB828647A500074A606 /* InternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CB728647A500074A606 /* InternalLoggerTests.swift */; }; 61DB33B225DEDFC200F7EA71 /* CustomObjcViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */; }; 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */; }; 61E262D42EB2592C0041E70F /* DatadogFlags.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BA8C2ED2E784B3C00B1DA80 /* DatadogFlags.framework */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; 61ED39D426C2A36B002C0F26 /* DataUploadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ED39D326C2A36B002C0F26 /* DataUploadStatus.swift */; }; @@ -859,12 +857,16 @@ D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */; }; D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21C26C528A3B49C005DD405 /* FeatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26C428A3B49C005DD405 /* FeatureStorage.swift */; }; - D21C26D128A64599005DD405 /* MessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26D028A64599005DD405 /* MessageBusTests.swift */; }; + D21C26D128A64599005DD405 /* CoreMessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */; }; D22442C52CA301DA002E71E4 /* UIColor+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */; }; D224430429E9588100274EC7 /* TelemetryReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */; }; - D224430629E95C2C00274EC7 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA429E072D7004D0AE8 /* MessageBus.swift */; }; + D224430629E95C2C00274EC7 /* CoreMessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */; }; D224430D29E95D6700274EC7 /* CrashReportReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224430C29E95D6600274EC7 /* CrashReportReceiverTests.swift */; }; D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */; }; + D224DFA82FAB8C4B000AED53 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFA72FAB8C4B000AED53 /* MessageBus.swift */; }; + D224DFAA2FAB9DCE000AED53 /* MessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */; }; + D224DFAE2FACFA43000AED53 /* RUMDataModels+BusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */; }; + D224DFB02FB1BBCA000AED53 /* WebViewBusMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */; }; D22689B92EB12C3100875E44 /* Filters in Frameworks */ = {isa = PBXBuildFile; productRef = D22689B82EB12C3100875E44 /* Filters */; }; D22689BB2EB12C3100875E44 /* Recording in Frameworks */ = {isa = PBXBuildFile; productRef = D22689BA2EB12C3100875E44 /* Recording */; }; D22689C32EB12D3D00875E44 /* KSCrashPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22689C22EB12D3D00875E44 /* KSCrashPlugin.swift */; }; @@ -1208,7 +1210,6 @@ D2C5D52B2B84F6AB00B63F36 /* WebViewRecordReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C5D52A2B84F6AB00B63F36 /* WebViewRecordReceiver.swift */; }; D2C5D52D2B84F6D800B63F36 /* WebViewRecordReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C5D52C2B84F6D800B63F36 /* WebViewRecordReceiverTests.swift */; }; D2C7E3AB28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */; }; - D2CC33DB2FBCC81D00377194 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CC33DA2FBCC81D00377194 /* MessageBus.swift */; }; D2D0E1F22E3CE59700BA4113 /* mach_profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E1F12E3CE59700BA4113 /* mach_profiler.cpp */; }; D2D0E1F82E3CE73700BA4113 /* mach_sampling_profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E1F72E3CE73700BA4113 /* mach_sampling_profiler.cpp */; }; D2D0E2072E3CEF1200BA4113 /* MachSamplingProfilerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E2022E3CEF1200BA4113 /* MachSamplingProfilerTests.swift */; }; @@ -2467,7 +2468,6 @@ 61C3E63824BF19B4008053F2 /* RUMContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContext.swift; sourceTree = ""; }; 61C3E63A24BF1A4B008053F2 /* RUMCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMCommand.swift; sourceTree = ""; }; 61C3E63D24BF1B91008053F2 /* RUMApplicationScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMApplicationScope.swift; sourceTree = ""; }; - 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptorTests.swift; sourceTree = ""; }; 61C5A87824509A0C00DA608C /* DDSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDSpan.swift; sourceTree = ""; }; 61C5A87924509A0C00DA608C /* DDNoOps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDNoOps.swift; sourceTree = ""; }; 61C5A87C24509A0C00DA608C /* Casting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Casting.swift; sourceTree = ""; }; @@ -2517,7 +2517,6 @@ 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomObjcViewController.m; sourceTree = ""; }; 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricControllerTests.swift; sourceTree = ""; }; 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionEndedMetricIntegrationTests.swift; sourceTree = ""; }; - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptor.swift; sourceTree = ""; }; 61DE333525C8278A008E3EC2 /* CrashReportingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingPlugin.swift; sourceTree = ""; }; 61E141EE2D42BC74008C8851 /* RUMViewEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewEndedMetricIntegrationTests.swift; sourceTree = ""; }; 61E45BCE2450A6EC00F2C652 /* TraceIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceIDTests.swift; sourceTree = ""; }; @@ -2713,7 +2712,7 @@ D20FD9D52ACC0934004D3569 /* WebLogIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogIntegrationTests.swift; sourceTree = ""; }; D21331C02D132F0600E4A6A1 /* SwiftUIWireframesBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIWireframesBuilderTests.swift; sourceTree = ""; }; D213532F270CA722000315AD /* DataCompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCompressionTests.swift; sourceTree = ""; }; - D214DAA429E072D7004D0AE8 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; + D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMessageBus.swift; sourceTree = ""; }; D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryReceiver.swift; sourceTree = ""; }; D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageReceiver.swift; sourceTree = ""; }; D215F0932F6D5DE000E86466 /* ThirdPartyDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyDelegateProxy.swift; sourceTree = ""; }; @@ -2737,11 +2736,15 @@ D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewMessage.swift; sourceTree = ""; }; D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryTests.swift; sourceTree = ""; }; D21C26C428A3B49C005DD405 /* FeatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureStorage.swift; sourceTree = ""; }; - D21C26D028A64599005DD405 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; + D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMessageBusTests.swift; sourceTree = ""; }; D21C26EA28AFA11E005DD405 /* LogMessageReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogMessageReceiverTests.swift; sourceTree = ""; }; D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageReceiverTests.swift; sourceTree = ""; }; D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+SessionReplay.swift"; sourceTree = ""; }; D224430C29E95D6600274EC7 /* CrashReportReceiverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReportReceiverTests.swift; sourceTree = ""; }; + D224DFA72FAB8C4B000AED53 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; + D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; + D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMDataModels+BusMessage.swift"; sourceTree = ""; }; + D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewBusMessages.swift; sourceTree = ""; }; D22689C22EB12D3D00875E44 /* KSCrashPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSCrashPlugin.swift; sourceTree = ""; }; D22689C62EB2151F00875E44 /* KSCrashPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSCrashPluginTests.swift; sourceTree = ""; }; D2268AD12EB4DA4100875E44 /* DatadogTypeSafeFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogTypeSafeFilter.swift; sourceTree = ""; }; @@ -2955,7 +2958,6 @@ D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusPublisherTests.swift; sourceTree = ""; }; D2CBC26A294383F200134409 /* WebViewEventReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewEventReceiver.swift; sourceTree = ""; }; D2CBC26D294395A300134409 /* RUMContextAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContextAttributes.swift; sourceTree = ""; }; - D2CC33DA2FBCC81D00377194 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; D2D0E1F12E3CE59700BA4113 /* mach_profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mach_profiler.cpp; sourceTree = ""; }; D2D0E1F72E3CE73700BA4113 /* mach_sampling_profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mach_sampling_profiler.cpp; sourceTree = ""; }; D2D0E2012E3CEF1200BA4113 /* MachProfilerCAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MachProfilerCAPITests.swift; sourceTree = ""; }; @@ -4530,7 +4532,7 @@ isa = PBXGroup; children = ( D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */, - D214DAA429E072D7004D0AE8 /* MessageBus.swift */, + D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */, D2EFA866286DA82700F1FAA6 /* Context */, 61133BA62423979B00786299 /* Storage */, 6128F56C2BA223A100D35B08 /* DataStore */, @@ -5018,7 +5020,6 @@ D236BE2729520FED00676E67 /* CrashReportReceiver.swift */, D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */, D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */, - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */, D2D748222DC0FF7E00C61353 /* FatalErrorContextNotifier.swift */, 5B1D02842E8EB78600AB2391 /* FlagEvaluationReceiver.swift */, ); @@ -5352,6 +5353,7 @@ D28FB6952DB7D3F000CD76D0 /* RUMDataModels.swift */, D2D9A9DB2DBFD507005DB31D /* RUMPayloadMessages.swift */, D2E8A8E62DCBBA5100CF7C63 /* RUMCoreContext.swift */, + D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */, ); path = RUM; sourceTree = ""; @@ -6172,7 +6174,7 @@ children = ( D23039C1298D5235001A1FA3 /* FeatureMessageReceiver.swift */, D23039C2298D5235001A1FA3 /* FeatureMessage.swift */, - D2CC33DA2FBCC81D00377194 /* MessageBus.swift */, + D224DFA72FAB8C4B000AED53 /* MessageBus.swift */, ); path = MessageBus; sourceTree = ""; @@ -6258,7 +6260,6 @@ D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */, 9E53889B2773C4B300A7DC42 /* WebViewEventReceiverTests.swift */, D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */, - 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */, 5B1D02932E8ED6BE00AB2391 /* FlagEvaluationReceiverTests.swift */, ); path = Integrations; @@ -6321,6 +6322,7 @@ isa = PBXGroup; children = ( D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */, + D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */, ); path = WebViewTracking; sourceTree = ""; @@ -6565,6 +6567,7 @@ isa = PBXGroup; children = ( D2DA23A0298D58F400C6C7E6 /* FeatureMessageReceiverTests.swift */, + D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */, ); path = MessageBus; sourceTree = ""; @@ -6812,7 +6815,7 @@ 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */, 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */, 613F9C172BAC3527007C7606 /* DatadogCore+FeatureDataStoreTests.swift */, - D21C26D028A64599005DD405 /* MessageBusTests.swift */, + D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */, ); path = DatadogCore; sourceTree = ""; @@ -7969,7 +7972,7 @@ 6128F5742BA3280300D35B08 /* DataStoreFileReader.swift in Sources */, 11F59BC22EAE2180009F8579 /* LaunchInfoPublisher.swift in Sources */, D2553829288F0B2400727FAD /* LowPowerModePublisher.swift in Sources */, - D224430629E95C2C00274EC7 /* MessageBus.swift in Sources */, + D224430629E95C2C00274EC7 /* CoreMessageBus.swift in Sources */, 61F930BE2BA1ACAC005F0EE2 /* Storage+TLV.swift in Sources */, 6128F5772BA32DE500D35B08 /* DataStoreFileWriter.swift in Sources */, 6139CD712589FAFD007E8BB7 /* Retrying.swift in Sources */, @@ -8163,7 +8166,7 @@ 61345613244756E300E7DA6B /* PerformancePresetTests.swift in Sources */, D28F836C29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */, F603F12B2CAEA4FA0088E6B7 /* DDInternalLoggerTests.swift in Sources */, - D21C26D128A64599005DD405 /* MessageBusTests.swift in Sources */, + D21C26D128A64599005DD405 /* CoreMessageBusTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8573,7 +8576,6 @@ D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */, D23039EC298D5236001A1FA3 /* LaunchInfo.swift in Sources */, 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */, - D2CC33DB2FBCC81D00377194 /* MessageBus.swift in Sources */, D227A0A42C7622EA00C83324 /* BenchmarkProfiler.swift in Sources */, D23039EE298D5236001A1FA3 /* FeatureMessageReceiver.swift in Sources */, D23039DE298D5235001A1FA3 /* Writer.swift in Sources */, @@ -8638,6 +8640,7 @@ 3CA8525F2BF2073800B52CBA /* TraceContextInjection.swift in Sources */, D23039F6298D5236001A1FA3 /* Attributes.swift in Sources */, D20731CB29A52E6000ECBF94 /* Sampler.swift in Sources */, + D224DFA82FAB8C4B000AED53 /* MessageBus.swift in Sources */, D2EBEE2029BA160F00B15732 /* TracePropagationHeadersWriter.swift in Sources */, 267134902D688D280048CB54 /* AccountInfo.swift in Sources */, D23039EF298D5236001A1FA3 /* FeatureMessage.swift in Sources */, @@ -8649,6 +8652,7 @@ D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D27465812E7B1C4D00C47FE2 /* ProfilingMessages.swift in Sources */, 5BFDD7A92E28FDD3009A2CEE /* RUMWebViewContext.swift in Sources */, + D224DFAE2FACFA43000AED53 /* RUMDataModels+BusMessage.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, D2C179E12DD2388800556F68 /* TraceCoreContext.swift in Sources */, B3E46CAE2D91B40000BABF66 /* NetworkContext.swift in Sources */, @@ -8668,6 +8672,7 @@ E996B2C5B9B268490842F924 /* HeatmapAttributes.swift in Sources */, 9D0C56F6A6472E38F662D6D7 /* HeatmapIdentifier.swift in Sources */, 13124CCC91AF2DC5EC18DDA6 /* HeatmapIdentifierRegistry.swift in Sources */, + D224DFB02FB1BBCA000AED53 /* WebViewBusMessages.swift in Sources */, A01D384CBDE4FFDC49CCD424 /* HeatmapIdentifierRegistryFeature.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8990,7 +8995,6 @@ 11A2F24A2E70CC08006EDC52 /* FrameInfoProvider.swift in Sources */, 11A2F24B2E70CC08006EDC52 /* MediaTimeProvider.swift in Sources */, D29A9F5229DD85BB005C54A4 /* RUMUUIDGenerator.swift in Sources */, - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */, 4062CCAEB02C9EEF6761F4F8 /* HeatmapIdentifierStore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -9002,7 +9006,6 @@ 6188697C2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D29A9FA629DDB483005C54A4 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, - 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D29A9FBD29DDB483005C54A4 /* RUMSessionScopeTests.swift in Sources */, 0904F9F62EE1DA6800ED9A22 /* UIKitExtensionsTests.swift in Sources */, 3C4CF9982C47CC91006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */, @@ -9102,6 +9105,7 @@ D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23A3298D58F400C6C7E6 /* AnyEncodableTests.swift in Sources */, 3CCECDAF2BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */, + D224DFAA2FAB9DCE000AED53 /* MessageBusTests.swift in Sources */, D263BCB429DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, D233E7D22E4627D900E7CFDE /* MultipartFormDataTests.swift in Sources */, 09F8243A2FB48F5B005D1DCE /* WebViewSessionRolloverHandlerTests.swift in Sources */, diff --git a/DatadogCore/Sources/Core/MessageBus.swift b/DatadogCore/Sources/Core/CoreMessageBus.swift similarity index 52% rename from DatadogCore/Sources/Core/MessageBus.swift rename to DatadogCore/Sources/Core/CoreMessageBus.swift index e8d687de5d..350371b4e1 100644 --- a/DatadogCore/Sources/Core/MessageBus.swift +++ b/DatadogCore/Sources/Core/CoreMessageBus.swift @@ -10,7 +10,7 @@ import DatadogInternal /// The message-bus sends messages to a set of registered receivers. /// /// The bus dispatches messages on a serial queue. -internal final class MessageBus { +internal final class CoreMessageBus: @unchecked Sendable { /// The message bus GDC queue. let queue = DispatchQueue( label: "com.datadoghq.ios-sdk-message-bus", @@ -27,6 +27,25 @@ internal final class MessageBus { /// The bus **must** be accessed within the queue. private var bus: [String: FeatureMessageReceiver] = [:] + /// A closure that delivers a `BusMessage` to a single subscriber. + /// + /// Captures the receiver strongly and performs the runtime cast from `Any` + /// to the receiver's expected `Message` type. + private typealias Dispatch = (Any, DatadogCoreProtocol) -> Void + + /// Typed `BusMessageReceiver` subscribers grouped by `BusMessage.key`. + /// + /// The outer key is the `BusMessage.key` of the message kind a subscriber + /// is registered for; the inner key is the receiver's object identity. The + /// keyed layout means dispatch only iterates receivers for the matching + /// message kind instead of the full subscriber set. + /// + /// Each `Dispatch` captures its receiver strongly, so the bus retains + /// subscribers until they are explicitly removed via `unsubscribe(receiver:)`. + /// + /// Must be accessed within the queue. + private var receivers: [String: [ObjectIdentifier: Dispatch]] = [:] + /// The current configuration. /// /// The message-bus wil accumulate configuration by merge. A message @@ -42,11 +61,18 @@ internal final class MessageBus { /// - Parameter configurationDispatchTime: The delay to dispatch the /// configuration telemetry init(configurationDispatchTime: DispatchTimeInterval = .seconds(5)) { - queue.asyncAfter(deadline: .now() + configurationDispatchTime) { - guard let core = self.core, let configuration = self.configuration else { + queue.asyncAfter(deadline: .now() + configurationDispatchTime) { [weak self] in + guard let self = self, let core = self.core, let configuration = self.configuration else { return } + // Dispatch via typed bus to TelemetryMessage subscribers. + if let bucket = self.receivers[TelemetryMessage.key] { + let message = TelemetryMessage.configuration(configuration) + bucket.values.forEach { dispatch in dispatch(message, core) } + } + + // Dispatch via legacy bus for receivers still on the legacy bus. self.bus.values.forEach { $0.receive(message: .telemetry(.configuration(configuration)), from: core) } @@ -125,7 +151,69 @@ internal final class MessageBus { } } -extension MessageBus: Flushable { +extension CoreMessageBus: MessageBus { + /// Adds `receiver` to the bucket for `Receiver.Message.key`. + /// + /// The receiver is retained by the bus (via the captured closure) until + /// `unsubscribe(receiver:)` is called. Re-subscribing the same instance for + /// the same message kind replaces the previous subscription — entries are + /// keyed by object identity. + func subscribe(receiver: Receiver) where Receiver: BusMessageReceiver { + queue.async { + let id = ObjectIdentifier(receiver) + self.receivers[Receiver.Message.key, default: [:]][id] = { message, core in + guard let typed = message as? Receiver.Message else { + return + } + receiver.receive(message: typed, from: core) + } + } + } + + /// Removes `receiver` from the bucket for `Receiver.Message.key`. + /// + /// No-op if `receiver` is not currently subscribed. Empty buckets are + /// pruned to keep the registry tidy. + func unsubscribe(receiver: Receiver) where Receiver: BusMessageReceiver { + queue.async { + let key = Receiver.Message.key + let id = ObjectIdentifier(receiver) + self.receivers[key]?.removeValue(forKey: id) + if self.receivers[key]?.isEmpty == true { + self.receivers.removeValue(forKey: key) + } + } + } + + /// Publishes `message` to every receiver in the bucket for `Message.key`. + /// + /// Configuration telemetry messages are intercepted and accumulated for deferred + /// batch dispatch; they are never routed to subscribers immediately. + /// + /// `fallback` is invoked when the bus has no core, or when the bucket is + /// empty. Delivery is dispatched on the bus's serial queue. + func send(message: Message, else fallback: @escaping () -> Void) where Message: BusMessage { + // Intercept configuration telemetry for deferred accumulated dispatch. + if let telemetry = message as? TelemetryMessage, case .configuration(let config) = telemetry { + save(configuration: config) + return + } + + queue.async { + guard let core = self.core else { + return fallback() + } + guard let bucket = self.receivers[Message.key], !bucket.isEmpty else { + return fallback() + } + bucket.values.forEach { dispatch in + dispatch(message, core) + } + } + } +} + +extension CoreMessageBus: Flushable { /// Awaits completion of all asynchronous operations. /// /// **blocks the caller thread** diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index c7b8738e2b..24a376b67c 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -53,7 +53,7 @@ internal final class DatadogCore { let applicationVersionPublisher: ApplicationVersionPublisher /// The message-bus instance. - let bus = MessageBus() + let bus = CoreMessageBus() /// Registry for Features. @ReadWriteLock @@ -119,9 +119,9 @@ internal final class DatadogCore { // the bus will keep a weak ref to the core. bus.connect(core: self) - // forward any context change on the message-bus + // forward any context change on the typed message-bus self.contextProvider.publish { [weak self] context in - self?.send(message: .context(context)) + self?.bus.send(message: context) } } @@ -264,8 +264,9 @@ internal final class DatadogCore { /// - key: The key associated with the receiver. private func add(messageReceiver: FeatureMessageReceiver, forKey key: String) { bus.connect(messageReceiver, forKey: key) + // Push current context to typed-bus DatadogContext subscribers. contextProvider.read { context in - self.bus.queue.async { messageReceiver.receive(message: .context(context), from: self) } + self.bus.send(message: context) } } @@ -323,6 +324,8 @@ internal final class DatadogCore { } extension DatadogCore: DatadogCoreProtocol { + var messageBus: MessageBus { bus } + /// Registers a Feature instance. /// /// A Feature collects and transfers data to a Datadog Product (e.g. Logs, RUM, ...). A registered Feature can diff --git a/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift b/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift index bb9c3f3c0e..bf37f3b489 100644 --- a/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift +++ b/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift @@ -6,24 +6,16 @@ import DatadogInternal -internal final class ContextSharingTransformer: FeatureMessageReceiver, ContextValuePublisher { +internal final class ContextSharingTransformer: BusMessageReceiver, ContextValuePublisher { @ReadWriteLock private var sharedContext: SharedContext? = nil @ReadWriteLock private var receiver: ContextValueReceiver? = nil - // MARK: - FeatureMessageReceiver - - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - switch message { - case .context(let context): - let newContext = SharedContext(datadogContext: context) - sharedContext = newContext - receiver?(newContext) - return true - default: - return false - } + func receive(message context: DatadogContext, from core: DatadogCoreProtocol) { + let newContext = SharedContext(datadogContext: context) + sharedContext = newContext + receiver?(newContext) } // MARK: - ContextValuePublisher diff --git a/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift b/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift index 15bad05648..a487f6e187 100644 --- a/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift +++ b/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift @@ -29,7 +29,9 @@ public final class CrossPlatformExtension: NSObject { if Self.contextSharingTransformer == nil { let core = CoreRegistry.default let contextSharingTransformer = ContextSharingTransformer() - try? core.register(feature: ContextSharingFeature(messageReceiver: contextSharingTransformer)) + // Subscribe before registration so initial context push is received: + core.messageBus.subscribe(receiver: contextSharingTransformer) + try? core.register(feature: ContextSharingFeature(messageReceiver: NOPFeatureMessageReceiver())) Self.contextSharingTransformer = contextSharingTransformer } contextSharingTransformer?.publish(to: toSharedContext) diff --git a/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift new file mode 100644 index 0000000000..d4cc24aeff --- /dev/null +++ b/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift @@ -0,0 +1,266 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +import DatadogInternal + +@testable import DatadogCore + +class CoreMessageBusTests: XCTestCase { + func testCoreMessageBus() throws { + let expectation = XCTestExpectation(description: "dispatch message") + expectation.expectedFulfillmentCount = 2 + + // Given + let core = PassthroughCoreMock() + + let receiver = FeatureMessageReceiverMock { message in + // Then + switch message { + case let .payload(payload as String) where payload == "value": + expectation.fulfill() + default: + XCTFail("wrong message case") + } + } + + let bus = CoreMessageBus() + bus.connect(core: core) + + bus.connect(receiver, forKey: "receiver 1") + bus.connect(receiver, forKey: "receiver 2") + + // When + bus.send(message: .payload("value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testItForwardConfigurationAfterDispatch() throws { + let expectation = XCTestExpectation(description: "dispatch configuration") + let receiver = FeatureMessageReceiverMock { message in + guard + case .telemetry(let telemetry) = message, + case .configuration(let configuration) = telemetry + else { + return XCTFail("Message bus should send configuration telemetry") + } + + XCTAssertEqual(configuration.batchSize, 1) + XCTAssertTrue(configuration.trackErrors ?? false) + expectation.fulfill() + } + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus(configurationDispatchTime: .milliseconds(90)) + bus.connect(core: core) + bus.connect(receiver, forKey: "test") + + // When + bus.configuration(batchSize: 1) + bus.configuration(trackErrors: true) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + // MARK: - typed bus (subscribe / unsubscribe / send) + + func testSubscribe_deliversMessageToReceiver() throws { + let expectation = XCTestExpectation(description: "receiver invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { message, _ in + XCTAssertEqual(message.value, "value") + expectation.fulfill() + } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSubscribe_routesByMessageType() throws { + let expectation = XCTestExpectation(description: "matching receiver invoked") + expectation.assertForOverFulfill = true + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let alpha = AlphaReceiver { _, _ in expectation.fulfill() } + let beta = BetaReceiver { _, _ in + XCTFail("BetaReceiver must not receive AlphaMessage") + } + bus.subscribe(receiver: alpha) + bus.subscribe(receiver: beta) + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSubscribe_deliversToMultipleReceiversOfSameType() throws { + let expectation = XCTestExpectation(description: "all receivers invoked") + expectation.expectedFulfillmentCount = 3 + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receivers = (0..<3).map { _ in + AlphaReceiver { _, _ in expectation.fulfill() } + } + receivers.forEach { bus.subscribe(receiver: $0) } + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testUnsubscribe_stopsDelivery() throws { + let fallbackExpectation = XCTestExpectation(description: "fallback invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { _, _ in + XCTFail("receiver must not be invoked after unsubscribe") + } + bus.subscribe(receiver: receiver) + bus.unsubscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { fallbackExpectation.fulfill() } + + // Then + wait(for: [fallbackExpectation], timeout: 0.5) + bus.flush() + } + + func testSend_callsFallbackWhenNoSubscribers() throws { + let expectation = XCTestExpectation(description: "fallback invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + // When + bus.send(message: AlphaMessage(value: "value")) { expectation.fulfill() } + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSend_callsFallbackWhenCoreNotConnected() throws { + let expectation = XCTestExpectation(description: "fallback invoked") + + // Given (no `connect(core:)` call) + let bus = CoreMessageBus() + let receiver = AlphaReceiver { _, _ in + XCTFail("receiver must not be invoked without a connected core") + } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { expectation.fulfill() } + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSend_doesNotCallFallbackWhenDelivered() throws { + let delivery = XCTestExpectation(description: "receiver invoked") + let fallback = XCTestExpectation(description: "fallback NOT invoked") + fallback.isInverted = true + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { _, _ in delivery.fulfill() } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { fallback.fulfill() } + + // Then + wait(for: [delivery, fallback], timeout: 0.5) + bus.flush() + } +} + +// MARK: - typed-bus fixtures + +private struct AlphaMessage: BusMessage { + static let key = "test.alpha" + let value: String +} + +private struct BetaMessage: BusMessage { + static let key = "test.beta" +} + +private final class AlphaReceiver: BusMessageReceiver { + typealias Message = AlphaMessage + + let onReceive: (AlphaMessage, DatadogCoreProtocol) -> Void + + init(_ onReceive: @escaping (AlphaMessage, DatadogCoreProtocol) -> Void) { + self.onReceive = onReceive + } + + func receive(message: AlphaMessage, from core: DatadogCoreProtocol) { + onReceive(message, core) + } +} + +private final class BetaReceiver: BusMessageReceiver { + typealias Message = BetaMessage + + let onReceive: (BetaMessage, DatadogCoreProtocol) -> Void + + init(_ onReceive: @escaping (BetaMessage, DatadogCoreProtocol) -> Void) { + self.onReceive = onReceive + } + + func receive(message: BetaMessage, from core: DatadogCoreProtocol) { + onReceive(message, core) + } +} + +extension CoreMessageBus: @retroactive Telemetry { + public func send(telemetry: DatadogInternal.TelemetryMessage) { + send(message: .telemetry(telemetry)) + } +} diff --git a/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift b/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift index b5df47d5c6..82a14b0fcb 100644 --- a/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift +++ b/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift @@ -27,25 +27,11 @@ class ContextSharingTransformerTests: XCTestCase { func testReceiveContextMessage_transformsToSharedContext() throws { // Given let transformer = ContextSharingTransformer() - let message = FeatureMessage.context(.mockRandom()) // When - let handled = transformer.receive(message: message, from: core) + transformer.receive(message: .mockRandom(), from: core) - // Then - XCTAssertTrue(handled) - } - - func testReceiveNonContextMessage_returnsNotHandled() throws { - // Given - let transformer = ContextSharingTransformer() - let customMessage = FeatureMessage.payload("") - - // When - let handled = transformer.receive(message: customMessage, from: core) - - // Then - XCTAssertFalse(handled) + // Then — no assertion needed; just checking it doesn't crash } func testPublish_callsReceiverImmediately() throws { @@ -78,8 +64,7 @@ class ContextSharingTransformerTests: XCTestCase { let userInfo = UserInfo(id: "user-456") let accountInfo = AccountInfo(id: "account-789") let context = DatadogContext.mockWith(userInfo: userInfo, accountInfo: accountInfo) - let message = FeatureMessage.context(context) - _ = transformer.receive(message: message, from: core) + transformer.receive(message: context, from: core) // Then XCTAssertEqual(receivedContexts.count, 2) @@ -101,8 +86,7 @@ class ContextSharingTransformerTests: XCTestCase { transformer.cancel() let context = DatadogContext.mockWith(userInfo: UserInfo(id: "user-789")) - let message = FeatureMessage.context(context) - _ = transformer.receive(message: message, from: core) + transformer.receive(message: context, from: core) // Then - receiver must not be called after cancel XCTAssertEqual(callCount, 1) // Only initial call from publish diff --git a/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift b/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift index 5adc207788..41c566cd69 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift @@ -46,8 +46,13 @@ extension DatadogCoreProtocol { /// /// - Parameter urlSessionHandler: The `URLSession` handler to register. public func register(urlSessionHandler: DatadogURLSessionHandler) throws { - let contextProvider = NetworkContextCoreProvider() - let feature = get(feature: NetworkInstrumentationFeature.self) ?? .init(networkContextProvider: contextProvider, messageReceiver: contextProvider) + let feature = get(feature: NetworkInstrumentationFeature.self) ?? { + let contextProvider = NetworkContextCoreProvider() + let feature = NetworkInstrumentationFeature(networkContextProvider: contextProvider, messageReceiver: NOPFeatureMessageReceiver()) + // Subscribe typed-bus receiver before registration so initial context push is received: + messageBus.subscribe(receiver: contextProvider) + return feature + }() feature.handlers.append(urlSessionHandler) try register(feature: feature) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift index 86d658e437..e178d2b602 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift @@ -19,12 +19,8 @@ internal class NetworkContextCoreProvider: NetworkContextProvider { var currentNetworkContext: NetworkContext? } -extension NetworkContextCoreProvider: FeatureMessageReceiver { - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .context(context) = message else { - return false - } - +extension NetworkContextCoreProvider: BusMessageReceiver { + func receive(message context: DatadogContext, from core: DatadogCoreProtocol) { let userConfigurationContext: UserConfigurationContext? = { guard let userInfo = context.userInfo else { return nil @@ -45,6 +41,5 @@ extension NetworkContextCoreProvider: FeatureMessageReceiver { userConfigurationContext: userConfigurationContext, accountConfigurationContext: accountConfigurationContext ) - return true } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift index 734a1bffd9..c150ce9f81 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift @@ -2190,10 +2190,9 @@ class NetworkInstrumentationFeatureTests: XCTestCase { ) // When - let result = provider.receive(message: .context(context), from: core) + provider.receive(message: context, from: core) // Then - XCTAssertTrue(result) let networkContext = try XCTUnwrap(provider.currentNetworkContext) // Verify RUM context @@ -2222,10 +2221,9 @@ class NetworkInstrumentationFeatureTests: XCTestCase { ) // When - let result = provider.receive(message: .context(context), from: core) + provider.receive(message: context, from: core) // Then - XCTAssertTrue(result) let networkContext = try XCTUnwrap(provider.currentNetworkContext) // Verify RUM context is still available @@ -2237,15 +2235,11 @@ class NetworkInstrumentationFeatureTests: XCTestCase { XCTAssertNil(networkContext.accountConfigurationContext) } - func testWhenReceivingNonContextMessage_itReturnsFalse() { + func testNetworkContextIsNilUntilContextMessageIsReceived() { // Given let provider = NetworkContextCoreProvider() - // When - let result = provider.receive(message: .payload("some data"), from: core) - - // Then - XCTAssertFalse(result) + // Then — no DatadogContext delivered yet XCTAssertNil(provider.currentNetworkContext) } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 6515d3c7b7..baa8aa9e18 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -187,23 +187,24 @@ class TelemetryTests: XCTestCase { // MARK: - Integration with Core func testWhenUsingCoreTelemetry_itSendsTelemetryToMessageReceiver() throws { - let receiver = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let receiver = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: receiver) core.telemetry.debug("debug message") - XCTAssertEqual(receiver.messages.lastTelemetry?.asDebug?.message, "debug message") + XCTAssertEqual(receiver.messages.firstDebug()?.message, "debug message") core.telemetry.error("error message") - XCTAssertEqual(receiver.messages.lastTelemetry?.asError?.message, "error message") + XCTAssertEqual(receiver.messages.firstError()?.message, "error message") core.telemetry.configuration(batchSize: 123) - XCTAssertEqual(receiver.messages.lastTelemetry?.asConfiguration?.batchSize, 123) + XCTAssertEqual(receiver.messages.firstConfiguration()?.batchSize, 123) core.telemetry.metric(name: "metric name", attributes: [:], sampleRate: 15) - XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, "metric name") + XCTAssertEqual(receiver.messages.firstMetric(named: "metric name")?.name, "metric name") let metricTrace = core.telemetry.startMethodCalled(operationName: .mockAny(), callerClass: .mockAny(), headSampleRate: 100) core.telemetry.stopMethodCalled(metricTrace) - XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, MethodCalledMetric.name) + XCTAssertEqual(receiver.messages.lastMetric(named: MethodCalledMetric.name)?.name, MethodCalledMetric.name) } } From bdc9ca44fe1fc53bd0ca13e066becd30610e4898 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:41 +0200 Subject: [PATCH 2/6] [chain-pr 4/10] DatadogCrashReporting: typed-bus migration --- .../CrashContextProviderTests.swift | 93 ++++++++++-------- .../CrashReporting/CrashReporterTests.swift | 3 +- .../Sources/CrashContextProvider.swift | 55 +++++++---- .../Sources/CrashReportSender.swift | 6 +- .../Sources/CrashReporting.swift | 3 + .../Tests/CrashContextCoreProviderTests.swift | 96 ++++++++----------- .../Tests/CrashReportSenderTests.swift | 12 ++- 7 files changed, 146 insertions(+), 122 deletions(-) diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift index be3805ef19..a266ea156d 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift @@ -20,7 +20,21 @@ import TestUtilities /// This suite tests if `CrashContextProvider` gets updated by different SDK components, each updating /// separate part of the `CrashContext` information. class CrashContextProviderTests: XCTestCase { - private let provider = CrashContextCoreProvider() + private var provider: CrashContextCoreProvider! // swiftlint:disable:this implicitly_unwrapped_optional + private var core: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + core = PassthroughCoreMock() + provider = CrashContextCoreProvider() + provider.subscribe(to: core.messageBus) + } + + override func tearDown() { + core = nil + provider = nil + super.tearDown() + } // MARK: - Receiving SDK Context @@ -32,7 +46,7 @@ class CrashContextProviderTests: XCTestCase { let sdkContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) // Then provider.flush() @@ -49,8 +63,8 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) // receive next + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial + core.messageBus.send(message: nextSDKContext) // receive next // Then provider.flush() @@ -70,8 +84,8 @@ class CrashContextProviderTests: XCTestCase { let rumView: RUMViewEvent = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .payload(rumView), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) + core.messageBus.send(message: rumView) // Then provider.flush() @@ -90,9 +104,9 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumView), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial SDK context + core.messageBus.send(message: rumView) + core.messageBus.send(message: nextSDKContext) // Then provider.flush() @@ -113,9 +127,9 @@ class CrashContextProviderTests: XCTestCase { let rumView: RUMViewEvent = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumView), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .payload(RUMPayloadMessages.viewReset), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) // receive initial SDK context + core.messageBus.send(message: rumView) + core.messageBus.send(message: RUMViewReset()) // Then provider.flush() @@ -134,10 +148,10 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumView), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .payload(RUMPayloadMessages.viewReset), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial SDK context + core.messageBus.send(message: rumView) + core.messageBus.send(message: RUMViewReset()) + core.messageBus.send(message: nextSDKContext) // Then provider.flush() @@ -158,8 +172,8 @@ class CrashContextProviderTests: XCTestCase { let rumSessionState: RUMSessionState = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumSessionState), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) // receive initial SDK context + core.messageBus.send(message: rumSessionState) // Then provider.flush() @@ -178,9 +192,9 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumSessionState), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial SDK context + core.messageBus.send(message: rumSessionState) + core.messageBus.send(message: nextSDKContext) // Then provider.flush() @@ -201,8 +215,8 @@ class CrashContextProviderTests: XCTestCase { let rumAttributes: RUMEventAttributes = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumAttributes), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) // receive initial SDK context + core.messageBus.send(message: rumAttributes) // Then provider.flush() @@ -221,9 +235,9 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(rumAttributes), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial SDK context + core.messageBus.send(message: rumAttributes) + core.messageBus.send(message: nextSDKContext) // Then provider.flush() @@ -244,8 +258,8 @@ class CrashContextProviderTests: XCTestCase { let logAttributes: LogEventAttributes = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(sdkContext), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(logAttributes), from: NOPDatadogCore())) + core.messageBus.send(message: sdkContext) // receive initial SDK context + core.messageBus.send(message: logAttributes) // Then provider.flush() @@ -264,9 +278,9 @@ class CrashContextProviderTests: XCTestCase { let nextSDKContext: DatadogContext = .mockRandom() // When - XCTAssertTrue(provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore())) // receive initial SDK context - XCTAssertTrue(provider.receive(message: .payload(logAttributes), from: NOPDatadogCore())) - XCTAssertTrue(provider.receive(message: .context(nextSDKContext), from: NOPDatadogCore())) + core.messageBus.send(message: DatadogContext.mockRandom()) // receive initial SDK context + core.messageBus.send(message: logAttributes) + core.messageBus.send(message: nextSDKContext) // Then provider.flush() @@ -279,24 +293,27 @@ class CrashContextProviderTests: XCTestCase { // MARK: - Thread safety func testWhenContextIsWrittenAndReadFromDifferentThreads_itRunsAllOperationsSafely() { - let provider = CrashContextCoreProvider() + let localProvider = CrashContextCoreProvider() + let localCore = PassthroughCoreMock() + localProvider.subscribe(to: localCore.messageBus) + let viewEvent: RUMViewEvent = .mockRandom() let sessionState: RUMSessionState = .mockRandom() // swiftlint:disable opening_brace callConcurrently( closures: [ - { _ = provider.currentCrashContext }, - { _ = provider.receive(message: .context(.mockRandom()), from: NOPDatadogCore()) }, - { _ = provider.receive(message: .payload(viewEvent), from: NOPDatadogCore()) }, - { _ = provider.receive(message: .payload(RUMPayloadMessages.viewReset), from: NOPDatadogCore()) }, - { _ = provider.receive(message: .payload(sessionState), from: NOPDatadogCore()) }, + { _ = localProvider.currentCrashContext }, + { localCore.messageBus.send(message: DatadogContext.mockRandom()) }, + { localCore.messageBus.send(message: viewEvent) }, + { localCore.messageBus.send(message: RUMViewReset()) }, + { localCore.messageBus.send(message: sessionState) }, ], iterations: 50 ) // swiftlint:enable opening_brace - provider.flush() + localProvider.flush() } // MARK: - Helpers diff --git a/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift b/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift index f2c3f35531..98a654c9d5 100644 --- a/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift +++ b/DatadogCore/Tests/Datadog/CrashReporting/CrashReporterTests.swift @@ -57,7 +57,8 @@ class CrashReporterTests: XCTestCase { let crashReport: DDCrashReport = .mockRandomWith(context: crashContext) let rumCrashReceiver = CrashReceiverMock() - let core = PassthroughCoreMock(messageReceiver: rumCrashReceiver) + let core = PassthroughCoreMock() + core.subscribe(receiver: rumCrashReceiver) let plugin = CrashReportingPluginMock() diff --git a/DatadogCrashReporting/Sources/CrashContextProvider.swift b/DatadogCrashReporting/Sources/CrashContextProvider.swift index 6216d5f116..3cd38cff4e 100644 --- a/DatadogCrashReporting/Sources/CrashContextProvider.swift +++ b/DatadogCrashReporting/Sources/CrashContextProvider.swift @@ -47,6 +47,9 @@ internal class CrashContextCoreProvider: CrashContextProvider { didSet { _context?.lastRUMAttributes = rumAttributes } } + /// Typed-bus subscription handles. Retaining these keeps the closures alive. + private var subscriptions: [MessageBusSubscription] = [] + // MARK: - CrashContextProviderType var currentCrashContext: CrashContext? { @@ -61,30 +64,13 @@ internal class CrashContextCoreProvider: CrashContextProvider { extension CrashContextCoreProvider: FeatureMessageReceiver { func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - switch message { - case .context(let context): - update(context: context) - case let .payload(viewEvent as RUMViewEvent): - queue.async { self.viewEvent = viewEvent } - case let .payload(message as String) where message == RUMPayloadMessages.viewReset: - queue.async { self.viewEvent = nil } - case let .payload(sessionState as RUMSessionState): - queue.async { self.sessionState = sessionState } - case let .payload(rumAttributes as RUMEventAttributes): - queue.async { self.rumAttributes = rumAttributes } - case let .payload(logAttributes as LogEventAttributes): - queue.async { self.logAttributes = logAttributes } - default: - return false - } - - return true + return false } /// Updates crash context. /// /// - Parameter context: The updated core context. - private func update(context: DatadogContext) { + fileprivate func update(context: DatadogContext) { queue.async { [weak self] in guard let self = self else { return @@ -105,6 +91,37 @@ extension CrashContextCoreProvider: FeatureMessageReceiver { } } +// MARK: - Typed-bus subscriptions + +extension CrashContextCoreProvider { + /// Subscribes to all crash-context update messages on the typed bus. + /// + /// Call once from `CrashReporting.enableOrThrow`. The returned subscriptions are retained + /// by the provider and cancelled when it is deallocated. + func subscribe(to bus: MessageBus) { + subscriptions = [ + bus.subscribe { [weak self] (context: DatadogContext, _) in + self?.update(context: context) + }, + bus.subscribe { [weak self] (message: RUMViewEvent, _) in + self?.queue.async { self?.viewEvent = message } + }, + bus.subscribe { [weak self] (_: RUMViewReset, _) in + self?.queue.async { self?.viewEvent = nil } + }, + bus.subscribe { [weak self] (message: RUMSessionState, _) in + self?.queue.async { self?.sessionState = message } + }, + bus.subscribe { [weak self] (message: RUMEventAttributes, _) in + self?.queue.async { self?.rumAttributes = message } + }, + bus.subscribe { [weak self] (message: LogEventAttributes, _) in + self?.queue.async { self?.logAttributes = message } + }, + ] + } +} + extension CrashContextCoreProvider: Flushable { /// Awaits completion of all asynchronous operations. /// diff --git a/DatadogCrashReporting/Sources/CrashReportSender.swift b/DatadogCrashReporting/Sources/CrashReportSender.swift index 0103d43ca4..bced563979 100644 --- a/DatadogCrashReporting/Sources/CrashReportSender.swift +++ b/DatadogCrashReporting/Sources/CrashReportSender.swift @@ -41,10 +41,8 @@ internal struct MessageBusSender: CrashReportSender { return } - core.send( - message: .payload( - Crash(report: report, context: context) - ), + core.messageBus.send( + message: Crash(report: report, context: context), else: { DD.logger.warn( """ diff --git a/DatadogCrashReporting/Sources/CrashReporting.swift b/DatadogCrashReporting/Sources/CrashReporting.swift index e00c74523d..7ec5e0a7f0 100644 --- a/DatadogCrashReporting/Sources/CrashReporting.swift +++ b/DatadogCrashReporting/Sources/CrashReporting.swift @@ -62,6 +62,9 @@ public final class CrashReporting { try core.register(feature: reporter) + // Subscribe typed-bus receivers for crash context updates: + contextProvider.subscribe(to: core.messageBus) + if let backtraceReporter = plugin.backtraceReporter { try core.register(backtraceReporter: backtraceReporter) } diff --git a/DatadogCrashReporting/Tests/CrashContextCoreProviderTests.swift b/DatadogCrashReporting/Tests/CrashContextCoreProviderTests.swift index bb973fa8bb..3d5c0f03ce 100644 --- a/DatadogCrashReporting/Tests/CrashContextCoreProviderTests.swift +++ b/DatadogCrashReporting/Tests/CrashContextCoreProviderTests.swift @@ -11,25 +11,36 @@ import DatadogInternal @testable import DatadogCrashReporting class CrashContextCoreProviderTests: XCTestCase { + // swiftlint:disable implicitly_unwrapped_optional + private var core: PassthroughCoreMock! + private var provider: CrashContextCoreProvider! + // swiftlint:enable implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + core = PassthroughCoreMock() + provider = CrashContextCoreProvider() + provider.subscribe(to: core.messageBus) + } + + override func tearDown() { + core = nil + provider = nil + super.tearDown() + } + // MARK: - Context Update Tests func testItUpdatesContextFromDatadogContext() { - // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let context: DatadogContext = .mockWith( + // When + core.messageBus.send(message: DatadogContext.mockWith( service: "test-service", env: "test-env", version: "1.0.0" - ) - - // When - let message: FeatureMessage = .context(context) - XCTAssertTrue(provider.receive(message: message, from: core)) + )) provider.flush() // Then - XCTAssertNotNil(provider.currentCrashContext) XCTAssertEqual(provider.currentCrashContext?.service, "test-service") XCTAssertEqual(provider.currentCrashContext?.env, "test-env") XCTAssertEqual(provider.currentCrashContext?.version, "1.0.0") @@ -37,21 +48,16 @@ class CrashContextCoreProviderTests: XCTestCase { func testItDoesNotUpdateContextWhenContextIsUnchanged() { // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() let context: DatadogContext = .mockWith(service: "test-service") var callbackCount = 0 - - provider.onCrashContextChange = { _ in - callbackCount += 1 - } + provider.onCrashContextChange = { _ in callbackCount += 1 } // When - XCTAssertTrue(provider.receive(message: .context(context), from: core)) - XCTAssertTrue(provider.receive(message: .context(context), from: core)) + core.messageBus.send(message: context) + core.messageBus.send(message: context) provider.flush() - // Then - callback should only be called once for the actual change + // Then — callback fires once per distinct value XCTAssertEqual(callbackCount, 1) } @@ -59,36 +65,27 @@ class CrashContextCoreProviderTests: XCTestCase { func testItStoresRUMViewEvent() { // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let context: DatadogContext = .mockAny() let viewEvent: RUMViewEvent = .mockRandom() + core.messageBus.send(message: DatadogContext.mockAny()) // When - XCTAssertTrue(provider.receive(message: .context(context), from: core)) - XCTAssertTrue(provider.receive(message: .payload(viewEvent), from: core)) + core.messageBus.send(message: viewEvent) provider.flush() // Then - XCTAssertNotNil(provider.currentCrashContext?.lastRUMViewEvent) XCTAssertEqual(provider.currentCrashContext?.lastRUMViewEvent?.view.id, viewEvent.view.id) } func testItResetsRUMViewEventOnViewReset() { // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let context: DatadogContext = .mockAny() let viewEvent: RUMViewEvent = .mockRandom() - - XCTAssertTrue(provider.receive(message: .context(context), from: core)) - XCTAssertTrue(provider.receive(message: .payload(viewEvent), from: core)) + core.messageBus.send(message: DatadogContext.mockAny()) + core.messageBus.send(message: viewEvent) provider.flush() - XCTAssertNotNil(provider.currentCrashContext?.lastRUMViewEvent) // When - XCTAssertTrue(provider.receive(message: .payload(RUMPayloadMessages.viewReset), from: core)) + core.messageBus.send(message: RUMViewReset()) provider.flush() // Then @@ -99,18 +96,15 @@ class CrashContextCoreProviderTests: XCTestCase { func testItStoresRUMSessionState() { // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let context: DatadogContext = .mockAny() let sessionState: RUMSessionState = .mockRandom() + core.messageBus.send(message: DatadogContext.mockAny()) + provider.flush() // When - XCTAssertTrue(provider.receive(message: .context(context), from: core)) - XCTAssertTrue(provider.receive(message: .payload(sessionState), from: core)) + core.messageBus.send(message: sessionState) provider.flush() // Then - XCTAssertNotNil(provider.currentCrashContext?.lastRUMSessionState) XCTAssertEqual(provider.currentCrashContext?.lastRUMSessionState?.sessionUUID, sessionState.sessionUUID) } @@ -118,18 +112,12 @@ class CrashContextCoreProviderTests: XCTestCase { func testItInvokesCallbackOnContextChange() { // Given - let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let context: DatadogContext = .mockWith(service: "test-service") var receivedContext: CrashContext? - - provider.onCrashContextChange = { crashContext in - receivedContext = crashContext - } + provider.onCrashContextChange = { receivedContext = $0 } provider.flush() // When - XCTAssertTrue(provider.receive(message: .context(context), from: core)) + core.messageBus.send(message: DatadogContext.mockWith(service: "test-service")) provider.flush() // Then @@ -137,18 +125,14 @@ class CrashContextCoreProviderTests: XCTestCase { XCTAssertEqual(receivedContext?.service, "test-service") } - // MARK: - Message Handling Tests + // MARK: - Initial State Tests - func testItReturnsFalseForUnhandledMessages() { + func testCrashContextIsNilUntilFirstContextMessageIsReceived() { // Given let provider = CrashContextCoreProvider() - let core = PassthroughCoreMock() - let unrelatedMessage = "some unrelated message" + provider.subscribe(to: core.messageBus) - // When - let handled = provider.receive(message: .payload(unrelatedMessage), from: core) - - // Then - XCTAssertFalse(handled) + // Then — no message sent yet + XCTAssertNil(provider.currentCrashContext) } } diff --git a/DatadogCrashReporting/Tests/CrashReportSenderTests.swift b/DatadogCrashReporting/Tests/CrashReportSenderTests.swift index c84ba3c389..64b0e623cb 100644 --- a/DatadogCrashReporting/Tests/CrashReportSenderTests.swift +++ b/DatadogCrashReporting/Tests/CrashReportSenderTests.swift @@ -16,7 +16,8 @@ class CrashReportSenderTests: XCTestCase { func testItSendsCrashReportWhenTrackingConsentIsGranted() { // Given let receiver = CrashReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let core = PassthroughCoreMock() + core.subscribe(receiver: receiver) let sender = MessageBusSender(core: core) let crashContext: CrashContext = .mockWith(trackingConsent: .granted) let crashReport: DDCrashReport = .mockAny() @@ -33,7 +34,8 @@ class CrashReportSenderTests: XCTestCase { func testItDoesNotSendCrashReportWhenTrackingConsentIsNotGranted() { // Given let receiver = CrashReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let core = PassthroughCoreMock() + core.subscribe(receiver: receiver) let sender = MessageBusSender(core: core) let crashContext: CrashContext = .mockWith(trackingConsent: .notGranted) let crashReport: DDCrashReport = .mockAny() @@ -48,7 +50,8 @@ class CrashReportSenderTests: XCTestCase { func testItDoesNotSendCrashReportWhenTrackingConsentIsPending() { // Given let receiver = CrashReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let core = PassthroughCoreMock() + core.subscribe(receiver: receiver) let sender = MessageBusSender(core: core) let crashContext: CrashContext = .mockWith(trackingConsent: .pending) let crashReport: DDCrashReport = .mockAny() @@ -63,7 +66,8 @@ class CrashReportSenderTests: XCTestCase { func testItSendsCrashReportWithCorrectContext() { // Given let receiver = CrashReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let core = PassthroughCoreMock() + core.subscribe(receiver: receiver) let sender = MessageBusSender(core: core) let crashContext: CrashContext = .mockWith( service: "test-service", From 935cf5ef7c78fc79f24520008913598d2a19cc96 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:46 +0200 Subject: [PATCH 3/6] [chain-pr 5/10] DatadogLogs: typed-bus migration --- DatadogLogs/Sources/Feature/LogsFeature.swift | 13 ++- .../Sources/Feature/MessageReceivers.swift | 34 ++---- DatadogLogs/Sources/Logger.swift | 1 + DatadogLogs/Sources/Logs.swift | 10 +- DatadogLogs/Sources/RemoteLogger.swift | 24 +++-- .../Tests/LogMessageReceiverTests.swift | 100 ++++++++---------- DatadogLogs/Tests/LogsTests.swift | 30 +++--- DatadogLogs/Tests/RemoteLoggerTests.swift | 47 ++++++-- .../Tests/WebViewLogReceiverTests.swift | 76 ++++++------- .../DatadogLogs/LoggingFeatureMocks.swift | 10 +- 10 files changed, 170 insertions(+), 175 deletions(-) diff --git a/DatadogLogs/Sources/Feature/LogsFeature.swift b/DatadogLogs/Sources/Feature/LogsFeature.swift index d8b3ab7460..9cecce4eee 100644 --- a/DatadogLogs/Sources/Feature/LogsFeature.swift +++ b/DatadogLogs/Sources/Feature/LogsFeature.swift @@ -14,6 +14,12 @@ internal struct LogsFeature: DatadogRemoteFeature { let messageReceiver: FeatureMessageReceiver + /// Typed-bus receiver for `LogMessage`. + let logMessageReceiver: LogMessageReceiver + + /// Typed-bus receiver for WebView log events. + let webViewLogReceiver: WebViewLogReceiver + let logEventMapper: LogEventMapper? let backtraceReporter: BacktraceReporting? @@ -40,10 +46,7 @@ internal struct LogsFeature: DatadogRemoteFeature { customIntakeURL: customIntakeURL, telemetry: telemetry ), - messageReceiver: CombinedFeatureMessageReceiver( - LogMessageReceiver(logEventMapper: logEventMapper), - WebViewLogReceiver() - ), + messageReceiver: NOPFeatureMessageReceiver(), dateProvider: dateProvider, backtraceReporter: backtraceReporter ) @@ -59,6 +62,8 @@ internal struct LogsFeature: DatadogRemoteFeature { self.logEventMapper = logEventMapper self.requestBuilder = requestBuilder self.messageReceiver = messageReceiver + self.logMessageReceiver = LogMessageReceiver(logEventMapper: logEventMapper) + self.webViewLogReceiver = WebViewLogReceiver() self.dateProvider = dateProvider self.backtraceReporter = backtraceReporter self.attributes = SynchronizedAttributes(attributes: [:]) diff --git a/DatadogLogs/Sources/Feature/MessageReceivers.swift b/DatadogLogs/Sources/Feature/MessageReceivers.swift index c9c2220d73..18970c7bec 100644 --- a/DatadogLogs/Sources/Feature/MessageReceivers.swift +++ b/DatadogLogs/Sources/Feature/MessageReceivers.swift @@ -8,26 +8,21 @@ import Foundation import DatadogInternal /// Receiver to consume a Log message -internal struct LogMessageReceiver: FeatureMessageReceiver { +internal final class LogMessageReceiver: BusMessageReceiver { /// The log event mapper let logEventMapper: LogEventMapper? - /// Process messages receives from the bus. - /// - /// - Parameters: - /// - message: The Feature message - /// - core: The core from which the message is transmitted. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .payload(log as LogMessage) = message else { - return false - } + init(logEventMapper: LogEventMapper?) { + self.logEventMapper = logEventMapper + } + func receive(message log: LogMessage, from core: DatadogCoreProtocol) { core.scope(for: LogsFeature.self).eventWriteContext { context, writer in let builder = LogEventBuilder( service: log.service ?? context.service, loggerName: log.logger, networkInfoEnabled: log.networkInfoEnabled ?? false, - eventMapper: logEventMapper + eventMapper: self.logEventMapper ) builder.createLogEvent( @@ -56,22 +51,13 @@ internal struct LogMessageReceiver: FeatureMessageReceiver { callback: writer.write ) } - - return true } } /// Receiver to consume a Log event coming from Browser SDK. -internal struct WebViewLogReceiver: FeatureMessageReceiver { - /// Process messages receives from the bus. - /// - /// - Parameters: - /// - message: The Feature message - /// - core: The core from which the message is transmitted. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case var .webview(.log(event)) = message else { - return false - } +internal final class WebViewLogReceiver: BusMessageReceiver { + func receive(message: WebViewLogMessage, from core: DatadogCoreProtocol) { + var event = message.event let tagsKey = LogEventEncoder.StaticCodingKeys.tags.rawValue let dateKey = LogEventEncoder.StaticCodingKeys.date.rawValue @@ -105,7 +91,5 @@ internal struct WebViewLogReceiver: FeatureMessageReceiver { writer.write(value: AnyEncodable(event)) } - - return true } } diff --git a/DatadogLogs/Sources/Logger.swift b/DatadogLogs/Sources/Logger.swift index c50da3dc01..a8fb6fedfc 100644 --- a/DatadogLogs/Sources/Logger.swift +++ b/DatadogLogs/Sources/Logger.swift @@ -155,6 +155,7 @@ public struct Logger { return RemoteLogger( featureScope: core.scope(for: LogsFeature.self), + messageBus: core.messageBus, globalAttributes: feature.attributes, configuration: RemoteLogger.Configuration( service: configuration.service, diff --git a/DatadogLogs/Sources/Logs.swift b/DatadogLogs/Sources/Logs.swift index 2586c31c7b..b71ec94e0d 100644 --- a/DatadogLogs/Sources/Logs.swift +++ b/DatadogLogs/Sources/Logs.swift @@ -89,6 +89,10 @@ public enum Logs { ) try core.register(feature: feature) + + // Subscribe typed-bus receivers: + core.messageBus.subscribe(receiver: feature.logMessageReceiver) + core.messageBus.subscribe(receiver: feature.webViewLogReceiver) } /// Adds a custom attribute to all future logs sent by any logger created from the provided Core. @@ -120,10 +124,10 @@ public enum Logs { } private static func sendAttributesChanged(for feature: LogsFeature, in core: DatadogCoreProtocol) { - core.send( - message: .payload(LogEventAttributes( + core.messageBus.send( + message: LogEventAttributes( attributes: feature.attributes.getAttributes() - )) + ) ) } } diff --git a/DatadogLogs/Sources/RemoteLogger.swift b/DatadogLogs/Sources/RemoteLogger.swift index 970c548617..b17b66f28f 100644 --- a/DatadogLogs/Sources/RemoteLogger.swift +++ b/DatadogLogs/Sources/RemoteLogger.swift @@ -27,6 +27,8 @@ internal final class RemoteLogger: LoggerProtocol, Sendable { /// Logs feature scope. let featureScope: FeatureScope + /// Typed message bus, used to forward log errors to RUM. + let messageBus: MessageBus /// Configuration specific to this logger. let configuration: Configuration /// Date provider for logs. @@ -48,6 +50,7 @@ internal final class RemoteLogger: LoggerProtocol, Sendable { init( featureScope: FeatureScope, + messageBus: MessageBus, globalAttributes: SynchronizedAttributes, configuration: Configuration, dateProvider: DateProvider, @@ -56,6 +59,7 @@ internal final class RemoteLogger: LoggerProtocol, Sendable { backtraceReporter: BacktraceReporting? ) { self.featureScope = featureScope + self.messageBus = messageBus self.globalAttributes = globalAttributes self.loggerAttributes = SynchronizedAttributes(attributes: [:]) self.loggerTags = SynchronizedTags(tags: []) @@ -193,17 +197,15 @@ internal final class RemoteLogger: LoggerProtocol, Sendable { busCombinedAttributes[Logs.Attributes.errorFingerprint] = errorFingerprint } - self.featureScope.send( - message: .payload( - RUMErrorMessage( - time: date, - message: log.error?.message ?? log.message, - source: "logger", - type: log.error?.kind, - stack: log.error?.stack, - attributes: busCombinedAttributes, - binaryImages: binaryImages - ) + self.messageBus.send( + message: RUMErrorMessage( + time: date, + message: log.error?.message ?? log.message, + source: "logger", + type: log.error?.kind, + stack: log.error?.stack, + attributes: busCombinedAttributes, + binaryImages: binaryImages ) ) } diff --git a/DatadogLogs/Tests/LogMessageReceiverTests.swift b/DatadogLogs/Tests/LogMessageReceiverTests.swift index 7fb320a608..51656ea848 100644 --- a/DatadogLogs/Tests/LogMessageReceiverTests.swift +++ b/DatadogLogs/Tests/LogMessageReceiverTests.swift @@ -13,27 +13,23 @@ class LogMessageReceiverTests: XCTestCase { func testReceivePartialLogMessage() throws { // Given let expectation = expectation(description: "Send log") - let core = PassthroughCoreMock( - context: .mockWith(service: "service-test"), - messageReceiver: LogMessageReceiver.mockAny() - ) + let core = PassthroughCoreMock(context: .mockWith(service: "service-test")) + core.subscribe(receiver: LogMessageReceiver.mockAny()) core.onEventWriteContext = { _ in expectation.fulfill() } // When - core.send( - message: .payload( - LogMessage( - logger: "logger-test", - service: nil, - date: .mockDecember15th2019At10AMUTC(), - message: "message-test", - error: nil, - level: .info, - thread: "thread-test", - networkInfoEnabled: nil, - userAttributes: nil, - internalAttributes: nil - ) + core.messageBus.send( + message: LogMessage( + logger: "logger-test", + service: nil, + date: .mockDecember15th2019At10AMUTC(), + message: "message-test", + error: nil, + level: .info, + thread: "thread-test", + networkInfoEnabled: nil, + userAttributes: nil, + internalAttributes: nil ) ) @@ -56,27 +52,23 @@ class LogMessageReceiverTests: XCTestCase { func testReceiveCompleteLogMessage() throws { // Given let expectation = expectation(description: "Send log") - let core = PassthroughCoreMock( - context: .mockAny(), - messageReceiver: LogMessageReceiver.mockAny() - ) + let core = PassthroughCoreMock(context: .mockAny()) + core.subscribe(receiver: LogMessageReceiver.mockAny()) core.onEventWriteContext = { _ in expectation.fulfill() } // When - core.send( - message: .payload( - LogMessage( - logger: "logger-test", - service: "service-test", - date: .mockDecember15th2019At10AMUTC(), - message: "message-test", - error: .mockAny(), - level: .info, - thread: "thread-test", - networkInfoEnabled: true, - userAttributes: ["user": "attribute"], - internalAttributes: ["internal": "attribute"] - ) + core.messageBus.send( + message: LogMessage( + logger: "logger-test", + service: "service-test", + date: .mockDecember15th2019At10AMUTC(), + message: "message-test", + error: .mockAny(), + level: .info, + thread: "thread-test", + networkInfoEnabled: true, + userAttributes: ["user": "attribute"], + internalAttributes: ["internal": "attribute"] ) ) @@ -105,29 +97,25 @@ class LogMessageReceiverTests: XCTestCase { func testReceiveRejectedLogMessage() throws { // Given let expectation = expectation(description: "Open scope but don't send log") - let core = PassthroughCoreMock( - context: .mockWith(service: "service-test"), - messageReceiver: LogMessageReceiver( - logEventMapper: SyncLogEventMapper { _ in nil } - ) - ) + let core = PassthroughCoreMock(context: .mockWith(service: "service-test")) + core.subscribe(receiver: LogMessageReceiver( + logEventMapper: SyncLogEventMapper { _ in nil } + )) core.onEventWriteContext = { _ in expectation.fulfill() } // When - core.send( - message: .payload( - LogMessage( - logger: "logger-test", - service: nil, - date: .mockDecember15th2019At10AMUTC(), - message: "message-test", - error: nil, - level: .info, - thread: "thread-test", - networkInfoEnabled: nil, - userAttributes: nil, - internalAttributes: nil - ) + core.messageBus.send( + message: LogMessage( + logger: "logger-test", + service: nil, + date: .mockDecember15th2019At10AMUTC(), + message: "message-test", + error: nil, + level: .info, + thread: "thread-test", + networkInfoEnabled: nil, + userAttributes: nil, + internalAttributes: nil ) ) diff --git a/DatadogLogs/Tests/LogsTests.swift b/DatadogLogs/Tests/LogsTests.swift index dda15e227d..695af9dc5e 100644 --- a/DatadogLogs/Tests/LogsTests.swift +++ b/DatadogLogs/Tests/LogsTests.swift @@ -128,12 +128,10 @@ class LogsTests: XCTestCase { func testItSendsGlobalLogUpdates_whenAddAttribute() throws { // Given - let mockMessageReceiver = FeatureMessageReceiverMock() - let core = SingleFeatureCoreMock( - messageReceiver: mockMessageReceiver - ) - let config = Logs.Configuration() - Logs.enable(with: config, in: core) + let core = SingleFeatureCoreMock() + Logs.enable(with: .init(), in: core) + var received: [LogEventAttributes] = [] + _ = core.messageBus.subscribe { (attrs: LogEventAttributes, _) in received.append(attrs) } // When let attributeKey: String = .mockRandom() @@ -141,20 +139,17 @@ class LogsTests: XCTestCase { Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) // Then - let messages = mockMessageReceiver.messages.compactMap { $0.asPayload as? LogEventAttributes } - XCTAssertEqual(messages.count, 1) - let message = try XCTUnwrap(messages.first) + XCTAssertEqual(received.count, 1) + let message = try XCTUnwrap(received.first) XCTAssertEqual(message.attributes[attributeKey] as? String, attributeValue) } func testItSendsGlobalLogUpdates_whenRemoveAttribute() throws { // Given - let mockMessageReceiver = FeatureMessageReceiverMock() - let core = SingleFeatureCoreMock( - messageReceiver: mockMessageReceiver - ) - let config = Logs.Configuration() - Logs.enable(with: config, in: core) + let core = SingleFeatureCoreMock() + Logs.enable(with: .init(), in: core) + var received: [LogEventAttributes] = [] + _ = core.messageBus.subscribe { (attrs: LogEventAttributes, _) in received.append(attrs) } let attributeKey: String = .mockRandom() let attributeValue: String = .mockRandom() Logs.addAttribute(forKey: attributeKey, value: attributeValue, in: core) @@ -163,9 +158,8 @@ class LogsTests: XCTestCase { Logs.removeAttribute(forKey: attributeKey, in: core) // Then - let messages = mockMessageReceiver.messages.compactMap { $0.asPayload as? LogEventAttributes } - XCTAssertEqual(messages.count, 2) - let message = try XCTUnwrap(messages.last) + XCTAssertEqual(received.count, 2) + let message = try XCTUnwrap(received.last) XCTAssertNil(message.attributes[attributeKey]) } } diff --git a/DatadogLogs/Tests/RemoteLoggerTests.swift b/DatadogLogs/Tests/RemoteLoggerTests.swift index ca9fb9e2f2..c827acb870 100644 --- a/DatadogLogs/Tests/RemoteLoggerTests.swift +++ b/DatadogLogs/Tests/RemoteLoggerTests.swift @@ -9,8 +9,22 @@ import TestUtilities import DatadogInternal @testable import DatadogLogs +private final class RUMErrorMessageRecorder: BusMessageReceiver { + private(set) var messages: [RUMErrorMessage] = [] + func receive(message: RUMErrorMessage, from core: DatadogCoreProtocol) { + messages.append(message) + } +} + class RemoteLoggerTests: XCTestCase { private let featureScope = FeatureScopeMock() + private let messageBus = PassthroughCoreMock() + private let errorRecorder = RUMErrorMessageRecorder() + + override func setUp() { + super.setUp() + messageBus.subscribe(receiver: errorRecorder) + } // MARK: - Sending Error Message over Message Bus @@ -33,6 +47,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -45,13 +60,14 @@ class RemoteLoggerTests: XCTestCase { logger.info("Info message") // Then - XCTAssertEqual(featureScope.messagesSent().count, 0) + XCTAssertEqual(errorRecorder.messages.count, 0) } func testWhenErrorLogged_itPostsToMessageBus() throws { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -64,7 +80,7 @@ class RemoteLoggerTests: XCTestCase { logger.error("Error message") // Then - let errorMessage = try XCTUnwrap(featureScope.messagesSent().firstPayload as? RUMErrorMessage) + let errorMessage = try XCTUnwrap(errorRecorder.messages.first) XCTAssertEqual(errorMessage.message, "Error message") } @@ -72,6 +88,7 @@ class RemoteLoggerTests: XCTestCase { let stubBacktrace: BacktraceReport = .mockRandom() let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -84,7 +101,7 @@ class RemoteLoggerTests: XCTestCase { logger.error("Information message", error: ErrorMock(), attributes: [CrossPlatformAttributes.includeBinaryImages: true]) // Then - let errorMessage = try XCTUnwrap(featureScope.messagesSent().firstPayload as? RUMErrorMessage) + let errorMessage = try XCTUnwrap(errorRecorder.messages.first) // This is removed because binary images are sent in the message, so the additional attribute isn't needed XCTAssertNil(errorMessage.attributes[CrossPlatformAttributes.includeBinaryImages]) XCTAssertEqual(errorMessage.binaryImages?.count, stubBacktrace.binaryImages.count) @@ -104,6 +121,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -124,7 +142,7 @@ class RemoteLoggerTests: XCTestCase { ) // Then - let errorMessage = try XCTUnwrap(featureScope.messagesSent().firstPayload as? RUMErrorMessage) + let errorMessage = try XCTUnwrap(errorRecorder.messages.first) XCTAssertEqual(errorMessage.attributes[CrossPlatformAttributes.errorSourceType] as? String, "flutter") XCTAssertEqual(errorMessage.attributes[Logs.Attributes.errorFingerprint] as? String, mockFingerprint) } @@ -133,6 +151,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -156,7 +175,7 @@ class RemoteLoggerTests: XCTestCase { ) // Then - let errorMessage = try XCTUnwrap(featureScope.messagesSent().firstPayload as? RUMErrorMessage) + let errorMessage = try XCTUnwrap(errorRecorder.messages.first) XCTAssertEqual(errorMessage.attributes[CrossPlatformAttributes.errorSourceType] as? String, "flutter") XCTAssertEqual(errorMessage.attributes[Logs.Attributes.errorFingerprint] as? String, mockFingerprint) } @@ -169,6 +188,7 @@ class RemoteLoggerTests: XCTestCase { let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -214,6 +234,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -250,6 +271,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: SynchronizedAttributes(attributes: [attributeKey: attributeValue]), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -277,6 +299,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: SynchronizedAttributes(attributes: [attributeKey: globalAttributeValue]), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -306,6 +329,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: SynchronizedAttributes(attributes: [attributeKey: globalAttributeValue]), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -333,6 +357,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: SynchronizedAttributes(attributes: [attributeKey: attributeValue]), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -345,7 +370,7 @@ class RemoteLoggerTests: XCTestCase { logger.error("Error message") // Then - let errorMessage = try XCTUnwrap(featureScope.messagesSent().firstPayload as? RUMErrorMessage) + let errorMessage = try XCTUnwrap(errorRecorder.messages.first) XCTAssertEqual(errorMessage.attributes[attributeKey] as? String, attributeValue) } @@ -353,6 +378,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -378,6 +404,7 @@ class RemoteLoggerTests: XCTestCase { let stubBacktrace: BacktraceReport = .mockRandom() let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -415,6 +442,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -454,6 +482,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -497,6 +526,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -537,6 +567,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -566,6 +597,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -601,6 +633,7 @@ class RemoteLoggerTests: XCTestCase { // Given let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -638,6 +671,7 @@ class RemoteLoggerTests: XCTestCase { let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), @@ -685,6 +719,7 @@ class RemoteLoggerTests: XCTestCase { let logger = RemoteLogger( featureScope: featureScope, + messageBus: messageBus.messageBus, globalAttributes: .mockAny(), configuration: .mockAny(), dateProvider: RelativeDateProvider(), diff --git a/DatadogLogs/Tests/WebViewLogReceiverTests.swift b/DatadogLogs/Tests/WebViewLogReceiverTests.swift index 24506da2d1..eb2e18cda7 100644 --- a/DatadogLogs/Tests/WebViewLogReceiverTests.swift +++ b/DatadogLogs/Tests/WebViewLogReceiverTests.swift @@ -65,11 +65,9 @@ class WebViewLogReceiverTests: XCTestCase { let value: String = .mockRandom() // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(["test": value])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: ["test": value]), + from: core ) // Then @@ -117,11 +115,9 @@ class WebViewLogReceiverTests: XCTestCase { ] // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(webLogEvent)), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: webLogEvent), + from: core ) // Then @@ -167,11 +163,9 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(["test": "value"])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: ["test": "value"]), + from: core ) // Then @@ -210,11 +204,9 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(["test": "value"])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: ["test": "value"]), + from: core ) // Then @@ -241,11 +233,9 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(["test": "value"])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: ["test": "value"]), + from: core ) // Then @@ -280,14 +270,12 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log([ - "test": "value", - "usr": ["id": webUsrId, "name": webUsrName] - ])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: [ + "test": "value", + "usr": ["id": webUsrId, "name": webUsrName] + ]), + from: core ) // Then @@ -314,14 +302,12 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log([ - "test": "value", - "usr": ["anonymous_id": browserAnonymousId] - ])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: [ + "test": "value", + "usr": ["anonymous_id": browserAnonymousId] + ]), + from: core ) // Then @@ -345,11 +331,9 @@ class WebViewLogReceiverTests: XCTestCase { core.onEventWriteContext = { _ in expectation.fulfill() } // When - XCTAssert( - messageReceiver.receive( - message: .webview(.log(["test": "value"])), - from: core - ) + messageReceiver.receive( + message: WebViewLogMessage(event: ["test": "value"]), + from: core ) // Then diff --git a/TestUtilities/Sources/Mocks/DatadogLogs/LoggingFeatureMocks.swift b/TestUtilities/Sources/Mocks/DatadogLogs/LoggingFeatureMocks.swift index c139b1015e..2f4f85baac 100644 --- a/TestUtilities/Sources/Mocks/DatadogLogs/LoggingFeatureMocks.swift +++ b/TestUtilities/Sources/Mocks/DatadogLogs/LoggingFeatureMocks.swift @@ -54,16 +54,14 @@ extension LogsFeature { } extension LogMessageReceiver: AnyMockable { - public static func mockAny() -> Self { - .mockWith() + public static func mockAny() -> LogMessageReceiver { + mockWith() } public static func mockWith( logEventMapper: LogEventMapper? = nil - ) -> Self { - .init( - logEventMapper: logEventMapper - ) + ) -> LogMessageReceiver { + LogMessageReceiver(logEventMapper: logEventMapper) } } From f8a05fde21d35a02ccc289f44781ac271e6f01b4 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:52 +0200 Subject: [PATCH 4/6] [chain-pr 6/10] DatadogTrace: typed-bus migration --- .../TracingWithLoggingIntegrationTests.swift | 3 +- DatadogCore/Tests/Datadog/TracerTests.swift | 20 +++++------- .../TracingURLSessionHandlerTests.swift | 10 +++--- .../Sources/Feature/MessageReceivers.swift | 23 ++----------- .../Sources/Feature/TraceFeature.swift | 2 +- .../TracingWithLoggingIntegration.swift | 32 +++++++++---------- DatadogTrace/Sources/Trace.swift | 4 +++ .../Tests/ContextMessageReceiverTests.swift | 6 ++-- .../Tests/TracingURLSessionHandlerTests.swift | 3 +- 9 files changed, 40 insertions(+), 63 deletions(-) diff --git a/DatadogCore/Tests/Datadog/FeaturesIntegration/TracingWithLoggingIntegrationTests.swift b/DatadogCore/Tests/Datadog/FeaturesIntegration/TracingWithLoggingIntegrationTests.swift index 86165eb71b..1b85f3f493 100644 --- a/DatadogCore/Tests/Datadog/FeaturesIntegration/TracingWithLoggingIntegrationTests.swift +++ b/DatadogCore/Tests/Datadog/FeaturesIntegration/TracingWithLoggingIntegrationTests.swift @@ -17,7 +17,8 @@ class TracingWithLoggingIntegrationTests: XCTestCase { override func setUp() { super.setUp() - core = PassthroughCoreMock(messageReceiver: LogMessageReceiver.mockAny()) + core = PassthroughCoreMock() + core.subscribe(receiver: LogMessageReceiver.mockAny()) } override func tearDown() { diff --git a/DatadogCore/Tests/Datadog/TracerTests.swift b/DatadogCore/Tests/Datadog/TracerTests.swift index 972a49d260..2672de9cc3 100644 --- a/DatadogCore/Tests/Datadog/TracerTests.swift +++ b/DatadogCore/Tests/Datadog/TracerTests.swift @@ -618,10 +618,9 @@ class TracerTests: XCTestCase { // MARK: - Integration With Logging Feature func testSendingSpanLogs() throws { - let logging: LogsFeature = .mockWith( - messageReceiver: LogMessageReceiver.mockAny() - ) + let logging: LogsFeature = .mockWith() try core.register(feature: logging) + core.messageBus.subscribe(receiver: logging.logMessageReceiver) Trace.enable(with: config, in: core) let tracer = Tracer.shared(in: core) @@ -651,10 +650,9 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromArguments() throws { - let logging: LogsFeature = .mockWith( - messageReceiver: LogMessageReceiver.mockAny() - ) + let logging: LogsFeature = .mockWith() try core.register(feature: logging) + core.messageBus.subscribe(receiver: logging.logMessageReceiver) Trace.enable(with: config, in: core) let tracer = Tracer.shared(in: core) @@ -676,10 +674,9 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromNSError() throws { - let logging: LogsFeature = .mockWith( - messageReceiver: LogMessageReceiver.mockAny() - ) + let logging: LogsFeature = .mockWith() try core.register(feature: logging) + core.messageBus.subscribe(receiver: logging.logMessageReceiver) Trace.enable(with: config, in: core) let tracer = Tracer.shared(in: core) @@ -707,10 +704,9 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromSwiftError() throws { - let logging: LogsFeature = .mockWith( - messageReceiver: LogMessageReceiver.mockAny() - ) + let logging: LogsFeature = .mockWith() try core.register(feature: logging) + core.messageBus.subscribe(receiver: logging.logMessageReceiver) Trace.enable(with: config, in: core) let tracer = Tracer.shared(in: core) diff --git a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift index 90c76cee88..49dbb243bf 100644 --- a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift @@ -21,10 +21,9 @@ class TracingURLSessionHandlerTests: XCTestCase { override func setUp() { super.setUp() let receiver = ContextMessageReceiver(samplerProvider: SamplerProvider(sampleRate: .mockAny())) - core = PassthroughCoreMock(messageReceiver: CombinedFeatureMessageReceiver([ - LogMessageReceiver.mockAny(), - receiver - ])) + core = PassthroughCoreMock() + core.subscribe(receiver: receiver) + core.subscribe(receiver: LogMessageReceiver.mockAny()) tracer = .mockWith( core: core, @@ -225,8 +224,7 @@ class TracingURLSessionHandlerTests: XCTestCase { ) ] ) - let message = FeatureMessage.context(fakeContext) - _ = handler.contextReceiver.receive(message: message, from: core) + handler.contextReceiver.receive(message: fakeContext, from: core) let (modifiedRequest, _, _) = handler.modify( request: request, headerTypes: [.datadog, .tracecontext, .b3, .b3multi], diff --git a/DatadogTrace/Sources/Feature/MessageReceivers.swift b/DatadogTrace/Sources/Feature/MessageReceivers.swift index 79abcbf288..2f374b5f99 100644 --- a/DatadogTrace/Sources/Feature/MessageReceivers.swift +++ b/DatadogTrace/Sources/Feature/MessageReceivers.swift @@ -21,7 +21,7 @@ internal struct CoreContext { var accountInfo: AccountInfo? } -internal final class ContextMessageReceiver: FeatureMessageReceiver { +internal final class ContextMessageReceiver: BusMessageReceiver { /// Creates a new `ContextMessageReceiver`. /// /// - parameters: @@ -41,24 +41,7 @@ internal final class ContextMessageReceiver: FeatureMessageReceiver { /// The tracer sampler that should be updated with the RUM deterministic sampler. let samplerProvider: SamplerProvider - /// Process messages receives from the bus. - /// - /// - Parameters: - /// - message: The Feature message - /// - core: The core from which the message is transmitted. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - switch message { - case .context(let context): - return update(context: context, from: core) - default: - return false - } - } - - /// Updates context of the `DatadogTracer` if available. - /// - /// - Parameter context: The updated core context. - private func update(context datadogContext: DatadogContext, from core: DatadogCoreProtocol) -> Bool { + func receive(message datadogContext: DatadogContext, from core: DatadogCoreProtocol) { let rumContext = datadogContext.additionalContext(ofType: RUMCoreContext.self) _context.mutate { @@ -69,7 +52,5 @@ internal final class ContextMessageReceiver: FeatureMessageReceiver { } samplerProvider.updateWith(deterministicSampler: rumContext?.sessionSampler) - - return true } } diff --git a/DatadogTrace/Sources/Feature/TraceFeature.swift b/DatadogTrace/Sources/Feature/TraceFeature.swift index 45f2183a3f..91bc77ee8f 100644 --- a/DatadogTrace/Sources/Feature/TraceFeature.swift +++ b/DatadogTrace/Sources/Feature/TraceFeature.swift @@ -11,7 +11,7 @@ internal final class TraceFeature: DatadogRemoteFeature { static let name = "tracing" let requestBuilder: FeatureRequestBuilder - var messageReceiver: FeatureMessageReceiver { contextReceiver } + let messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() let tracer: DatadogTracer let contextReceiver: ContextMessageReceiver diff --git a/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift b/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift index ade1b6beda..45ce2a1613 100644 --- a/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift +++ b/DatadogTrace/Sources/Integrations/TracingWithLoggingIntegration.swift @@ -61,23 +61,21 @@ internal struct TracingWithLoggingIntegration { ) : nil - core.send( - message: .payload( - LogMessage( - logger: "trace", - service: service, - date: date, - message: message, - error: extractedError, - level: level, - thread: Thread.current.dd.name, - networkInfoEnabled: networkInfoEnabled, - userAttributes: userAttributes, - internalAttributes: [ - Constants.traceIDKey: String(spanContext.traceID, representation: .hexadecimal), - Constants.spanIDKey: String(spanContext.spanID, representation: .hexadecimal) - ] - ) + core.messageBus.send( + message: LogMessage( + logger: "trace", + service: service, + date: date, + message: message, + error: extractedError, + level: level, + thread: Thread.current.dd.name, + networkInfoEnabled: networkInfoEnabled, + userAttributes: userAttributes, + internalAttributes: [ + Constants.traceIDKey: String(spanContext.traceID, representation: .hexadecimal), + Constants.spanIDKey: String(spanContext.spanID, representation: .hexadecimal) + ] ), else: fallback ) diff --git a/DatadogTrace/Sources/Trace.swift b/DatadogTrace/Sources/Trace.swift index 375fd0577d..cd5ce1c719 100644 --- a/DatadogTrace/Sources/Trace.swift +++ b/DatadogTrace/Sources/Trace.swift @@ -42,6 +42,10 @@ public enum Trace { // Register Trace feature: let trace = TraceFeature(in: core, configuration: configuration) + + // Subscribe typed-bus receivers before registration so initial context push is received: + core.messageBus.subscribe(receiver: trace.contextReceiver) + try core.register(feature: trace) // If `URLSession` tracking is configured, register `URLSessionHandler` to enable distributed tracing: diff --git a/DatadogTrace/Tests/ContextMessageReceiverTests.swift b/DatadogTrace/Tests/ContextMessageReceiverTests.swift index 7d20e38fcb..5d635c8927 100644 --- a/DatadogTrace/Tests/ContextMessageReceiverTests.swift +++ b/DatadogTrace/Tests/ContextMessageReceiverTests.swift @@ -14,10 +14,8 @@ class ContextMessageReceiverTests: XCTestCase { func testItReceivesApplicationStateHistory() throws { // Given let receiver = ContextMessageReceiver(samplerProvider: SamplerProvider(sampleRate: .mockAny())) - let core = PassthroughCoreMock( - context: .mockWith(applicationStateHistory: .mockAppInBackground()), - messageReceiver: receiver - ) + let core = PassthroughCoreMock(context: .mockWith(applicationStateHistory: .mockAppInBackground())) + core.subscribe(receiver: receiver) XCTAssertEqual(receiver.context.applicationStateHistory?.currentState, .background) diff --git a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift index 5c590de497..7947925ae8 100644 --- a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift +++ b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift @@ -20,7 +20,8 @@ class TracingURLSessionHandlerTests: XCTestCase { override func setUp() { super.setUp() let receiver = ContextMessageReceiver(samplerProvider: SamplerProvider(sampleRate: .mockAny())) - core = PassthroughCoreMock(messageReceiver: receiver) + core = PassthroughCoreMock() + core.subscribe(receiver: receiver) tracer = .mockWith( core: core, From 176b9f2fb48b8479fd103be28d2834db673166c7 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:57 +0200 Subject: [PATCH 5/6] [chain-pr 7/10] DatadogFlags: typed-bus migration --- DatadogFlags/Sources/Client/FlagsClient.swift | 2 +- .../Client/RUMFlagEvaluationReporter.swift | 16 ++++++-------- .../Sources/Feature/FlagsFeature.swift | 6 ++--- .../Tests/Client/FlagsClientTests.swift | 22 ++++++++++++------- .../RUMFlagEvaluationReporterTests.swift | 19 +++++++++++----- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/DatadogFlags/Sources/Client/FlagsClient.swift b/DatadogFlags/Sources/Client/FlagsClient.swift index cc2f837cd9..5768f19984 100644 --- a/DatadogFlags/Sources/Client/FlagsClient.swift +++ b/DatadogFlags/Sources/Client/FlagsClient.swift @@ -189,7 +189,7 @@ public final class FlagsClient { ), exposureLogger: feature.makeExposureLogger(featureScope), evaluationLogger: feature.makeEvaluationLogger(featureScope), - rumFlagEvaluationReporter: feature.makeRUMFlagEvaluationReporter(featureScope) + rumFlagEvaluationReporter: feature.makeRUMFlagEvaluationReporter() ) feature.clientRegistry.register(client, named: name) diff --git a/DatadogFlags/Sources/Client/RUMFlagEvaluationReporter.swift b/DatadogFlags/Sources/Client/RUMFlagEvaluationReporter.swift index 9934046a5f..c5e2255bb3 100644 --- a/DatadogFlags/Sources/Client/RUMFlagEvaluationReporter.swift +++ b/DatadogFlags/Sources/Client/RUMFlagEvaluationReporter.swift @@ -12,19 +12,17 @@ internal protocol RUMFlagEvaluationReporting { } internal final class RUMFlagEvaluationReporter: RUMFlagEvaluationReporting { - private let featureScope: any FeatureScope + private let messageBus: any MessageBus - init(featureScope: any FeatureScope) { - self.featureScope = featureScope + init(messageBus: any MessageBus) { + self.messageBus = messageBus } func sendFlagEvaluation(flagKey: String, value: T) where T: FlagValue { - featureScope.send( - message: .payload( - RUMFlagEvaluationMessage( - flagKey: flagKey, - value: value - ) + messageBus.send( + message: RUMFlagEvaluationMessage( + flagKey: flagKey, + value: value ) ) } diff --git a/DatadogFlags/Sources/Feature/FlagsFeature.swift b/DatadogFlags/Sources/Feature/FlagsFeature.swift index 4be5725a08..3da108818d 100644 --- a/DatadogFlags/Sources/Feature/FlagsFeature.swift +++ b/DatadogFlags/Sources/Feature/FlagsFeature.swift @@ -21,7 +21,7 @@ internal struct FlagsFeature: DatadogRemoteFeature { let clientRegistry: FlagsClientRegistry let makeExposureLogger: (any FeatureScope) -> any ExposureLogging let makeEvaluationLogger: (any FeatureScope) -> any EvaluationLogging - let makeRUMFlagEvaluationReporter: (any FeatureScope) -> any RUMFlagEvaluationReporting + let makeRUMFlagEvaluationReporter: () -> any RUMFlagEvaluationReporting let performanceOverride: PerformancePresetOverride? let issueReporter: IssueReporter private let evaluationAggregator: EvaluationAggregator? @@ -76,11 +76,11 @@ internal struct FlagsFeature: DatadogRemoteFeature { return EvaluationLogger(aggregator: aggregator) } - makeRUMFlagEvaluationReporter = { featureScope in + makeRUMFlagEvaluationReporter = { [messageBus = core.messageBus] in guard configuration.rumIntegrationEnabled else { return NOPRUMFlagEvaluationReporter() } - return RUMFlagEvaluationReporter(featureScope: featureScope) + return RUMFlagEvaluationReporter(messageBus: messageBus) } performanceOverride = PerformancePresetOverride(maxObjectsInFile: 50) diff --git a/DatadogFlags/Tests/Client/FlagsClientTests.swift b/DatadogFlags/Tests/Client/FlagsClientTests.swift index 4f33d9314f..b746c9a016 100644 --- a/DatadogFlags/Tests/Client/FlagsClientTests.swift +++ b/DatadogFlags/Tests/Client/FlagsClientTests.swift @@ -308,15 +308,18 @@ final class FlagsClientTests: XCTestCase { date: .mockAny() ) let data = try JSONEncoder().encode(initialState) - let messageReceiver = FeatureMessageReceiverMock() let core = SingleFeatureCoreMock( dataStore: DataStoreMock( storage: [ FlagsClient.defaultName: .value(data, dataStoreDefaultKeyVersion) ] - ), - messageReceiver: messageReceiver + ) ) + var rumMessages: [RUMFlagEvaluationMessage] = [] + let subscription = core.messageBus.subscribe { (msg: RUMFlagEvaluationMessage, _) in + rumMessages.append(msg) + } + defer { core.messageBus.unsubscribe(subscription) } // When Flags.enable(with: .init(trackExposures: false), in: core) @@ -326,7 +329,7 @@ final class FlagsClientTests: XCTestCase { // Then XCTAssertEqual(core.events(ofType: ExposureEvent.self).count, 0, "No exposure events should be written") - XCTAssertEqual(messageReceiver.messages.filter(\.isRUMMessage).count, 1, "RUM integration should still work") + XCTAssertEqual(rumMessages.count, 1, "RUM integration should still work") } func testRUMIntegrationDisabled() throws { @@ -337,15 +340,18 @@ final class FlagsClientTests: XCTestCase { date: .mockAny() ) let data = try JSONEncoder().encode(initialState) - let messageReceiver = FeatureMessageReceiverMock() let core = SingleFeatureCoreMock( dataStore: DataStoreMock( storage: [ FlagsClient.defaultName: .value(data, dataStoreDefaultKeyVersion) ] - ), - messageReceiver: messageReceiver + ) ) + var rumMessages: [RUMFlagEvaluationMessage] = [] + let subscription = core.messageBus.subscribe { (msg: RUMFlagEvaluationMessage, _) in + rumMessages.append(msg) + } + defer { core.messageBus.unsubscribe(subscription) } // When Flags.enable(with: .init(rumIntegrationEnabled: false), in: core) @@ -355,7 +361,7 @@ final class FlagsClientTests: XCTestCase { // Then XCTAssertEqual(core.events(ofType: ExposureEvent.self).count, 1, "Exposure should still be logged") - XCTAssertEqual(messageReceiver.messages.filter(\.isRUMMessage).count, 0, "No RUM messages should be sent") + XCTAssertEqual(rumMessages.count, 0, "No RUM messages should be sent") } // MARK: - Internal methods consumed by the React Native SDK diff --git a/DatadogFlags/Tests/Client/RUMFlagEvaluationReporterTests.swift b/DatadogFlags/Tests/Client/RUMFlagEvaluationReporterTests.swift index c28c9fa895..2508fe25e2 100644 --- a/DatadogFlags/Tests/Client/RUMFlagEvaluationReporterTests.swift +++ b/DatadogFlags/Tests/Client/RUMFlagEvaluationReporterTests.swift @@ -10,12 +10,20 @@ import DatadogInternal @testable import DatadogFlags -final class RUMFlagEvaluationReporterTests: XCTestCase { - private let featureScope = FeatureScopeMock() +private final class RUMFlagEvaluationRecorder: BusMessageReceiver { + private(set) var messages: [RUMFlagEvaluationMessage] = [] + func receive(message: RUMFlagEvaluationMessage, from core: DatadogCoreProtocol) { + messages.append(message) + } +} +final class RUMFlagEvaluationReporterTests: XCTestCase { func testSendFlagEvaluation() throws { // Given - let reporter = RUMFlagEvaluationReporter(featureScope: featureScope) + let core = PassthroughCoreMock() + let recorder = RUMFlagEvaluationRecorder() + core.subscribe(receiver: recorder) + let reporter = RUMFlagEvaluationReporter(messageBus: core.messageBus) // When reporter.sendFlagEvaluation( @@ -24,10 +32,9 @@ final class RUMFlagEvaluationReporterTests: XCTestCase { ) // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 1, "Should send flag evaluation message") + XCTAssertEqual(recorder.messages.count, 1, "Should send flag evaluation message") - let flagEvaluation = try XCTUnwrap(messages.firstPayload as? RUMFlagEvaluationMessage) + let flagEvaluation = try XCTUnwrap(recorder.messages.first) XCTAssertEqual(flagEvaluation.flagKey, "feature-flag") XCTAssertEqual(flagEvaluation.value as? Bool, true) } From a9385569cd75678d14f6a4c4022eea930d45d62e Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:17:03 +0200 Subject: [PATCH 6/6] [chain-pr 8/10] DatadogRUM: typed-bus migration and TelemetryInterceptor merge --- .../Datadog/Mocks/RUM/RUMFeatureMocks.swift | 6 +- .../CrashReportReceiverTests.swift | 185 ++++++++---------- DatadogRUM/Sources/Feature/RUMFeature.swift | 95 +++++---- .../WatchdogTerminationMonitor.swift | 25 +-- .../Integrations/CrashReportReceiver.swift | 29 ++- .../Integrations/ErrorMessageReceiver.swift | 15 +- .../FatalErrorContextNotifier.swift | 14 +- .../Integrations/FlagEvaluationReceiver.swift | 14 +- .../Integrations/TelemetryReceiver.swift | 40 ++-- .../Integrations/WebViewEventReceiver.swift | 18 +- DatadogRUM/Sources/RUM.swift | 11 ++ .../AppHangs/AppHangsMonitorTests.swift | 2 +- .../WatchdogTerminationMonitorTests.swift | 4 +- .../ErrorMessageReceiverTests.swift | 46 ++--- .../FlagEvaluationReceiverTests.swift | 12 +- .../Integrations/TelemetryReceiverTests.swift | 101 +++++++--- .../WebViewEventReceiverTests.swift | 59 ++---- .../FatalErrorContextNotifierTests.swift | 61 +++--- DatadogRUM/Tests/RUMTests.swift | 18 +- .../Mocks/DatadogRUM/RUMFeatureMocks.swift | 17 +- 20 files changed, 372 insertions(+), 400 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUM/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUM/RUMFeatureMocks.swift index 906b577319..3a38c8f6ca 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUM/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUM/RUMFeatureMocks.swift @@ -33,8 +33,8 @@ extension RUM.Configuration { } extension WebViewEventReceiver: AnyMockable { - public static func mockAny() -> Self { - .mockWith() + public static func mockAny() -> WebViewEventReceiver { + mockWith() } static func mockWith( @@ -42,7 +42,7 @@ extension WebViewEventReceiver: AnyMockable { dateProvider: DateProvider = SystemDateProvider(), commandSubscriber: RUMCommandSubscriber = RUMCommandSubscriberMock(), viewCache: ViewCache = ViewCache(dateProvider: SystemDateProvider()) - ) -> Self { + ) -> WebViewEventReceiver { .init( featureScope: featureScope, dateProvider: dateProvider, diff --git a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift index 7e96c3635e..f090a1be32 100644 --- a/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift +++ b/DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift @@ -18,16 +18,13 @@ class CrashReportReceiverTests: XCTestCase { let receiver: CrashReportReceiver = .mockWith(featureScope: featureScope) // When - let message: FeatureMessage = .payload( - Crash( - report: DDCrashReport.mockAny(), - context: CrashContext.mockWith(lastRUMViewEvent: nil) - ) + let message = Crash( + report: DDCrashReport.mockAny(), + context: CrashContext.mockWith(lastRUMViewEvent: nil) ) - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: message, from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(featureScope.eventsWritten(ofType: RUMErrorEvent.self).count, 1, "It should send error event") } @@ -37,18 +34,15 @@ class CrashReportReceiverTests: XCTestCase { let lastRUMViewEvent: RUMViewEvent = .mockRandom() // When - let message: FeatureMessage = .payload( - Crash( - report: DDCrashReport.mockWith(date: Date()), - context: CrashContext.mockWith( - lastRUMViewEvent: lastRUMViewEvent - ) + let message = Crash( + report: DDCrashReport.mockWith(date: Date()), + context: CrashContext.mockWith( + lastRUMViewEvent: lastRUMViewEvent ) ) - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: message, from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(featureScope.eventsWritten(ofType: RUMErrorEvent.self).count, 1, "It should send error event") XCTAssertEqual(featureScope.eventsWritten(ofType: RUMViewEvent.self).count, 1, "It should send view event") } @@ -77,10 +71,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -111,10 +104,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertFalse( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -143,10 +135,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -182,10 +173,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -220,10 +210,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -252,10 +241,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertFalse( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -282,10 +270,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -318,10 +305,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -351,10 +337,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -452,10 +437,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -534,10 +518,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -579,10 +562,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -652,10 +634,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -733,10 +714,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -766,10 +746,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -842,10 +821,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -978,10 +956,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1055,10 +1032,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1123,10 +1099,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1194,10 +1169,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1317,10 +1291,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1394,10 +1367,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then @@ -1477,10 +1449,9 @@ class CrashReportReceiverTests: XCTestCase { ) // When - XCTAssertTrue( - receiver.receive(message: .payload( - Crash(report: crashReport, context: crashContext) - ), from: NOPDatadogCore()) + receiver.receive( + message: Crash(report: crashReport, context: crashContext), + from: NOPDatadogCore() ) // Then diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 2a1b118c1c..b458fae2af 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -15,6 +15,18 @@ internal final class RUMFeature: DatadogRemoteFeature, RUMSessionSamplerProvider let messageReceiver: FeatureMessageReceiver + let crashReportReceiver: CrashReportReceiver + + let errorMessageReceiver: ErrorMessageReceiver + + let flagEvaluationReceiver: FlagEvaluationReceiver + + let telemetryReceiver: TelemetryReceiver + + let webViewEventReceiver: WebViewEventReceiver + + let watchdogTerminationMonitor: WatchdogTerminationMonitor? + let monitor: Monitor let instrumentation: RUMInstrumentation @@ -158,7 +170,7 @@ internal final class RUMFeature: DatadogRemoteFeature, RUMSessionSamplerProvider accessibilityReader: accessibilityReader, onSessionUpdate: onSessionUpdate, viewCache: ViewCache(dateProvider: configuration.dateProvider), - fatalErrorContext: FatalErrorContextNotifier(messageBus: featureScope), + fatalErrorContext: FatalErrorContextNotifier(messageBus: core.messageBus), sessionEndedMetric: sessionEndedMetric, viewEndedMetricFactory: { let viewEndedController = ViewEndedController( @@ -268,49 +280,48 @@ internal final class RUMFeature: DatadogRemoteFeature, RUMSessionSamplerProvider telemetry: core.telemetry ) - var messageReceivers: [FeatureMessageReceiver] = [ - TelemetryInterceptor(sessionEndedMetric: sessionEndedMetric), - TelemetryReceiver( - featureScope: featureScope, - dateProvider: configuration.dateProvider, - sampler: Sampler(samplingRate: configuration.telemetrySampleRate), - configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate) - ), - ErrorMessageReceiver( - featureScope: featureScope, - monitor: monitor - ), - FlagEvaluationReceiver(monitor: monitor), - WebViewEventReceiver( - featureScope: featureScope, - dateProvider: configuration.dateProvider, - commandSubscriber: monitor, - viewCache: dependencies.viewCache - ), - CrashReportReceiver( - featureScope: featureScope, - applicationID: configuration.applicationID, - dateProvider: configuration.dateProvider, - sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate), - trackBackgroundEvents: configuration.trackBackgroundEvents, - uuidGenerator: configuration.uuidGenerator, - ciTest: configuration.ciTestExecutionID.map { RUMCITest(testExecutionId: $0) }, - syntheticsTest: { - if let testId = configuration.syntheticsTestId, let resultId = configuration.syntheticsResultId { - return RUMSyntheticsTest(injected: nil, resultId: resultId, testId: testId, syntheticsInfo: [:]) - } else { - return nil - } - }(), - eventsMapper: eventsMapper - ) - ] + self.crashReportReceiver = CrashReportReceiver( + featureScope: featureScope, + applicationID: configuration.applicationID, + dateProvider: configuration.dateProvider, + sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate), + trackBackgroundEvents: configuration.trackBackgroundEvents, + uuidGenerator: configuration.uuidGenerator, + ciTest: configuration.ciTestExecutionID.map { RUMCITest(testExecutionId: $0) }, + syntheticsTest: { + if let testId = configuration.syntheticsTestId, let resultId = configuration.syntheticsResultId { + return RUMSyntheticsTest(injected: nil, resultId: resultId, testId: testId, syntheticsInfo: [:]) + } else { + return nil + } + }(), + eventsMapper: eventsMapper + ) - if let watchdogTermination = watchdogTermination { - messageReceivers.append(watchdogTermination) - } + self.errorMessageReceiver = ErrorMessageReceiver( + featureScope: featureScope, + monitor: monitor + ) + + self.flagEvaluationReceiver = FlagEvaluationReceiver(monitor: monitor) + + self.telemetryReceiver = TelemetryReceiver( + featureScope: featureScope, + dateProvider: configuration.dateProvider, + sampler: Sampler(samplingRate: configuration.telemetrySampleRate), + configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate), + sessionEndedMetric: sessionEndedMetric + ) + + self.webViewEventReceiver = WebViewEventReceiver( + featureScope: featureScope, + dateProvider: configuration.dateProvider, + commandSubscriber: monitor, + viewCache: dependencies.viewCache + ) - self.messageReceiver = CombinedFeatureMessageReceiver(messageReceivers) + self.watchdogTerminationMonitor = watchdogTermination + self.messageReceiver = NOPFeatureMessageReceiver() // Forward instrumentation calls to monitor: instrumentation.publish(to: monitor) diff --git a/DatadogRUM/Sources/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitor.swift b/DatadogRUM/Sources/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitor.swift index 541b62a495..6d01d8d040 100644 --- a/DatadogRUM/Sources/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitor.swift +++ b/DatadogRUM/Sources/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitor.swift @@ -139,36 +139,21 @@ extension WatchdogTerminationMonitor: Flushable { } } -extension WatchdogTerminationMonitor: FeatureMessageReceiver { - /// Receives the feature message and updates the app state based on the context message. - /// It relies on `ApplicationStatePublisher` context message to update the app state. - /// Other messages are ignored. - /// - Parameters: - /// - message: The feature message. - /// - core: The core instance. - /// - Returns: Always `false`, because it doesn't block the message propagation. - func receive(message: DatadogInternal.FeatureMessage, from core: any DatadogInternal.DatadogCoreProtocol) -> Bool { - guard case .context(let context) = message else { - return false - } - +extension WatchdogTerminationMonitor: BusMessageReceiver { + /// Receives DatadogContext updates and tracks app state for watchdog termination detection. + func receive(message context: DatadogContext, from core: any DatadogCoreProtocol) { if currentState == .stopped { guard let launchReport = context.additionalContext(ofType: LaunchReport.self) else { - return false + return } - self.start(launchReport: launchReport) } - // Once the monitor has started, ie watchdog termination check has been done - // we can start updating the app state based on the context message guard currentState == .started else { - return false + return } let state = context.applicationStateHistory.currentState appStateManager.updateAppState(state: state) - - return false } } diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index f2fd603ecf..8a36301cf4 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -8,7 +8,7 @@ import Foundation import DatadogInternal /// Receiver to consume crash reports as RUM events. -internal struct CrashReportReceiver: FeatureMessageReceiver { +internal final class CrashReportReceiver: BusMessageReceiver { private struct AdjustedCrashTimings { /// Crash date read from `CrashReport`. It uses device time. let crashDate: Date @@ -57,15 +57,11 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { self.eventsMapper = eventsMapper } - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .payload(crash as Crash) = message else { - return false - } - - return send(report: crash.report, with: crash.context) + func receive(message: Crash, from core: DatadogCoreProtocol) { + send(report: message.report, with: message.context) } - private func send(report: DDCrashReport, with context: CrashContext) -> Bool { + private func send(report: DDCrashReport, with context: CrashContext) { // 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. @@ -100,18 +96,17 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { ) } 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 } - return true + return } if let lastRUMSessionState = context.lastRUMSessionState { sendCrashReportToPreviousSession(report, crashContext: context, lastRUMSessionStateInPreviousSession: lastRUMSessionState, using: adjustedCrashTimings) - return true + return } - return sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings) + sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings) } /// If the crash occurred in an existing RUM session and we know its `lastRUMViewEvent` we send the error using that session UUID and link @@ -128,7 +123,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // To avoid inconsistency, we only send the RUM error. DD.logger.debug("Sending crash as RUM error.") featureScope.eventWriteContext(bypassConsent: true) { context, writer in - let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) + let builder = self.createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) let rumError = builder.createRUMError(with: lastRUMViewEvent) if let mappedError = self.eventsMapper.map(event: rumError) { @@ -200,7 +195,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { _ crashReport: DDCrashReport, crashContext: CrashContext, using crashTimings: AdjustedCrashTimings - ) -> Bool { + ) { let sessionID = uuidGenerator.generateUnique() let sampled = DeterministicSampler( uuid: sessionID.rawValue, @@ -209,7 +204,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { guard sampled else { DD.logger.debug("There was a crash in previous session, but it is ignored due to sampling.") - return false + return } // We can ignore `sessionState` for building the rule as we can assume there was no session sent - otherwise, @@ -258,8 +253,6 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { if let newRUMView = newRUMView { send(crashReport: crashReport, to: newRUMView, using: crashTimings) } - - return true } /// Sends given `CrashReport` by linking it to given `rumView` and updating view counts accordingly. @@ -269,7 +262,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // 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. featureScope.eventWriteContext(bypassConsent: true) { context, writer in - let builder = createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) + let builder = self.createFatalErrorBuilder(context: context, crash: crashReport, crashDate: crashTimings.realCrashDate, timeSinceAppStart: crashTimings.timeSinceAppStart) let updatedRUMView = builder.updateRUMViewWithError(rumView) let rumError = builder.createRUMError(with: updatedRUMView) diff --git a/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift b/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift index eac7b1553f..725798a98b 100644 --- a/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift +++ b/DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift @@ -7,17 +7,18 @@ import Foundation import DatadogInternal -internal struct ErrorMessageReceiver: FeatureMessageReceiver { +internal final class ErrorMessageReceiver: BusMessageReceiver { /// RUM feature scope. let featureScope: FeatureScope let monitor: Monitor - /// Adds RUM Error with given message and stack to current RUM View. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .payload(error as RUMErrorMessage) = message else { - return false - } + init(featureScope: FeatureScope, monitor: Monitor) { + self.featureScope = featureScope + self.monitor = monitor + } + /// Adds RUM Error with given message and stack to current RUM View. + func receive(message error: RUMErrorMessage, from core: DatadogCoreProtocol) { monitor._internal?.addError( at: error.time, message: error.message, @@ -28,7 +29,5 @@ internal struct ErrorMessageReceiver: FeatureMessageReceiver { attributes: error.attributes, binaryImages: error.binaryImages ) - - return true } } diff --git a/DatadogRUM/Sources/Integrations/FatalErrorContextNotifier.swift b/DatadogRUM/Sources/Integrations/FatalErrorContextNotifier.swift index 6f30387440..bab4aa9067 100644 --- a/DatadogRUM/Sources/Integrations/FatalErrorContextNotifier.swift +++ b/DatadogRUM/Sources/Integrations/FatalErrorContextNotifier.swift @@ -23,10 +23,10 @@ internal protocol FatalErrorContextNotifying: AnyObject { /// Manages RUM information necessary for building context of fatal errors such as Crashes or Fatal App Hangs. /// It tracks value changes and notifies updates on message bus. internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { - /// Message bus interface to send context updates to Crash Reporting. - private let messageBus: MessageSending + /// Typed message bus to send context updates to Crash Reporting. + private let messageBus: MessageBus - init(messageBus: MessageSending) { + init(messageBus: MessageBus) { self.messageBus = messageBus } @@ -36,7 +36,7 @@ internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { var sessionState: RUMSessionState? { didSet { if let sessionState { - messageBus.send(message: .payload(sessionState)) + messageBus.send(message: sessionState) } } } @@ -47,9 +47,9 @@ internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { var view: RUMViewEvent? { didSet { if let view { - messageBus.send(message: .payload(view)) + messageBus.send(message: view) } else { - messageBus.send(message: .payload(RUMPayloadMessages.viewReset)) + messageBus.send(message: RUMViewReset()) } } } @@ -57,7 +57,7 @@ internal final class FatalErrorContextNotifier: FatalErrorContextNotifying { @ReadWriteLock var globalAttributes: [String: Encodable] = [:] { didSet { - messageBus.send(message: .payload(RUMEventAttributes(contextInfo: globalAttributes))) + messageBus.send(message: RUMEventAttributes(contextInfo: globalAttributes)) } } } diff --git a/DatadogRUM/Sources/Integrations/FlagEvaluationReceiver.swift b/DatadogRUM/Sources/Integrations/FlagEvaluationReceiver.swift index 953b1215a5..138f84cc58 100644 --- a/DatadogRUM/Sources/Integrations/FlagEvaluationReceiver.swift +++ b/DatadogRUM/Sources/Integrations/FlagEvaluationReceiver.swift @@ -8,21 +8,19 @@ import Foundation import DatadogInternal /// Receives flag evaluation messages and adds them to RUM. -internal struct FlagEvaluationReceiver: FeatureMessageReceiver { +internal final class FlagEvaluationReceiver: BusMessageReceiver { /// The RUM monitor instance. let monitor: Monitor - /// Adds feature flag evaluation to the current RUM view. - func receive(message: FeatureMessage, from core: any DatadogCoreProtocol) -> Bool { - guard case let .payload(flagEvaluation as RUMFlagEvaluationMessage) = message else { - return false - } + init(monitor: Monitor) { + self.monitor = monitor + } + /// Adds feature flag evaluation to the current RUM view. + func receive(message flagEvaluation: RUMFlagEvaluationMessage, from core: any DatadogCoreProtocol) { monitor.addFeatureFlagEvaluation( name: flagEvaluation.flagKey, value: flagEvaluation.value ) - - return true } } diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 3fe6690b8b..d1d38e75bf 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -7,7 +7,7 @@ import Foundation import DatadogInternal -internal final class TelemetryReceiver: FeatureMessageReceiver { +internal final class TelemetryReceiver: BusMessageReceiver { /// Maximum number of telemetry events allowed per RUM sessions. static let maxEventsPerSessions: Int = 100 @@ -25,6 +25,9 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// Additional sampler for configuration telemetry events, applied in addition to the `sampler`. let configurationExtraSampler: Sampler + /// "RUM Session Ended" controller to count SDK errors and upload quality metrics. + let sessionEndedMetric: SessionEndedMetricController + /// Keeps track of current session @ReadWriteLock private var currentSessionID: String? @@ -44,47 +47,36 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - dateProvider: Current device time provider. /// - sampler: Telemetry events sampler. /// - configurationExtraSampler: Extra sampler for configuration events (applied on top of `sampler`). + /// - sessionEndedMetric: Controller for tracking session-ended metric data. init( featureScope: FeatureScope, dateProvider: DateProvider, sampler: Sampler, - configurationExtraSampler: Sampler + configurationExtraSampler: Sampler, + sessionEndedMetric: SessionEndedMetricController ) { self.featureScope = featureScope self.dateProvider = dateProvider self.sampler = sampler self.configurationExtraSampler = configurationExtraSampler + self.sessionEndedMetric = sessionEndedMetric } - /// Receives a message from the bus. - /// - /// The receiver will only consume `TelemetryMessage`. - /// - /// - Parameters: - /// - message: The message to consume. - /// - core: The core sending the message. - /// - Returns: `true` if the message is a `.telemetry` case. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .telemetry(telemetry) = message else { - return false - } - - return receive(telemetry: telemetry) - } - - /// Receives a Telemetry message from the bus. - /// - /// - Parameter telemetry: The telemetry message to consume. - /// - Returns: Always `true`. - private func receive(telemetry: TelemetryMessage) -> Bool { + func receive(message telemetry: TelemetryMessage, from core: DatadogCoreProtocol) { switch telemetry { case let .debug(id, message, attributes): debug(id: id, message: message, attributes: attributes) case let .error(id, message, kind, stack): + sessionEndedMetric.track(sdkErrorKind: kind, in: nil) error(id: id, message: message, kind: kind, stack: stack) case .configuration(let configuration): send(configuration: configuration) case let .metric(metric): + if metric.name == UploadQualityMetric.name { + sessionEndedMetric.track(uploadQuality: metric.attributes, in: nil) + // Upload quality metrics are intercepted for the session-ended metric and not forwarded. + return + } if sampled(event: metric) { send(metric: metric) } @@ -93,8 +85,6 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { send(usage: usage) } } - - return true } private func sampled(event: SampledTelemetry) -> Bool { diff --git a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift index ae92ca64fd..c85e7bbc5c 100644 --- a/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift +++ b/DatadogRUM/Sources/Integrations/WebViewEventReceiver.swift @@ -10,7 +10,7 @@ import DatadogInternal internal typealias JSON = [String: Any] /// Receiver to consume a RUM event coming from Browser SDK. -internal final class WebViewEventReceiver: FeatureMessageReceiver { +internal final class WebViewEventReceiver: BusMessageReceiver { /// RUM feature scope. let featureScope: FeatureScope /// Subscriber that can process a `RUMKeepSessionAliveCommand`. @@ -48,17 +48,13 @@ internal final class WebViewEventReceiver: FeatureMessageReceiver { /// - Parameters: /// - message: The message containing the Browser RUM event. /// - core: The core to write the event. - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - switch message { - case let .webview(.rum(event)): - receive(rum: event, core: core) - case let .webview(.telemetry(event)): - receive(telemetry: event) - default: - return false + func receive(message: WebViewRUMMessage, from core: DatadogCoreProtocol) { + switch message.kind { + case .rum: + receive(rum: message.event, core: core) + case .telemetry: + receive(telemetry: message.event) } - - return true } private func receive(rum event: JSON, core: DatadogCoreProtocol) { diff --git a/DatadogRUM/Sources/RUM.swift b/DatadogRUM/Sources/RUM.swift index 171953d2b7..614a1772e7 100644 --- a/DatadogRUM/Sources/RUM.swift +++ b/DatadogRUM/Sources/RUM.swift @@ -43,6 +43,17 @@ public enum RUM { // Register RUM feature: let rum = try RUMFeature(in: core, configuration: configuration) + + // Subscribe typed-bus receivers before registration so initial context push is received: + core.messageBus.subscribe(receiver: rum.crashReportReceiver) + core.messageBus.subscribe(receiver: rum.errorMessageReceiver) + core.messageBus.subscribe(receiver: rum.flagEvaluationReceiver) + core.messageBus.subscribe(receiver: rum.telemetryReceiver) + core.messageBus.subscribe(receiver: rum.webViewEventReceiver) + if let watchdog = rum.watchdogTerminationMonitor { + core.messageBus.subscribe(receiver: watchdog) + } + try core.register(feature: rum) // If resource tracking is configured, register URLSessionHandler to enable network instrumentation: diff --git a/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift index f8af81ce34..284ecb5d37 100644 --- a/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift +++ b/DatadogRUM/Tests/Instrumentation/AppHangs/AppHangsMonitorTests.swift @@ -24,7 +24,7 @@ private class WatchdogThreadMock: AppHangsObservingThread { class AppHangsMonitorTests: XCTestCase { private let featureScope = FeatureScopeMock() private let watchdogThread = WatchdogThreadMock() - private let fatalErrorContext = FatalErrorContextNotifier(messageBus: NOPFeatureScope()) + private let fatalErrorContext = FatalErrorContextNotifier(messageBus: NOPMessageBus()) private let currentProcessID = UUID() private let dateProvider = DateProviderMock() private let uuidGenerator = RUMUUIDGeneratorMock() diff --git a/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift b/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift index 9c82ea39a1..461a07ad10 100644 --- a/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift +++ b/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift @@ -40,7 +40,7 @@ final class WatchdogTerminationMonitorTests: XCTestCase { sut.update(viewEvent: viewEvent1) // monitor reveives the launch report - _ = sut.receive(message: .context(featureScope.contextMock), from: NOPDatadogCore()) + sut.receive(message: featureScope.contextMock, from: NOPDatadogCore()) // Flush the queue to ensure the monitor has transitioned to `.started` // before updating the view event. Without this, the update would be @@ -69,7 +69,7 @@ final class WatchdogTerminationMonitorTests: XCTestCase { sut.update(viewEvent: viewEvent3) // monitor reveives the launch report - _ = sut.receive(message: .context(featureScope.contextMock), from: NOPDatadogCore()) + sut.receive(message: featureScope.contextMock, from: NOPDatadogCore()) waitForExpectations(timeout: 1) XCTAssertEqual(reporter.sendParams?.viewEvent.view.id, viewEvent2.view.id) diff --git a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift index 3bc6c8fef2..9c47693320 100644 --- a/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/ErrorMessageReceiverTests.swift @@ -30,22 +30,19 @@ class ErrorMessageReceiverTests: XCTestCase { func testReceivePartialLogError() throws { // When - let message: FeatureMessage = .payload( - RUMErrorMessage( - time: Date(), - message: "message-test", - source: "logger", - type: nil, - stack: nil, - attributes: [:], - binaryImages: nil - ) + let message = RUMErrorMessage( + time: Date(), + message: "message-test", + source: "logger", + type: nil, + stack: nil, + attributes: [:], + binaryImages: nil ) - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: message, from: NOPDatadogCore()) // 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.source, .logger) @@ -55,25 +52,22 @@ class ErrorMessageReceiverTests: XCTestCase { // Given let mockAttribute: String = .mockRandom() let mockBinaryImage: BinaryImage = .mockRandom() - let message: FeatureMessage = .payload( - RUMErrorMessage( - time: Date(), - message: "message-test", - source: "custom", - type: "type-test", - stack: "stack-test", - attributes: [ - "any-key": mockAttribute - ], - binaryImages: [mockBinaryImage] - ) + let message = RUMErrorMessage( + time: Date(), + message: "message-test", + source: "custom", + type: "type-test", + stack: "stack-test", + attributes: [ + "any-key": mockAttribute + ], + binaryImages: [mockBinaryImage] ) // When - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: message, from: NOPDatadogCore()) // 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") diff --git a/DatadogRUM/Tests/Integrations/FlagEvaluationReceiverTests.swift b/DatadogRUM/Tests/Integrations/FlagEvaluationReceiverTests.swift index 982fa16a76..6b2122e7b9 100644 --- a/DatadogRUM/Tests/Integrations/FlagEvaluationReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/FlagEvaluationReceiverTests.swift @@ -21,19 +21,15 @@ class FlagEvaluationReceiverTests: XCTestCase { dateProvider: SystemDateProvider() ) ) - let message: FeatureMessage = .payload( - RUMFlagEvaluationMessage( - flagKey: "feature-flag", - value: true - ) + let message = RUMFlagEvaluationMessage( + flagKey: "feature-flag", + value: true ) // When - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: message, from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must accept the message") - let viewEvents: [RUMViewEvent] = featureScope.eventsWritten() XCTAssertFalse(viewEvents.isEmpty, "It should write a view event") diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 9fb3bb32f3..b72ea27448 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -6,11 +6,12 @@ import XCTest import TestUtilities -import DatadogInternal +@testable import DatadogInternal @testable import DatadogRUM /// Utility to access the convenience methods defined in `Telemetry` protocol extension while sending telemetry to tested receiver. -private struct TelemetryMock: Telemetry { +/// Routes `Telemetry` calls directly to a `TelemetryReceiver` for unit testing. +private struct TelemetryReceiverProxy: Telemetry { let receiver: TelemetryReceiver init(with receiver: TelemetryReceiver) { @@ -18,8 +19,7 @@ private struct TelemetryMock: Telemetry { } func send(telemetry: TelemetryMessage) { - let result = receiver.receive(message: .telemetry(telemetry), from: NOPDatadogCore()) - XCTAssertTrue(result, "It must accept every message") + receiver.receive(message: telemetry, from: NOPDatadogCore()) } } @@ -44,7 +44,7 @@ class TelemetryReceiverTests: XCTestCase { ) // When - TelemetryMock(with: receiver).debug("Hello world!", attributes: ["foo": 42]) + TelemetryReceiverProxy(with: receiver).debug("Hello world!", attributes: ["foo": 42]) // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first @@ -73,7 +73,7 @@ class TelemetryReceiverTests: XCTestCase { ) // When - TelemetryMock(with: receiver).error("Oops", kind: "OutOfMemory", stack: "a\nhay\nneedle\nstack") + TelemetryReceiverProxy(with: receiver).error("Oops", kind: "OutOfMemory", stack: "a\nhay\nneedle\nstack") // Then let event = featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).first @@ -94,7 +94,7 @@ class TelemetryReceiverTests: XCTestCase { let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - TelemetryMock(with: receiver).debug("telemetry debug", attributes: ["foo": 42]) + TelemetryReceiverProxy(with: receiver).debug("telemetry debug", attributes: ["foo": 42]) // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first @@ -114,7 +114,7 @@ class TelemetryReceiverTests: XCTestCase { let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - TelemetryMock(with: receiver).error("telemetry error") + TelemetryReceiverProxy(with: receiver).error("telemetry error") // Then let event = featureScope.eventsWritten(ofType: TelemetryErrorEvent.self).first @@ -135,7 +135,7 @@ class TelemetryReceiverTests: XCTestCase { let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, dateProvider: dateProvider) // When - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) telemetry.debug("Debug", attributes: ["foo": 1]) telemetry.error("Error") telemetry.metric(name: "Metric", attributes: ["bar": 2], sampleRate: 100) @@ -161,7 +161,7 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetry_discardDuplicates() throws { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When telemetry.debug(id: "0", message: "telemetry debug 0") @@ -195,7 +195,7 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetry_toSessionLimit() throws { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sampler: .mockKeepAll()) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When // sends 101 telemetry events @@ -220,7 +220,7 @@ class TelemetryReceiverTests: XCTestCase { func testSampledTelemetry_rejectAll() throws { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sampler: .mockRejectAll()) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When // sends 10 telemetry events @@ -247,7 +247,7 @@ class TelemetryReceiverTests: XCTestCase { sampler: .mockKeepAll(), configurationExtraSampler: .mockRejectAll() ) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When for index in 0..<10 { @@ -269,7 +269,7 @@ class TelemetryReceiverTests: XCTestCase { featureScope: featureScope, sampler: .mockKeepAll() ) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When for index in 0..<10 { @@ -288,7 +288,7 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetry_resetAfterSessionExpire() throws { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) let applicationId: String = .mockRandom() featureScope.contextMock.set( @@ -327,7 +327,7 @@ class TelemetryReceiverTests: XCTestCase { featureScope: featureScope, dateProvider: RelativeDateProvider(using: .init(timeIntervalSince1970: 0)) ) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) let backgroundTasksEnabled: Bool? = .mockRandom() let batchProcessingLevel: Int64? = .mockRandom() @@ -433,7 +433,7 @@ class TelemetryReceiverTests: XCTestCase { func testSendTelemetryConfiguration_trackResourceHeaders() { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) let trackResourceHeaders: TelemetryConfigurationEvent.Telemetry.Configuration.TrackResourceHeaders = [.defaultHeaders, .custom].randomElement()! // When @@ -466,7 +466,7 @@ class TelemetryReceiverTests: XCTestCase { // When let randomName: String = .mockRandom() let randomAttributes = mockRandomAttributes() - TelemetryMock(with: receiver).metric(name: randomName, attributes: randomAttributes, sampleRate: 100) + TelemetryReceiverProxy(with: receiver).metric(name: randomName, attributes: randomAttributes, sampleRate: 100) // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first @@ -501,7 +501,7 @@ class TelemetryReceiverTests: XCTestCase { let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) // When - TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: mockRandomAttributes(), sampleRate: 100) + TelemetryReceiverProxy(with: receiver).metric(name: .mockRandom(), attributes: mockRandomAttributes(), sampleRate: 100) // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first @@ -530,7 +530,7 @@ class TelemetryReceiverTests: XCTestCase { // When var attributes = mockRandomAttributes() attributes[SDKMetricFields.sessionIDOverrideKey] = sessionIDOverride - TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: attributes, sampleRate: 100) + TelemetryReceiverProxy(with: receiver).metric(name: .mockRandom(), attributes: attributes, sampleRate: 100) // Then let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first @@ -547,7 +547,7 @@ class TelemetryReceiverTests: XCTestCase { let osMock: OperatingSystem = .mockRandom() featureScope.contextMock = .mockWith(device: deviceMock, os: osMock) let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When let operationName = String.mockRandom() @@ -581,7 +581,7 @@ class TelemetryReceiverTests: XCTestCase { featureScope: featureScope, dateProvider: RelativeDateProvider(using: .init(timeIntervalSince1970: 0)) ) - let telemetry = TelemetryMock(with: receiver) + let telemetry = TelemetryReceiverProxy(with: receiver) // When let trace = telemetry.startMethodCalled( @@ -604,11 +604,7 @@ class TelemetryReceiverTests: XCTestCase { let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sampler: .mockKeepAll()) // When - let result = receiver.receive( - message: .telemetry(.usage(.init(event: .trackWebView, sampleRate: 100))), - from: NOPDatadogCore() - ) - XCTAssertTrue(result) + receiver.receive(message: .usage(.init(event: .trackWebView, sampleRate: 100)), from: NOPDatadogCore()) // Then let event = featureScope.eventsWritten(ofType: TelemetryUsageEvent.self).first @@ -623,4 +619,55 @@ class TelemetryReceiverTests: XCTestCase { return } } + + // MARK: - Interceptor behaviour (merged from TelemetryInterceptorTests) + + func testWhenInterceptingErrorTelemetry_itUpdatesSessionEndedMetric() throws { + let telemetry = TelemetryMock() + let sessionID: RUMUUID = .mockRandom() + let metricController = SessionEndedMetricController( + telemetry: telemetry, + sampleRate: 100, + tracksBackgroundEvents: .mockAny(), + isUsingSceneLifecycle: .mockAny() + ) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sessionEndedMetric: metricController) + + metricController.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockAny()) + receiver.receive( + message: .error(id: .mockAny(), message: .mockAny(), kind: .mockAny(), stack: .mockAny()), + from: NOPDatadogCore() + ) + + metricController.endMetric(sessionID: sessionID, with: .mockRandom()) + let metric = try XCTUnwrap(telemetry.messages.lastMetric(named: SessionEndedMetric.Constants.name)) + let rse = try XCTUnwrap(metric.attributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes) + XCTAssertEqual(rse.sdkErrorsCount.total, 1) + } + + func testWhenInterceptingUploadQualityMetric_itUpdatesSessionEndedMetricAndDoesNotForward() throws { + let telemetry = TelemetryMock() + let sessionID: RUMUUID = .mockRandom() + let metricController = SessionEndedMetricController( + telemetry: telemetry, + sampleRate: 100, + tracksBackgroundEvents: .mockAny(), + isUsingSceneLifecycle: .mockAny() + ) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, sessionEndedMetric: metricController) + + metricController.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockAny()) + receiver.receive( + message: .metric(MetricTelemetry(name: UploadQualityMetric.name, attributes: [UploadQualityMetric.track: "feature"], sampleRate: 100)), + from: NOPDatadogCore() + ) + + // Upload quality metric should NOT be forwarded as a RUM event + XCTAssertTrue(featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).isEmpty) + + metricController.endMetric(sessionID: sessionID, with: .mockRandom()) + let metric = try XCTUnwrap(telemetry.messages.lastMetric(named: SessionEndedMetric.Constants.name)) + let rse = try XCTUnwrap(metric.attributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes) + XCTAssertEqual(rse.uploadQuality["feature"]?.cycleCount, 1) + } } diff --git a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift index 6f799a694f..f35836b58e 100644 --- a/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/WebViewEventReceiverTests.swift @@ -16,12 +16,7 @@ class WebViewEventReceiverTests: XCTestCase { /// 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() } - /// Creates message sent from `DatadogWebViewTracking`. - private func webViewTrackingMessage(with webEvent: JSON) -> FeatureMessage { - return .webview(.rum(webEvent)) - } - - // MARK: - Handling `FeatureMessage` + // MARK: - Parsing Web Events func testParsingViewEvent() throws { // Given @@ -200,32 +195,13 @@ class WebViewEventReceiverTests: XCTestCase { ) // When - let message = webViewTrackingMessage(with: randomWebEvent()) - let result = receiver.receive(message: message, from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: randomWebEvent()), from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must acknowledge the message") let command = try XCTUnwrap(commandsSubscriberMock.receivedCommands.firstElement(of: RUMKeepSessionAliveCommand.self), "It must keep RUM session alive") XCTAssertEqual(command.time, .mockDecember15th2019At10AMUTC()) } - func testWhenReceivingOtherMessage_itRejectsIt() throws { - // Given - let receiver = WebViewEventReceiver( - featureScope: featureScope, - dateProvider: DateProviderMock(), - commandSubscriber: RUMCommandSubscriberMock(), - viewCache: ViewCache(dateProvider: SystemDateProvider()) - ) - - // When - let otherMessage: FeatureMessage = .payload(String.mockRandom()) - let result = receiver.receive(message: otherMessage, from: NOPDatadogCore()) - - // Then - XCTAssertFalse(result, "It must reject messages addressed to other receivers") - } - // MARK: - Modifying Web Events func testGivenRUMContextAvailable_whenReceivingWebEvent_itInjectRUMInfo() throws { @@ -258,8 +234,7 @@ class WebViewEventReceiverTests: XCTestCase { ].merging(random, uniquingKeysWith: { old, _ in old }) // When - - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then let expectedWebEventWritten: JSON = [ @@ -275,7 +250,6 @@ class WebViewEventReceiverTests: XCTestCase { "date": date + featureScope.contextMock.serverTimeOffset.dd.toInt64Milliseconds, ].merging(random, uniquingKeysWith: { old, _ in old }) - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must write web event to core") let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) DDAssertJSONEqual(AnyCodable(actualWebEventWritten), AnyCodable(expectedWebEventWritten)) @@ -293,10 +267,9 @@ class WebViewEventReceiverTests: XCTestCase { ) // When - let result = receiver.receive(message: webViewTrackingMessage(with: randomWebEvent()), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: randomWebEvent()), from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must accept the message") XCTAssertTrue(featureScope.eventsWritten.isEmpty, "The event must be dropped") } @@ -350,7 +323,7 @@ class WebViewEventReceiverTests: XCTestCase { ].merging(random, uniquingKeysWith: { old, _ in old }) // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then let expectedWebEventWritten: JSON = [ @@ -376,7 +349,6 @@ class WebViewEventReceiverTests: XCTestCase { "date": date + featureScope.contextMock.serverTimeOffset.dd.toInt64Milliseconds, ].merging(random, uniquingKeysWith: { old, _ in old }) - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must write web event to core") let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) DDAssertJSONEqual(AnyCodable(actualWebEventWritten), AnyCodable(expectedWebEventWritten)) @@ -431,7 +403,7 @@ class WebViewEventReceiverTests: XCTestCase { ].merging(random, uniquingKeysWith: { old, _ in old }) // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then let expectedWebEventWritten: JSON = [ @@ -452,7 +424,6 @@ class WebViewEventReceiverTests: XCTestCase { "date": date + featureScope.contextMock.serverTimeOffset.dd.toInt64Milliseconds, ].merging(random, uniquingKeysWith: { old, _ in old }) - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must write web event to core") let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) DDAssertJSONEqual(AnyCodable(actualWebEventWritten), AnyCodable(expectedWebEventWritten)) @@ -493,10 +464,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then - XCTAssertTrue(result) XCTAssertEqual(featureScope.eventsWritten.count, 1, "It must write web event to core") let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) let expectedWebEventWritten: JSON = [ @@ -542,10 +512,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then - XCTAssertTrue(result) XCTAssertEqual(featureScope.eventsWritten.count, 1) let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) let expectedWebEventWritten: JSON = [ @@ -615,10 +584,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: core) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: core) // Then - XCTAssertTrue(result) let events = core.waitAndReturnEvents(ofFeature: "rum", ofType: AnyEncodable.self) let actualWebEventWritten = try XCTUnwrap((events.first)?.value as? [String: Any]) let actualDD = try XCTUnwrap(actualWebEventWritten["_dd"] as? [String: Any]) @@ -680,10 +648,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: core) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: core) // Then - XCTAssertTrue(result) let events = core.waitAndReturnEvents(ofFeature: "rum", ofType: AnyEncodable.self) let actualWebEventWritten = try XCTUnwrap((events.first)?.value as? [String: Any]) let actualDD = try XCTUnwrap(actualWebEventWritten["_dd"] as? [String: Any]) @@ -743,10 +710,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then - XCTAssertTrue(result) let events = core.waitAndReturnEvents(ofFeature: "rum", ofType: AnyEncodable.self) let actualWebEventWritten = try XCTUnwrap((events.first)?.value as? [String: Any]) let actualDD = try XCTUnwrap(actualWebEventWritten["_dd"] as? [String: Any]) @@ -784,10 +750,9 @@ class WebViewEventReceiverTests: XCTestCase { ] // When - let result = receiver.receive(message: webViewTrackingMessage(with: webEventMock), from: NOPDatadogCore()) + receiver.receive(message: WebViewRUMMessage(kind: .rum, event: webEventMock), from: NOPDatadogCore()) // Then - XCTAssertTrue(result) XCTAssertEqual(featureScope.eventsWritten.count, 1) let actualWebEventWritten = try XCTUnwrap(featureScope.eventsWritten.first) let expectedWebEventWritten: JSON = [ diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift index e0f281ab83..ab9812f36b 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/FatalErrorContextNotifierTests.swift @@ -10,28 +10,40 @@ import TestUtilities @testable import DatadogRUM class FatalErrorContextNotifierTests: XCTestCase { - private let featureScope = FeatureScopeMock() + private var bus: PassthroughCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + bus = PassthroughCoreMock() + } + + override func tearDown() { + bus = nil + super.tearDown() + } // MARK: - Changing Session State func testWhenSessionStateIsSet_itSendsSessionStateMessage() throws { // Given - let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let fatalErrorContext = FatalErrorContextNotifier(messageBus: bus.messageBus) + var received: [RUMSessionState] = [] + _ = bus.messageBus.subscribe { (state: RUMSessionState, _) in received.append(state) } let newSessionState: RUMSessionState = .mockRandom() // When fatalErrorContext.sessionState = newSessionState // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 1) - let sessionStateMessage = try XCTUnwrap(messages.lastPayload as? RUMSessionState) - XCTAssertEqual(newSessionState, sessionStateMessage) + XCTAssertEqual(received.count, 1) + XCTAssertEqual(received.first, newSessionState) } func testWhenSessionStateIsReset_itDoesNotSendNextSessionStateMessage() throws { // Given - let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let fatalErrorContext = FatalErrorContextNotifier(messageBus: bus.messageBus) + var received: [RUMSessionState] = [] + _ = bus.messageBus.subscribe { (state: RUMSessionState, _) in received.append(state) } let originalSessionState: RUMSessionState = .mockRandom() fatalErrorContext.sessionState = originalSessionState @@ -39,58 +51,55 @@ class FatalErrorContextNotifierTests: XCTestCase { fatalErrorContext.sessionState = nil // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 1) - let sessionStateMessage = try XCTUnwrap(messages.lastPayload as? RUMSessionState) - XCTAssertEqual(originalSessionState, sessionStateMessage) + XCTAssertEqual(received.count, 1) + XCTAssertEqual(received.first, originalSessionState) } // MARK: - Changing View State func testWhenViewIsSet_itSendsViewEventMessage() throws { // Given - let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let fatalErrorContext = FatalErrorContextNotifier(messageBus: bus.messageBus) + var receivedViews: [RUMViewEvent] = [] + _ = bus.messageBus.subscribe { (event: RUMViewEvent, _) in receivedViews.append(event) } let newViewEvent: RUMViewEvent = .mockRandom() // When fatalErrorContext.view = newViewEvent // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 1) - let viewEventMessage = try XCTUnwrap(messages.firstPayload as? RUMViewEvent) - DDAssertJSONEqual(newViewEvent, viewEventMessage) + XCTAssertEqual(receivedViews.count, 1) + DDAssertJSONEqual(receivedViews.first, newViewEvent) } func testWhenViewIsReset_itSendsViewResetMessage() throws { // Given - let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let fatalErrorContext = FatalErrorContextNotifier(messageBus: bus.messageBus) + var resetCount = 0 + _ = bus.messageBus.subscribe { (_: RUMViewReset, _) in resetCount += 1 } fatalErrorContext.view = .mockRandom() // When fatalErrorContext.view = nil // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 2) - let viewEventMessage = try XCTUnwrap(messages.lastPayload as? String) - XCTAssertEqual(viewEventMessage, RUMPayloadMessages.viewReset) + XCTAssertEqual(resetCount, 1) } // MARK: - Changing Global Attributes func testWhenGlobalAttributesAreSet_itSendsAttributesMessage() throws { // Given - let fatalErrorContext = FatalErrorContextNotifier(messageBus: featureScope) + let fatalErrorContext = FatalErrorContextNotifier(messageBus: bus.messageBus) + var received: [RUMEventAttributes] = [] + _ = bus.messageBus.subscribe { (attrs: RUMEventAttributes, _) in received.append(attrs) } let newGlobalAttributes = mockRandomAttributes() // When fatalErrorContext.globalAttributes = newGlobalAttributes // Then - let messages = featureScope.messagesSent() - XCTAssertEqual(messages.count, 1) - let attributesMessage = try XCTUnwrap(messages.lastPayload as? RUMEventAttributes) - DDAssertJSONEqual(newGlobalAttributes, attributesMessage) + XCTAssertEqual(received.count, 1) + DDAssertJSONEqual(received.first, newGlobalAttributes) } } diff --git a/DatadogRUM/Tests/RUMTests.swift b/DatadogRUM/Tests/RUMTests.swift index ec422dfde0..83de7d4940 100644 --- a/DatadogRUM/Tests/RUMTests.swift +++ b/DatadogRUM/Tests/RUMTests.swift @@ -87,14 +87,14 @@ class RUMTests: XCTestCase { // Then let rum = try XCTUnwrap(core.get(feature: RUMFeature.self)) let monitor = try XCTUnwrap(RUMMonitor.shared(in: core) as? Monitor) - let telemetryReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: TelemetryReceiver.self) - let crashReportReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: CrashReportReceiver.self) + let telemetryReceiver: TelemetryReceiver? = rum.telemetryReceiver + let crashReportReceiver = rum.crashReportReceiver XCTAssertEqual(rum.performanceOverride?.maxFileAgeForRead, 24.hours) XCTAssertEqual(monitor.scopes.dependencies.rumApplicationID, applicationID) XCTAssertEqual(monitor.scopes.dependencies.samplingRate, 100) XCTAssertEqual(monitor.scopes.dependencies.sessionEndedMetric.sampleRate, 15) XCTAssertEqual(telemetryReceiver?.configurationExtraSampler.samplingRate, 20) - XCTAssertEqual(crashReportReceiver?.sessionSampler.samplingRate, 100) + XCTAssertEqual(crashReportReceiver.sessionSampler.samplingRate, 100) } #if !os(watchOS) @@ -321,9 +321,9 @@ class RUMTests: XCTestCase { // Then let rum = try XCTUnwrap(core.get(feature: RUMFeature.self)) let monitor = try XCTUnwrap(RUMMonitor.shared(in: core) as? Monitor) - let crashReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: CrashReportReceiver.self) + let crashReceiver = rum.crashReportReceiver XCTAssertEqual(monitor.scopes.dependencies.samplingRate, 100) - XCTAssertEqual(crashReceiver?.sessionSampler.samplingRate, 100) + XCTAssertEqual(crashReceiver.sessionSampler.samplingRate, 100) } func testWhenEnabledWithNoDebugSDKArgument() throws { @@ -338,9 +338,9 @@ class RUMTests: XCTestCase { // Then let rum = try XCTUnwrap(core.get(feature: RUMFeature.self)) let monitor = try XCTUnwrap(RUMMonitor.shared(in: core) as? Monitor) - let crashReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: CrashReportReceiver.self) + let crashReceiver = rum.crashReportReceiver XCTAssertEqual(monitor.scopes.dependencies.samplingRate, random) - XCTAssertEqual(crashReceiver?.sessionSampler.samplingRate, random) + XCTAssertEqual(crashReceiver.sessionSampler.samplingRate, random) } func testWhenEnabledWithDebugViewsArgument() { @@ -374,7 +374,7 @@ class RUMTests: XCTestCase { // Then let rum = try XCTUnwrap(core.get(feature: RUMFeature.self)) - let telemetryReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: TelemetryReceiver.self) + let telemetryReceiver: TelemetryReceiver? = rum.telemetryReceiver XCTAssertEqual(telemetryReceiver?.configurationExtraSampler.samplingRate, 42) } @@ -384,7 +384,7 @@ class RUMTests: XCTestCase { // Then let rum = try XCTUnwrap(core.get(feature: RUMFeature.self)) - let telemetryReceiver = (rum.messageReceiver as! CombinedFeatureMessageReceiver).receivers.firstElement(of: TelemetryReceiver.self) + let telemetryReceiver: TelemetryReceiver? = rum.telemetryReceiver XCTAssertEqual(telemetryReceiver?.configurationExtraSampler.samplingRate, 20) } diff --git a/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift b/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift index f3f5d7bfd0..57cbb08bfc 100644 --- a/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift +++ b/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift @@ -1585,19 +1585,26 @@ public class ContinuousVitalReaderMock: ContinuousVitalReader { #endif extension TelemetryReceiver: AnyMockable { - public static func mockAny() -> Self { .mockWith() } + public static func mockAny() -> TelemetryReceiver { mockWith() } public static func mockWith( featureScope: FeatureScope = NOPFeatureScope(), dateProvider: DateProvider = SystemDateProvider(), sampler: Sampler = .mockKeepAll(), - configurationExtraSampler: Sampler = .mockKeepAll() - ) -> Self { - .init( + configurationExtraSampler: Sampler = .mockKeepAll(), + sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController( + telemetry: NOPTelemetry(), + sampleRate: 100, + tracksBackgroundEvents: false, + isUsingSceneLifecycle: false + ) + ) -> TelemetryReceiver { + TelemetryReceiver( featureScope: featureScope, dateProvider: dateProvider, sampler: sampler, - configurationExtraSampler: configurationExtraSampler + configurationExtraSampler: configurationExtraSampler, + sessionEndedMetric: sessionEndedMetric ) } }