diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 703a35735f..686a91f529 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -23,16 +23,10 @@ 3C0D5DF62A5443B100446CF9 /* DataFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0D5DF42A5443B100446CF9 /* DataFormatTests.swift */; }; 3C1890152ABDE9BF00CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */; }; 3C1890162ABDE9C000CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */; }; - 3C2206F32AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */; }; - 3C2206F42AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */; }; 3C2206F52AB9DB9000DE780C /* DatadogSessionReplay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F62AB9DBA700DE780C /* DatadogRUM.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F72AB9DBB600DE780C /* DatadogTrace.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D25EE93429C4C3C300CE3839 /* DatadogTrace.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C2206F82AB9DBC600DE780C /* DatadogInternal.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 3C394EF72AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; - 3C394EF82AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */; }; - 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; - 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */; }; 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C74305C29FBC0480053B80F /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; 3C85D42129F7C5C900AFF894 /* WebViewTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */; }; @@ -40,22 +34,8 @@ 3C85D42C29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */; }; 3C85D42D29F7C87D00AFF894 /* HostsSanitizerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */; }; 3C9C6BB429F7C0C000581C43 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; - 3CB32AD42ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; - 3CB32AD52ACB733000D602ED /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */; }; - 3CB32AD72ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */; }; - 3CB32AD82ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */; }; - 3CBDE66E2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */; }; - 3CBDE66F2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */; }; - 3CBDE6712AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */; }; - 3CBDE6722AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */; }; 3CBDE6742AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */; }; 3CBDE6752AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */; }; - 3CBDE6812AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */; }; - 3CBDE6822AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */; }; - 3CBDE6842AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */; }; - 3CBDE6852AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */; }; - 3CBDE6872AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; }; - 3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */; }; 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */; }; 3CCCA5C42ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */; }; @@ -64,8 +44,6 @@ 3CCCA5C82ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */; }; 3CE11A1129F7BE0900202522 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3CE11A1229F7BE0900202522 /* DatadogWebViewTracking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 3CFD81952ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; - 3CFD81962ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */; }; 49274906288048B500ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49274907288048B800ECD49B /* InternalProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49274903288048AA00ECD49B /* InternalProxyTests.swift */; }; 49D8C0B72AC5D2160075E427 /* RUM+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */; }; @@ -618,6 +596,8 @@ D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49B52889416300802B2D /* UploadPerformancePreset.swift */; }; D2160CF729C0EE2B00FAA9A5 /* UploadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */; }; D2160CF829C0EE2B00FAA9A5 /* UploadMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */; }; + D2181A8E2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */; }; + D2181A8F2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */; }; D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21AE6BD29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21C26C528A3B49C005DD405 /* FeatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26C428A3B49C005DD405 /* FeatureStorage.swift */; }; @@ -919,6 +899,10 @@ D26C49C0288982DA00802B2D /* FeatureUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49BE288982DA00802B2D /* FeatureUpload.swift */; }; D26F741129ACBDA100D25622 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; }; D26F741229ACBDAD00D25622 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2DA2385298D57AA00C6C7E6 /* DatadogInternal.framework */; }; + D270CDDD2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */; }; + D270CDDE2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */; }; + D270CDE02B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */; }; + D270CDE12B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */; }; D2777D9D29F6A75800FFBB40 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */; }; D2777D9E29F6A75800FFBB40 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */; }; D2790C7229DEFCF400D88DA9 /* RUMDataModelMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22743E829DEC9A9001A7EF9 /* RUMDataModelMocks.swift */; }; @@ -1110,6 +1094,18 @@ D2B3F04E282A85FD00C2B5EE /* DatadogCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */; }; D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; + D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; + D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */; }; + D2BEEDAF2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */; }; + D2BEEDB02B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */; }; + D2BEEDB22B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */; }; + D2BEEDB32B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */; }; + D2BEEDB52B3360820065F3AC /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */; }; + D2BEEDB62B3360830065F3AC /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */; }; + D2BEEDB82B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */; }; + D2BEEDB92B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */; }; + D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */; }; + D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */; }; D2C1A4FA29C4C4CB00946C31 /* SpanSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61122ECD25B1B74500F9C7F5 /* SpanSanitizer.swift */; }; D2C1A4FB29C4C4CB00946C31 /* MessageReceivers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2546C0A29AF56270054E00B /* MessageReceivers.swift */; }; D2C1A4FC29C4C4CB00946C31 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2546C0729AF55E90054E00B /* RequestBuilder.swift */; }; @@ -1879,25 +1875,14 @@ 3C0D5DEE2A5442A900446CF9 /* EventMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMocks.swift; sourceTree = ""; }; 3C0D5DF42A5443B100446CF9 /* DataFormatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataFormatTests.swift; sourceTree = ""; }; 3C1890132ABDE99200CE9E73 /* DDURLSessionInstrumentationTests+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDURLSessionInstrumentationTests+apiTests.m"; sourceTree = ""; }; - 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensions.swift; sourceTree = ""; }; - 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; - 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; 3C85D41429F7C59C00AFF894 /* WebViewTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTracking.swift; sourceTree = ""; }; 3C85D42B29F7C87D00AFF894 /* HostsSanitizerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsSanitizerMock.swift; sourceTree = ""; }; - 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; - 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; - 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = ""; }; 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInstrumentation.swift; sourceTree = ""; }; - 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; - 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTaskDelegate+Tracking.swift"; sourceTree = ""; }; 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionTask+Tracking.swift"; sourceTree = ""; }; 3CCCA5C32ABAF0F80029D7BD /* DDURLSessionInstrumentation+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DDURLSessionInstrumentation+objc.swift"; sourceTree = ""; }; 3CCCA5C62ABAF5230029D7BD /* DDURLSessionInstrumentationConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionInstrumentationConfigurationTests.swift; sourceTree = ""; }; 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogWebViewTracking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3CE11A0529F7BE0300202522 /* DatadogWebViewTrackingTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogWebViewTrackingTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTypeExtensionsTests.swift; sourceTree = ""; }; 49274903288048AA00ECD49B /* InternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalProxyTests.swift; sourceTree = ""; }; 49274908288048F400ECD49B /* RUMInternalProxyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMInternalProxyTests.swift; sourceTree = ""; }; 49D8C0B62AC5D2160075E427 /* RUM+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUM+Internal.swift"; sourceTree = ""; }; @@ -2489,6 +2474,8 @@ D2160CEC29C0E0E600FAA9A5 /* DatadogURLSessionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogURLSessionHandler.swift; sourceTree = ""; }; D2160CEF29C0EC4D00FAA9A5 /* SingleFeatureCoreMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFeatureCoreMock.swift; sourceTree = ""; }; D2160CF629C0EE2B00FAA9A5 /* UploadMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadMocks.swift; sourceTree = ""; }; + D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInstrumentationSwizzler.swift; sourceTree = ""; }; + D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzlerTests.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 = ""; }; @@ -2605,6 +2592,8 @@ D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStatePublisherTests.swift; sourceTree = ""; }; D26C49B52889416300802B2D /* UploadPerformancePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPerformancePreset.swift; sourceTree = ""; }; D26C49BE288982DA00802B2D /* FeatureUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureUpload.swift; sourceTree = ""; }; + D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzler.swift; sourceTree = ""; }; + D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataDelegateSwizzlerTests.swift; sourceTree = ""; }; D2777D9C29F6A75800FFBB40 /* TelemetryReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryReceiverTests.swift; sourceTree = ""; }; D286626D2A43487500852CE3 /* Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Datadog.swift; sourceTree = ""; }; D28F836729C9E71C00EF8EA2 /* DDSpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDSpanTests.swift; sourceTree = ""; }; @@ -2648,6 +2637,11 @@ D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; D2BCB11E29D30AF000737A9A /* URLSessionRUMResourcesHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandler.swift; sourceTree = ""; }; D2BCB12129D34A5F00737A9A /* URLSessionRUMResourcesHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionRUMResourcesHandlerTests.swift; sourceTree = ""; }; + D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = ""; }; + D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzlerTests.swift; sourceTree = ""; }; + D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzler.swift; sourceTree = ""; }; + D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = ""; }; + D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTaskDelegateSwizzlerTests.swift; sourceTree = ""; }; D2C1A51929C4C5DD00946C31 /* JSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEncoder.swift; sourceTree = ""; }; D2C1A55A29C4F2DF00946C31 /* DatadogTrace.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogTrace.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2C1A57329C4F2E800946C31 /* DatadogTraceTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogTraceTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -5188,14 +5182,14 @@ children = ( D295A16429F299C9007C0E9A /* URLSessionInterceptor.swift */, D2160CC129C0DED100FAA9A5 /* URLSessionTaskInterception.swift */, - D2160CC329C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift */, - 3CBDE66D2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift */, - 3CBDE6702AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift */, 3CBDE6732AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift */, - 3CBDE6862AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift */, + D2160CC329C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift */, 3CBDE6892AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift */, - 3C394EF62AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift */, - 3CB32AD32ACB733000D602ED /* URLSessionSwizzler.swift */, + D2181A8A2B0500BB00A518C0 /* NetworkInstrumentationSwizzler.swift */, + D2BEEDB42B33607D0065F3AC /* URLSessionSwizzler.swift */, + D2BEEDAB2B3356710065F3AC /* URLSessionTaskSwizzler.swift */, + D2BEEDB12B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift */, + D270CDDC2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift */, ); path = URLSession; sourceTree = ""; @@ -5698,7 +5692,6 @@ D23039DC298D5235001A1FA3 /* DDError.swift */, 61133BBA2423979B00786299 /* SwiftExtensions.swift */, D29A9F9429DDB1DB005C54A4 /* UIKitExtensions.swift */, - 3C2206F22AB9CE9300DE780C /* MetaTypeExtensions.swift */, ); path = Utils; sourceTree = ""; @@ -5708,7 +5701,6 @@ children = ( 613C6B912768FF3100870CBF /* SamplerTests.swift */, 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */, - 3CFD81942ABBB66400977C22 /* MetaTypeExtensionsTests.swift */, ); path = Utils; sourceTree = ""; @@ -5820,10 +5812,10 @@ A79B0F60292BB071008742B3 /* B3HTTPHeadersReaderTests.swift */, A728ADA22934DB5000397996 /* W3CHTTPHeadersWriterTests.swift */, A728ADA52934DF2400397996 /* W3CHTTPHeadersReaderTests.swift */, - 3CBDE6802AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift */, - 3CBDE6832AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift */, - 3C394EF92AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift */, - 3CB32AD62ACB735600D602ED /* URLSessionSwizzlerTests.swift */, + D2181A8D2B051B7900A518C0 /* URLSessionSwizzlerTests.swift */, + D2BEEDAE2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift */, + D2BEEDB72B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift */, + D270CDDF2B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift */, ); path = NetworkInstrumentation; sourceTree = ""; @@ -7986,12 +7978,11 @@ D2EBEE1F29BA160F00B15732 /* HTTPHeadersReader.swift in Sources */, D263BCAF29DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D23039E7298D5236001A1FA3 /* NetworkConnectionInfo.swift in Sources */, - 3CBDE6712AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */, D23039E9298D5236001A1FA3 /* TrackingConsent.swift in Sources */, D2EBEE2629BA160F00B15732 /* B3HTTPHeaders.swift in Sources */, D23354FC2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, - 3C2206F32AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */, D23039F3298D5236001A1FA3 /* DynamicCodingKey.swift in Sources */, + D2BEEDB22B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D23039FE298D5236001A1FA3 /* FeatureRequestBuilder.swift in Sources */, D2160CE429C0DFEE00FAA9A5 /* MethodSwizzler.swift in Sources */, D2160CC929C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, @@ -7999,13 +7990,16 @@ D2432CF929EDB22C00D93657 /* Flushable.swift in Sources */, D23039F7298D5236001A1FA3 /* AttributesSanitizer.swift in Sources */, D23039EB298D5236001A1FA3 /* DatadogFeature.swift in Sources */, + D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, 3CBDE6742AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */, D23039E4298D5236001A1FA3 /* CarrierInfo.swift in Sources */, D2303A03298D5236001A1FA3 /* DDError.swift in Sources */, D23039F4298D5236001A1FA3 /* AnyCodable.swift in Sources */, D29A9F9529DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + D2BEEDB52B3360820065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE2529BA160F00B15732 /* TraceID.swift in Sources */, D2EBEE2129BA160F00B15732 /* W3CHTTPHeaders.swift in Sources */, + D2BEEDAC2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D23039E3298D5236001A1FA3 /* BatteryStatus.swift in Sources */, D2EBEE2A29BA160F00B15732 /* TracingHTTPHeaders.swift in Sources */, D23039EC298D5236001A1FA3 /* LaunchTime.swift in Sources */, @@ -8017,7 +8011,6 @@ D2160C9E29C0DE5700FAA9A5 /* TracingHeaderType.swift in Sources */, D23039F5298D5236001A1FA3 /* AnyEncodable.swift in Sources */, D2303A00298D5236001A1FA3 /* DatadogExtended.swift in Sources */, - 3CBDE66E2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */, D23039E6298D5236001A1FA3 /* Sysctl.swift in Sources */, D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D23039E1298D5236001A1FA3 /* AppState.swift in Sources */, @@ -8027,13 +8020,11 @@ D2EBEE2329BA160F00B15732 /* B3HTTPHeadersReader.swift in Sources */, D23039F8298D5236001A1FA3 /* InternalLogger.swift in Sources */, D2303A01298D5236001A1FA3 /* DateFormatting.swift in Sources */, - 3CBDE6872AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */, D2216EC02A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D23039F1298D5236001A1FA3 /* AnyDecodable.swift in Sources */, D2160CC529C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, D2160C9A29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, - 3CB32AD42ACB733000D602ED /* URLSessionSwizzler.swift in Sources */, D2EBEE2229BA160F00B15732 /* TracePropagationHeadersReader.swift in Sources */, D2303A02298D5236001A1FA3 /* ReadWriteLock.swift in Sources */, D2EBEE2429BA160F00B15732 /* W3CHTTPHeadersReader.swift in Sources */, @@ -8050,7 +8041,6 @@ D23039F2298D5236001A1FA3 /* AnyDecoder.swift in Sources */, D23039EF298D5236001A1FA3 /* FeatureMessage.swift in Sources */, D2160CA029C0DE5700FAA9A5 /* HostsSanitizer.swift in Sources */, - 3C394EF72AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D729DAFD500026CC3C /* FixedWidthInteger+Convenience.swift in Sources */, D295A16529F299C9007C0E9A /* URLSessionInterceptor.swift in Sources */, D23039E5298D5236001A1FA3 /* DateProvider.swift in Sources */, @@ -8060,6 +8050,7 @@ D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD72A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68A2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, + D270CDDD2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D929DAFD500026CC3C /* TimeInterval+Convenience.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8792,12 +8783,11 @@ D2EBEE2D29BA161100B15732 /* HTTPHeadersReader.swift in Sources */, D263BCB029DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D2DA2359298D57AA00C6C7E6 /* NetworkConnectionInfo.swift in Sources */, - 3CBDE6722AA08C0B00F6A7B6 /* URLSessionTaskSwizzler.swift in Sources */, D2DA235A298D57AA00C6C7E6 /* TrackingConsent.swift in Sources */, D2EBEE3429BA161100B15732 /* B3HTTPHeaders.swift in Sources */, D23354FD2A42E32000AFCAE2 /* InternalExtended.swift in Sources */, - 3C2206F42AB9CE9300DE780C /* MetaTypeExtensions.swift in Sources */, D2DA235B298D57AA00C6C7E6 /* DynamicCodingKey.swift in Sources */, + D2BEEDB32B335DA90065F3AC /* URLSessionTaskDelegateSwizzler.swift in Sources */, D2DA235C298D57AA00C6C7E6 /* FeatureRequestBuilder.swift in Sources */, D2160CE629C0DFEE00FAA9A5 /* MethodSwizzler.swift in Sources */, D2160CCA29C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, @@ -8805,13 +8795,16 @@ D2432CFA29EDB22C00D93657 /* Flushable.swift in Sources */, D2DA235D298D57AA00C6C7E6 /* AttributesSanitizer.swift in Sources */, D2DA235E298D57AA00C6C7E6 /* DatadogFeature.swift in Sources */, + D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, 3CBDE6752AA08C2F00F6A7B6 /* URLSessionInstrumentation.swift in Sources */, D2DA235F298D57AA00C6C7E6 /* CarrierInfo.swift in Sources */, D2DA2360298D57AA00C6C7E6 /* DDError.swift in Sources */, D2DA2361298D57AA00C6C7E6 /* AnyCodable.swift in Sources */, D29A9F9629DDB1DB005C54A4 /* UIKitExtensions.swift in Sources */, + D2BEEDB62B3360830065F3AC /* URLSessionSwizzler.swift in Sources */, D2EBEE3329BA161100B15732 /* TraceID.swift in Sources */, D2EBEE2F29BA161100B15732 /* W3CHTTPHeaders.swift in Sources */, + D2BEEDAD2B3356710065F3AC /* URLSessionTaskSwizzler.swift in Sources */, D2DA2363298D57AA00C6C7E6 /* BatteryStatus.swift in Sources */, D2EBEE3829BA161100B15732 /* TracingHTTPHeaders.swift in Sources */, D2DA2364298D57AA00C6C7E6 /* LaunchTime.swift in Sources */, @@ -8823,7 +8816,6 @@ D2160C9F29C0DE5700FAA9A5 /* TracingHeaderType.swift in Sources */, D2DA2369298D57AA00C6C7E6 /* AnyEncodable.swift in Sources */, D2DA236A298D57AA00C6C7E6 /* DatadogExtended.swift in Sources */, - 3CBDE66F2AA08BF600F6A7B6 /* URLSessionTaskDelegateSwizzler.swift in Sources */, D2DA236B298D57AA00C6C7E6 /* Sysctl.swift in Sources */, D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */, @@ -8833,13 +8825,11 @@ D2EBEE3129BA161100B15732 /* B3HTTPHeadersReader.swift in Sources */, D2DA236E298D57AA00C6C7E6 /* InternalLogger.swift in Sources */, D2DA236F298D57AA00C6C7E6 /* DateFormatting.swift in Sources */, - 3CBDE6882AA0B7F000F6A7B6 /* URLSessionTaskDelegate+Tracking.swift in Sources */, D2216EC12A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, D2DA2370298D57AA00C6C7E6 /* AnyDecodable.swift in Sources */, D2160CC629C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, - 3CB32AD52ACB733000D602ED /* URLSessionSwizzler.swift in Sources */, D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, D2DA2373298D57AA00C6C7E6 /* ReadWriteLock.swift in Sources */, D2EBEE3229BA161100B15732 /* W3CHTTPHeadersReader.swift in Sources */, @@ -8856,7 +8846,6 @@ D2DA2379298D57AA00C6C7E6 /* AnyDecoder.swift in Sources */, D2DA237A298D57AA00C6C7E6 /* FeatureMessage.swift in Sources */, D2160CA129C0DE5700FAA9A5 /* HostsSanitizer.swift in Sources */, - 3C394EF82AA5F49F008F48BA /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06D829DAFD500026CC3C /* FixedWidthInteger+Convenience.swift in Sources */, D295A16629F299C9007C0E9A /* URLSessionInterceptor.swift in Sources */, D2DA237B298D57AA00C6C7E6 /* DateProvider.swift in Sources */, @@ -8866,6 +8855,7 @@ D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */, 3C0D5DD82A543B3B00446CF9 /* Event.swift in Sources */, 3CBDE68B2AA0C47300F6A7B6 /* URLSessionTask+Tracking.swift in Sources */, + D270CDDE2B46E3DB0002EACD /* URLSessionDataDelegateSwizzler.swift in Sources */, D22F06DA29DAFD500026CC3C /* TimeInterval+Convenience.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8874,24 +8864,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C394EFA2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, + D2BEEDAF2B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */, D26416B62A30E84F00BCD9F7 /* CoreRegistryTest.swift in Sources */, D2EBEE3C29BA163E00B15732 /* B3HTTPHeadersWriterTests.swift in Sources */, - 3CBDE6812AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23A3298D58F400C6C7E6 /* AnyEncodableTests.swift in Sources */, D263BCB429DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, 3C0D5DF52A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4429BA168200B15732 /* TraceIDTests.swift in Sources */, - 3CBDE6842AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */, D2EBEE4329BA168200B15732 /* TraceIDGeneratorTests.swift in Sources */, D2DA23A7298D58F400C6C7E6 /* AppStateHistoryTests.swift in Sources */, D2DA23A5298D58F400C6C7E6 /* AnyDecodableTests.swift in Sources */, - 3CFD81952ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */, D2EBEE3D29BA163E00B15732 /* W3CHTTPHeadersWriterTests.swift in Sources */, - 3CB32AD72ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */, D2DA23A4298D58F400C6C7E6 /* AnyCodableTests.swift in Sources */, D2160CDE29C0DF6700FAA9A5 /* URLSessionTaskInterceptionTests.swift in Sources */, + D270CDE02B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23A1298D58F400C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD829C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, D20731CD29A52E8700ECBF94 /* SamplerTests.swift in Sources */, @@ -8904,6 +8891,8 @@ D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE029C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3B29BA163E00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, + D2BEEDB82B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, + D2181A8E2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */, D2A783DA29A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */, D2D36DCB2AC6DCCA0021F28A /* DatadogCoreProtocolTests.swift in Sources */, D2160CD429C0DF6700FAA9A5 /* NetworkInstrumentationFeatureTests.swift in Sources */, @@ -8917,24 +8906,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3C394EFB2AA5F4C8008F48BA /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, + D2BEEDB02B335C400065F3AC /* URLSessionTaskSwizzlerTests.swift in Sources */, D26416B72A30E84F00BCD9F7 /* CoreRegistryTest.swift in Sources */, D2EBEE4029BA163F00B15732 /* B3HTTPHeadersWriterTests.swift in Sources */, - 3CBDE6822AA092A200F6A7B6 /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, D21AE6BD29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23B1298D59DC00C6C7E6 /* AnyEncodableTests.swift in Sources */, D263BCB529DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, 3C0D5DF62A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4629BA168400B15732 /* TraceIDTests.swift in Sources */, - 3CBDE6852AA092BC00F6A7B6 /* URLSessionTaskSwizzlerTests.swift in Sources */, D2EBEE4529BA168400B15732 /* TraceIDGeneratorTests.swift in Sources */, D2DA23B2298D59DC00C6C7E6 /* AppStateHistoryTests.swift in Sources */, D2DA23B3298D59DC00C6C7E6 /* AnyDecodableTests.swift in Sources */, - 3CFD81962ABBB66400977C22 /* MetaTypeExtensionsTests.swift in Sources */, D2EBEE4129BA163F00B15732 /* W3CHTTPHeadersWriterTests.swift in Sources */, - 3CB32AD82ACB735600D602ED /* URLSessionSwizzlerTests.swift in Sources */, D2DA23B4298D59DC00C6C7E6 /* AnyCodableTests.swift in Sources */, D2160CDF29C0DF6700FAA9A5 /* URLSessionTaskInterceptionTests.swift in Sources */, + D270CDE12B46E5A50002EACD /* URLSessionDataDelegateSwizzlerTests.swift in Sources */, D2DA23B5298D59DC00C6C7E6 /* ReadWriteLockTests.swift in Sources */, D2160CD929C0DF6700FAA9A5 /* FirstPartyHostsTests.swift in Sources */, D20731CE29A52E8700ECBF94 /* SamplerTests.swift in Sources */, @@ -8947,6 +8933,8 @@ D2F44FB9299AA1DB0074B0D9 /* DataCompressionTests.swift in Sources */, D2160CE129C0DF6700FAA9A5 /* URLSessionDelegateAsSuperclassTests.swift in Sources */, D2EBEE3F29BA163F00B15732 /* B3HTTPHeadersReaderTests.swift in Sources */, + D2BEEDB92B3360F50065F3AC /* URLSessionTaskDelegateSwizzlerTests.swift in Sources */, + D2181A8F2B051B7900A518C0 /* URLSessionSwizzlerTests.swift in Sources */, D2A783D929A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */, D2D36DCC2AC6DCCA0021F28A /* DatadogCoreProtocolTests.swift in Sources */, D2160CD529C0DF6700FAA9A5 /* NetworkInstrumentationFeatureTests.swift in Sources */, diff --git a/DatadogCore/Tests/TestsObserver/DatadogTestsObserver.swift b/DatadogCore/Tests/TestsObserver/DatadogTestsObserver.swift index d6bade59bf..117de6c64b 100644 --- a/DatadogCore/Tests/TestsObserver/DatadogTestsObserver.swift +++ b/DatadogCore/Tests/TestsObserver/DatadogTestsObserver.swift @@ -39,7 +39,7 @@ internal class DatadogTestsObserver: NSObject, XCTestObservation { Make sure all applied swizzling are reset by the end of test with `unswizzle()`. `DatadogTestsObserver` found \(Swizzling.methods.count) leaked swizzlings: - \(Swizzling.methods) + \(Swizzling.description) """ ), .init( @@ -132,34 +132,6 @@ internal class DatadogTestsObserver: NSObject, XCTestObservation { If all above conditions are met, this failure might indicate a memory leak in the implementation. """ - ), - .init( - assert: { URLSessionTaskDelegateSwizzler.isBinded == false }, - problem: "No URLSessionTaskDelegate swizzling must be applied.", - solution: """ - Make sure all the binded delegates are unbinded by the end of test with `URLSessionTaskDelegateSwizzler.unbind(delegate:)`. - """ - ), - .init( - assert: { URLSessionDataDelegateSwizzler.isBinded == false }, - problem: "No URLSessionDataDelegate swizzling must be applied.", - solution: """ - Make sure all the binded delegates are unbinded by the end of test with `URLSessionDataDelegateSwizzler.unbind(delegate:)`. - """ - ), - .init( - assert: { URLSessionTaskSwizzler.isBinded == false }, - problem: "No URLSessionTask swizzling must be applied.", - solution: """ - Make sure all the binded delegates are unbinded by the end of test with `URLSessionTaskSwizzler.unbind()`. - """ - ), - .init( - assert: { URLSessionSwizzler.isBinded == false }, - problem: "No URLSession swizzling must be applied.", - solution: """ - Make sure all the binded delegates are unbinded by the end of test with `URLSessionSwizzler.unbind()`. - """ ) ] diff --git a/DatadogInternal/Sources/Concurrency/ReadWriteLock.swift b/DatadogInternal/Sources/Concurrency/ReadWriteLock.swift index 596c9406c7..bb46135609 100644 --- a/DatadogInternal/Sources/Concurrency/ReadWriteLock.swift +++ b/DatadogInternal/Sources/Concurrency/ReadWriteLock.swift @@ -39,20 +39,16 @@ public final class ReadWriteLock { defer { pthread_rwlock_unlock(&rwlock) } return value } - set { - pthread_rwlock_wrlock(&rwlock) - value = newValue - pthread_rwlock_unlock(&rwlock) - } + set { mutate { $0 = newValue } } } /// Provides a non-escaping closure for mutation. /// The lock will be acquired once for writing before invoking the closure. /// /// - Parameter closure: The closure with the mutable value. - public func mutate(_ closure: (inout Value) -> Void) { + public func mutate(_ closure: (inout Value) throws -> Void) rethrows { pthread_rwlock_wrlock(&rwlock) - closure(&value) - pthread_rwlock_unlock(&rwlock) + defer { pthread_rwlock_unlock(&rwlock) } + try closure(&value) } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift index d546ef6f59..8dccc27d0a 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift @@ -38,6 +38,9 @@ internal final class NetworkInstrumentationFeature: DatadogFeature { @ReadWriteLock internal var handlers: [DatadogURLSessionHandler] = [] + @ReadWriteLock + private var swizzlers: [ObjectIdentifier: NetworkInstrumentationSwizzler] = [:] + /// Maps `URLSessionTask` to its `TaskInterception` object. /// /// The interceptions **must** be accessed using the `queue`. @@ -49,80 +52,76 @@ internal final class NetworkInstrumentationFeature: DatadogFeature { /// - Parameter configuration: The configuration to use for swizzling. /// Note: We are only concerned with type of the delegate here but to provide compile time safety, we /// use the instance of the delegate to get the type. - internal func bindIfNeeded(configuration: URLSessionInstrumentation.Configuration) throws { + internal func bind(configuration: URLSessionInstrumentation.Configuration) throws { let configuredFirstPartyHosts = FirstPartyHosts(firstPartyHosts: configuration.firstPartyHostsTracing) ?? .init() - try URLSessionTaskDelegateSwizzler.bindIfNeeded( + let identifier = ObjectIdentifier(configuration.delegateClass) + + if let swizzler = swizzlers[identifier] { + DD.logger.warn( + """ + The delegate class \(configuration.delegateClass) is already instrumented. + The previous instrumentation will be disabled in favor of the new one. + """ + ) + + swizzler.unswizzle() + } + + let swizzler = NetworkInstrumentationSwizzler() + swizzlers[identifier] = swizzler + + try swizzler.swizzle( + interceptResume: { [weak self] task in + // intercept task if delegate match + guard let self = self, task.dd.delegate?.isKind(of: configuration.delegateClass) == true else { + return + } + + if let currentRequest = task.currentRequest { + let request = self.intercept(request: currentRequest, additionalFirstPartyHosts: configuredFirstPartyHosts) + task.dd.override(currentRequest: request) + } + + self.intercept(task: task, additionalFirstPartyHosts: configuredFirstPartyHosts) + } + ) + + try swizzler.swizzle( delegateClass: configuration.delegateClass, interceptDidFinishCollecting: { [weak self] session, task, metrics in - self?.queue.async { [weak self, weak session] in - self?._task(task, didFinishCollecting: metrics) - session?.delegate?.interceptor?.task(task, didFinishCollecting: metrics) + self?.task(task, didFinishCollecting: metrics) - // iOS 16 and above, didCompleteWithError is not called hence we use task state to detect task completion + if #available(iOS 15, tvOS 15, *) { + // iOS 15 and above, didCompleteWithError is not called hence we use task state to detect task completion // while prior to iOS 15, task state doesn't change to completed hence we use didCompleteWithError to detect task completion - if #available(iOS 15, tvOS 15, *) { - self?._task(task, didCompleteWithError: task.error) - session?.delegate?.interceptor?.task(task, didCompleteWithError: task.error) - } - } - }, interceptDidCompleteWithError: { [weak self] session, task, error in - self?.queue.async { [weak self, weak session] in - // prior to iOS 15, task state doesn't change to completed - // hence we use didCompleteWithError to detect task completion - self?._task(task, didCompleteWithError: task.error) - session?.delegate?.interceptor?.task(task, didCompleteWithError: task.error) + self?.task(task, didCompleteWithError: task.error) } + }, + interceptDidCompleteWithError: { [weak self] session, task, error in + self?.task(task, didCompleteWithError: error) } ) - try URLSessionDataDelegateSwizzler.bindIfNeeded(delegateClass: configuration.delegateClass, interceptDidReceive: { [weak self] session, task, data in - // sync update to task prevents a race condition where the currentRequest could already be sent to the transport - self?.queue.sync { [weak self, weak session] in - self?._task(task, didReceive: data) - session?.delegate?.interceptor?.task(task, didReceive: data) + try swizzler.swizzle( + delegateClass: configuration.delegateClass, + interceptDidReceive: { [weak self] session, task, data in + self?.task(task, didReceive: data) } - }) - - if #available(iOS 13, tvOS 13, *) { - try URLSessionTaskSwizzler.bindIfNeeded(interceptResume: { [weak self] task in - self?.queue.sync { [weak self] in - let additionalFirstPartyHosts = configuredFirstPartyHosts + task.firstPartyHosts - self?._intercept(task: task, additionalFirstPartyHosts: additionalFirstPartyHosts) - } - }) - } else { - try URLSessionSwizzler.bindIfNeeded(interceptURLRequest: { request in - return self.intercept(request: request, additionalFirstPartyHosts: configuredFirstPartyHosts) - }, interceptTask: { [weak self] task in - self?.queue.async { [weak self] in - let additionalFirstPartyHosts = configuredFirstPartyHosts + task.firstPartyHosts - self?._intercept(task: task, additionalFirstPartyHosts: additionalFirstPartyHosts) - } - }) - } - } + ) - internal func unbindAll() { - URLSessionTaskDelegateSwizzler.unbindAll() - URLSessionDataDelegateSwizzler.unbindAll() - URLSessionTaskSwizzler.unbind() - URLSessionSwizzler.unbind() + try swizzler.swizzle( + interceptCompletionHandler: { [weak self] task, _, error in + self?.task(task, didCompleteWithError: error) + } + ) } /// Unswizzles `URLSessionTaskDelegate`, `URLSessionDataDelegate`, `URLSessionTask` and `URLSession` methods /// - Parameter delegateClass: The delegate class to unswizzle. internal func unbind(delegateClass: URLSessionDataDelegate.Type) { - URLSessionTaskDelegateSwizzler.unbind(delegateClass: delegateClass) - URLSessionDataDelegateSwizzler.unbind(delegateClass: delegateClass) - - guard URLSessionTaskDelegateSwizzler.didFinishCollectingMap.isEmpty, - URLSessionDataDelegateSwizzler.didReceiveMap.isEmpty else { - return - } - - URLSessionTaskSwizzler.unbind() - URLSessionSwizzler.unbind() + let identifier = ObjectIdentifier(delegateClass) + swizzlers.removeValue(forKey: identifier) } } @@ -152,47 +151,32 @@ extension NetworkInstrumentationFeature { /// - task: The created task. /// - additionalFirstPartyHosts: Extra hosts to consider in the interception. func intercept(task: URLSessionTask, additionalFirstPartyHosts: FirstPartyHosts?) { - // sync update to task prevents a race condition where the currentRequest could already be sent to the transport - queue.sync { [weak self] in - self?._intercept(task: task, additionalFirstPartyHosts: additionalFirstPartyHosts) - } - } - - private func _intercept(task: URLSessionTask, additionalFirstPartyHosts: FirstPartyHosts?) { - guard let request = task.currentRequest ?? task.originalRequest else { - return - } + queue.async { [weak self] in + guard let self = self, let request = task.currentRequest else { + return + } - var interceptedRequest: URLRequest - /// task.setValue is not available on iOS 12, hence for iOS 12 we modify the request by swizzling URLSession methods - if #available(iOS 13, tvOS 13, *) { - let request = self.intercept(request: request, additionalFirstPartyHosts: additionalFirstPartyHosts) - interceptedRequest = request - task.setValue(interceptedRequest, forKey: "currentRequest") - } else { - interceptedRequest = request - } + let firstPartyHosts = self.firstPartyHosts(with: additionalFirstPartyHosts) - let firstPartyHosts = self.firstPartyHosts(with: additionalFirstPartyHosts) + let interception = self.interceptions[task] ?? + URLSessionTaskInterception( + request: request, + isFirstParty: firstPartyHosts.isFirstParty(url: request.url) + ) - let interception = self.interceptions[task] ?? - URLSessionTaskInterception( - request: interceptedRequest, - isFirstParty: firstPartyHosts.isFirstParty(url: interceptedRequest.url) - ) + interception.register(request: request) - interception.register(request: interceptedRequest) + if let trace = self.extractTrace(firstPartyHosts: firstPartyHosts, request: request) { + interception.register(traceID: trace.traceID, spanID: trace.spanID, parentSpanID: trace.parentSpanID) + } - if let trace = self.extractTrace(firstPartyHosts: firstPartyHosts, request: interceptedRequest) { - interception.register(traceID: trace.traceID, spanID: trace.spanID, parentSpanID: trace.parentSpanID) - } + if let origin = request.value(forHTTPHeaderField: TracingHTTPHeaders.originField) { + interception.register(origin: origin) + } - if let origin = interceptedRequest.value(forHTTPHeaderField: TracingHTTPHeaders.originField) { - interception.register(origin: origin) + self.interceptions[task] = interception + self.handlers.forEach { $0.interceptionDidStart(interception: interception) } } - - self.interceptions[task] = interception - self.handlers.forEach { $0.interceptionDidStart(interception: interception) } } /// Tells the interceptors that metrics were collected for the given task. @@ -202,21 +186,17 @@ extension NetworkInstrumentationFeature { /// - metrics: The collected metrics. func task(_ task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { queue.async { [weak self] in - self?._task(task, didFinishCollecting: metrics) - } - } - - private func _task(_ task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { - guard let interception = self.interceptions[task] else { - return - } + guard let self = self, let interception = self.interceptions[task] else { + return + } - interception.register( - metrics: ResourceMetrics(taskMetrics: metrics) - ) + interception.register( + metrics: ResourceMetrics(taskMetrics: metrics) + ) - if interception.isDone { - self.finish(task: task, interception: interception) + if interception.isDone { + self.finish(task: task, interception: interception) + } } } @@ -227,17 +207,10 @@ extension NetworkInstrumentationFeature { /// - data: A data object containing the transferred data. func task(_ task: URLSessionTask, didReceive data: Data) { queue.async { [weak self] in - self?._task(task, didReceive: data) + self?.interceptions[task]?.register(nextData: data) } } - private func _task(_ task: URLSessionTask, didReceive data: Data) { - guard let interception = self.interceptions[task] else { - return - } - interception.register(nextData: data) - } - /// Tells the interceptors that the task did complete. /// /// - Parameters: @@ -245,22 +218,18 @@ extension NetworkInstrumentationFeature { /// - error: If an error occurred, an error object indicating how the transfer failed, otherwise NULL. func task(_ task: URLSessionTask, didCompleteWithError error: Error?) { queue.async { [weak self] in - self?._task(task, didCompleteWithError: error) - } - } - - private func _task(_ task: URLSessionTask, didCompleteWithError error: Error?) { - guard let interception = self.interceptions[task] else { - return - } + guard let self = self, let interception = self.interceptions[task] else { + return + } - interception.register( - response: task.response, - error: error - ) + interception.register( + response: task.response, + error: error + ) - if interception.isDone { - self.finish(task: task, interception: interception) + if interception.isDone { + self.finish(task: task, interception: interception) + } } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift index a2732af537..1462812d0b 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/DatadogURLSessionDelegate.swift @@ -14,6 +14,13 @@ public typealias DDURLSessionDelegate = DatadogURLSessionDelegate @objc @available(*, deprecated, message: "Use `URLSessionInstrumentation.enable(with:)` instead.") public protocol __URLSessionDelegateProviding: URLSessionDelegate { + /// Datadog delegate object. + /// + /// The class implementing `DDURLSessionDelegateProviding` must ensure that following method calls are forwarded to `ddURLSessionDelegate`: + /// - `func urlSession(_:task:didFinishCollecting:)` + /// - `func urlSession(_:task:didCompleteWithError:)` + /// - `func urlSession(_:dataTask:didReceive:)` + var ddURLSessionDelegate: DatadogURLSessionDelegate { get } } /// The `URLSession` delegate object which enables network requests instrumentation. **It must be @@ -28,7 +35,7 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate { return URLSessionInterceptor.shared(in: core) } - /* private */ public let firstPartyHosts: FirstPartyHosts + let swizzler = NetworkInstrumentationSwizzler() /// The instance of the SDK core notified by this delegate. /// @@ -39,17 +46,8 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate { @objc override public init() { core = nil - firstPartyHosts = .init() - - URLSessionInstrumentation.enable( - with: .init( - delegateClass: Self.self, - firstPartyHostsTracing: .traceWithHeaders(hostsWithHeaders: firstPartyHosts.hostsWithTracingHeaderTypes) - ), - in: core ?? CoreRegistry.default - ) - super.init() + swizzle(firstPartyHosts: .init()) } /// Automatically tracked hosts can be customized per instance with this initializer. @@ -94,20 +92,17 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate { additionalFirstPartyHostsWithHeaderTypes: [String: Set] = [:] ) { self.core = core - self.firstPartyHosts = FirstPartyHosts(additionalFirstPartyHostsWithHeaderTypes) - - URLSessionInstrumentation.enable( - with: .init( - delegateClass: Self.self, - firstPartyHostsTracing: .traceWithHeaders(hostsWithHeaders: firstPartyHosts.hostsWithTracingHeaderTypes) - ), - in: core ?? CoreRegistry.default - ) super.init() + swizzle(firstPartyHosts: FirstPartyHosts(additionalFirstPartyHostsWithHeaderTypes)) } open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { interceptor?.task(task, didFinishCollecting: metrics) + if #available(iOS 15, tvOS 15, *) { + // iOS 15 and above, didCompleteWithError is not called hence we use task state to detect task completion + // while prior to iOS 15, task state doesn't change to completed hence we use didCompleteWithError to detect task completion + interceptor?.task(task, didCompleteWithError: task.error) + } } open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { @@ -119,6 +114,41 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate { // NOTE: This delegate method is only called for `URLSessionTasks` created without the completion handler. interceptor?.task(task, didCompleteWithError: error) } + + private func swizzle(firstPartyHosts: FirstPartyHosts) { + do { + try swizzler.swizzle( + interceptResume: { [weak self] task in + guard + let interceptor = self?.interceptor, + let provider = task.dd.delegate as? __URLSessionDelegateProviding, + provider.ddURLSessionDelegate === self // intercept task with self as delegate + else { + return + } + + if let currentRequest = task.currentRequest { + let request = interceptor.intercept(request: currentRequest, additionalFirstPartyHosts: firstPartyHosts) + task.dd.override(currentRequest: request) + } + + interceptor.intercept(task: task, additionalFirstPartyHosts: firstPartyHosts) + } + ) + + try swizzler.swizzle( + interceptCompletionHandler: { [weak self] task, _, error in + self?.interceptor?.task(task, didCompleteWithError: error) + } + ) + } catch { + DD.logger.error("Fails to apply swizzling for instrumenting \(Self.self)", error: error) + } + } + + deinit { + swizzler.unswizzle() + } } @available(*, deprecated, message: "Use `URLSessionInstrumentation.enable(with:)` instead.") diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift new file mode 100644 index 0000000000..f694655db5 --- /dev/null +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/NetworkInstrumentationSwizzler.swift @@ -0,0 +1,72 @@ +/* + * 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 Foundation + +/// Swizzles `URLSession*` methods. +internal final class NetworkInstrumentationSwizzler { + let urlSessionSwizzler: URLSessionSwizzler + let urlSessionTaskSwizzler: URLSessionTaskSwizzler + let urlSessionTaskDelegateSwizzler: URLSessionTaskDelegateSwizzler + let urlSessionDataDelegateSwizzler: URLSessionDataDelegateSwizzler + + init() { + let lock = NSRecursiveLock() + urlSessionSwizzler = URLSessionSwizzler(lock: lock) + urlSessionTaskSwizzler = URLSessionTaskSwizzler(lock: lock) + urlSessionTaskDelegateSwizzler = URLSessionTaskDelegateSwizzler(lock: lock) + urlSessionDataDelegateSwizzler = URLSessionDataDelegateSwizzler(lock: lock) + } + + /// Swizzles `URLSession.dataTask(with:completionHandler:)` methods (with `URL` and `URLRequest`). + func swizzle( + interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void + ) throws { + try urlSessionSwizzler.swizzle(interceptCompletionHandler: interceptCompletionHandler) + } + + /// Swizzles `URLSessionTask.resume()` method. + func swizzle( + interceptResume: @escaping (URLSessionTask) -> Void + ) throws { + try urlSessionTaskSwizzler.swizzle(interceptResume: interceptResume) + } + + /// Swizzles methods: + /// - `URLSessionTaskDelegate.urlSession(_:task:didFinishCollecting:)` + /// - `URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)` + func swizzle( + delegateClass: URLSessionTaskDelegate.Type, + interceptDidFinishCollecting: @escaping (URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void, + interceptDidCompleteWithError: @escaping (URLSession, URLSessionTask, Error?) -> Void + ) throws { + try urlSessionTaskDelegateSwizzler.swizzle( + delegateClass: delegateClass, + interceptDidFinishCollecting: interceptDidFinishCollecting, + interceptDidCompleteWithError: interceptDidCompleteWithError + ) + } + + /// Swizzles methods: + /// - `URLSessionDataDelegate.urlSession(_:dataTask:didReceive:)` + func swizzle( + delegateClass: URLSessionDataDelegate.Type, + interceptDidReceive: @escaping (URLSession, URLSessionDataTask, Data) -> Void + ) throws { + try urlSessionDataDelegateSwizzler.swizzle( + delegateClass: delegateClass, + interceptDidReceive: interceptDidReceive + ) + } + + /// Unswizzles all. + func unswizzle() { + urlSessionSwizzler.unswizzle() + urlSessionTaskSwizzler.unswizzle() + urlSessionTaskDelegateSwizzler.unswizzle() + urlSessionDataDelegateSwizzler.unswizzle() + } +} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionDataDelegateSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionDataDelegateSwizzler.swift index 1f9e115101..0c6ef61e94 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionDataDelegateSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionDataDelegateSwizzler.swift @@ -8,74 +8,36 @@ import Foundation /// Swizzles `URLSessionDataDelegate` callbacks. internal class URLSessionDataDelegateSwizzler { - private static var _didReceiveMap: [String: DidReceive?] = [:] - static var didReceiveMap: [String: DidReceive?] { - get { - lock.lock() - defer { lock.unlock() } - return _didReceiveMap - } - set { - lock.lock() - defer { lock.unlock() } - _didReceiveMap = newValue - } - } + private let lock: NSLocking + private var didReceive: DidReceive? - private static var lock = NSRecursiveLock() - - static var isBinded: Bool { - lock.lock() - defer { lock.unlock() } - return didReceiveMap.isEmpty == false + init(lock: NSLocking = NSLock()) { + self.lock = lock } - static func bindIfNeeded( + /// Swizzles methods: + /// - `URLSessionDataDelegate.urlSession(_:dataTask:didReceive:)` + func swizzle( delegateClass: URLSessionDataDelegate.Type, interceptDidReceive: @escaping (URLSession, URLSessionDataTask, Data) -> Void ) throws { lock.lock() defer { lock.unlock() } - - let key = MetaTypeExtensions.key(from: delegateClass) - guard didReceiveMap[key] == nil else { - return - } - - try bind(delegateClass: delegateClass, interceptDidReceive: interceptDidReceive) - } - - static func bind( - delegateClass: URLSessionDataDelegate.Type, - interceptDidReceive: @escaping (URLSession, URLSessionDataTask, Data - ) -> Void) throws { - lock.lock() - defer { lock.unlock() } - - let didReceive = try DidReceive.build(klass: delegateClass) - let key = MetaTypeExtensions.key(from: delegateClass) - - didReceive.swizzle(intercept: interceptDidReceive) - didReceiveMap[key] = didReceive + didReceive = try DidReceive.build(klass: delegateClass) + didReceive?.swizzle(intercept: interceptDidReceive) } - static func unbind(delegateClass: URLSessionDataDelegate.Type) { + /// Unswizzles all. + /// + /// This method is called during deinit. + func unswizzle() { lock.lock() - defer { lock.unlock() } - - let key = MetaTypeExtensions.key(from: delegateClass) - didReceiveMap[key]??.unswizzle() - didReceiveMap.removeValue(forKey: key) + didReceive?.unswizzle() + lock.unlock() } - static func unbindAll() { - lock.lock() - defer { lock.unlock() } - - didReceiveMap.forEach { _, didReceive in - didReceive?.unswizzle() - } - didReceiveMap.removeAll() + deinit { + unswizzle() } /// Swizzles `urlSession(_:dataTask:didReceive:)` callback. @@ -86,7 +48,7 @@ internal class URLSessionDataDelegateSwizzler { private let method: Method - static func build(klass: URLSessionDataDelegate.Type) throws -> DidReceive { + static func build(klass: AnyClass) throws -> DidReceive { return try DidReceive(selector: self.selector, klass: klass) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInstrumentation.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInstrumentation.swift index 777291eb26..bb3f98cd91 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInstrumentation.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionInstrumentation.swift @@ -26,7 +26,7 @@ public enum URLSessionInstrumentation { throw ProgrammerError(description: "URLSession tracking must be enabled before enabling URLSessionInstrumentation using either RUM or Trace feature.") } - try feature.bindIfNeeded(configuration: configuration) + try feature.bind(configuration: configuration) } /// Disables URLSession instrumentation. diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift index 717f6e13e3..4758689131 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionSwizzler.swift @@ -6,74 +6,54 @@ import Foundation -/// Swizzles `URLSession` methods. -internal class URLSessionSwizzler { - private static var _dataTaskWithURLRequestAndCompletion: DataTaskWithURLRequestAndCompletion? - static var dataTaskWithURLRequestAndCompletion: DataTaskWithURLRequestAndCompletion? { - get { - lock.lock() - defer { lock.unlock() } - return _dataTaskWithURLRequestAndCompletion - } - set { - lock.lock() - defer { lock.unlock() } - _dataTaskWithURLRequestAndCompletion = newValue - } - } - - private static var lock = NSRecursiveLock() - - static var isBinded: Bool { - lock.lock() - defer { lock.unlock() } - return dataTaskWithURLRequestAndCompletion != nil +/// Swizzles `URLSession*` methods. +internal final class URLSessionSwizzler { + private let lock: NSLocking + private var dataTaskURLRequestCompletionHandler: DataTaskURLRequestCompletionHandler? + private var dataTaskURLCompletionHandler: DataTaskURLCompletionHandler? + + init(lock: NSLocking = NSLock()) { + self.lock = lock } - static func bindIfNeeded( - interceptURLRequest: @escaping (URLRequest) -> URLRequest?, - interceptTask: @escaping (URLSessionTask) -> Void + /// Swizzles `URLSession.dataTask(with:completionHandler:)` methods (with `URL` and `URLRequest`). + func swizzle( + interceptCompletionHandler: @escaping (URLSessionTask, Data?, Error?) -> Void ) throws { lock.lock() defer { lock.unlock() } - - guard dataTaskWithURLRequestAndCompletion == nil else { - return - } - - try bind(interceptURLRequest: interceptURLRequest, interceptTask: interceptTask) + dataTaskURLRequestCompletionHandler = try DataTaskURLRequestCompletionHandler.build() + dataTaskURLRequestCompletionHandler?.swizzle(interceptCompletion: interceptCompletionHandler) + dataTaskURLCompletionHandler = try DataTaskURLCompletionHandler.build() + dataTaskURLCompletionHandler?.swizzle(interceptCompletion: interceptCompletionHandler) } - static func bind( - interceptURLRequest: @escaping (URLRequest) -> URLRequest?, - interceptTask: @escaping (URLSessionTask) -> Void - ) throws { + /// Unswizzles all. + /// + /// This method is called during deinit. + func unswizzle() { lock.lock() - defer { lock.unlock() } - - self.dataTaskWithURLRequestAndCompletion = try DataTaskWithURLRequestAndCompletion.build() - dataTaskWithURLRequestAndCompletion?.swizzle(interceptRequest: interceptURLRequest, interceptTask: interceptTask) + dataTaskURLRequestCompletionHandler?.unswizzle() + dataTaskURLCompletionHandler?.unswizzle() + lock.unlock() } - static func unbind() { - lock.lock() - defer { lock.unlock() } - dataTaskWithURLRequestAndCompletion?.unswizzle() - dataTaskWithURLRequestAndCompletion = nil + deinit { + unswizzle() } typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void - /// Swizzles `URLSession.dataTask(with:completionHandler:)` method. - class DataTaskWithURLRequestAndCompletion: MethodSwizzler<@convention(c) (URLSession, Selector, URLRequest, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask> { + /// Swizzles `URLSession.dataTask(with:completionHandler:)` (with `URLRequest`) method. + class DataTaskURLRequestCompletionHandler: MethodSwizzler<@convention(c) (URLSession, Selector, URLRequest, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask> { private static let selector = #selector( URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URLRequest, @escaping CompletionHandler) -> URLSessionDataTask ) private let method: Method - static func build() throws -> DataTaskWithURLRequestAndCompletion { - return try DataTaskWithURLRequestAndCompletion( + static func build() throws -> DataTaskURLRequestCompletionHandler { + return try DataTaskURLRequestCompletionHandler( selector: self.selector, klass: URLSession.self ) @@ -85,15 +65,79 @@ internal class URLSessionSwizzler { } func swizzle( - interceptRequest: @escaping (URLRequest) -> URLRequest?, - interceptTask: @escaping (URLSessionTask) -> Void + interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void ) { typealias Signature = @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask swizzle(method) { previousImplementation -> Signature in return { session, request, completionHandler -> URLSessionDataTask in - let interceptedRequest = interceptRequest(request) ?? request - let task = previousImplementation(session, Self.selector, interceptedRequest, completionHandler) - interceptTask(task) + guard let completionHandler = completionHandler else { + // The `completionHandler` can be `nil` in two cases: + // - on iOS 11 or 12, where `dataTask(with:)` (for `URL` and `URLRequest`) calls + // the `dataTask(with:completionHandler:)` (for `URLRequest`) internally by nullifying the completion block. + // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing + // `nil` as the `completionHandler` (it produces a warning, but compiles). + return previousImplementation(session, Self.selector, request, completionHandler) + } + + var _task: URLSessionDataTask? + let task = previousImplementation(session, Self.selector, request) { data, response, error in + completionHandler(data, response, error) + + if let task = _task { // sanity check, should always succeed + interceptCompletion(task, data, error) + } + } + _task = task + return task + } + } + } + } + + /// Swizzles `URLSession.dataTask(with:completionHandler:)` (with `URL`) method. + class DataTaskURLCompletionHandler: MethodSwizzler<@convention(c) (URLSession, Selector, URL, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask> { + private static let selector = #selector( + URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URL, @escaping CompletionHandler) -> URLSessionDataTask + ) + + private let method: Method + + static func build() throws -> DataTaskURLCompletionHandler { + return try DataTaskURLCompletionHandler( + selector: self.selector, + klass: URLSession.self + ) + } + + private init(selector: Selector, klass: AnyClass) throws { + self.method = try dd_class_getInstanceMethod(klass, selector) + super.init() + } + + func swizzle( + interceptCompletion: @escaping (URLSessionTask, Data?, Error?) -> Void + ) { + typealias Signature = @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask + swizzle(method) { previousImplementation -> Signature in + return { session, url, completionHandler -> URLSessionDataTask in + guard let completionHandler = completionHandler else { + // The `completionHandler` can be `nil` in two cases: + // - on iOS 11 or 12, where `dataTask(with:)` (for `URL` and `URLRequest`) calls + // the `dataTask(with:completionHandler:)` (for `URLRequest`) internally by nullifying the completion block. + // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing + // `nil` as the `completionHandler` (it produces a warning, but compiles). + return previousImplementation(session, Self.selector, url, completionHandler) + } + + var _task: URLSessionDataTask? + let task = previousImplementation(session, Self.selector, url) { data, response, error in + completionHandler(data, response, error) + + if let task = _task { // sanity check, should always succeed + interceptCompletion(task, data, error) + } + } + _task = task return task } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift index befdfd1673..6c3f19035b 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift @@ -6,23 +6,31 @@ import Foundation -private var sessionFirstPartyHostsKey: UInt8 = 31 -internal extension URLSessionTask { - /// Returns the first party hosts for this task. - var firstPartyHosts: FirstPartyHosts { - get { - return sessionFirstPartyHosts ?? .init() - } +extension URLSessionTask: DatadogExtended {} +extension DatadogExtension where ExtendedType: URLSessionTask { + /// Overrides the current request of the ``URLSessionTask``. + /// + /// The current request must be overriden before the task resumes. + /// + /// - Parameter request: The new request. + func override(currentRequest request: URLRequest) { + // The `URLSessionTask` is Key-Value Coding compliant and we can + // set the `currentRequest` property + type.setValue(request, forKey: "currentRequest") } - /// Extension property for storing first party hosts passed from `URLSession` to `URLSessionTask`. - /// This is used for `URLSessionTask` based APIs. - var sessionFirstPartyHosts: FirstPartyHosts? { - set { - objc_setAssociatedObject(self, &sessionFirstPartyHostsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + /// Returns the delegate instance the task is reporting to. + var delegate: URLSessionDelegate? { + if #available(iOS 15.0, tvOS 15.0, *), let delegate = type.delegate { + return delegate } - get { - return objc_getAssociatedObject(self, &sessionFirstPartyHostsKey) as? FirstPartyHosts + + // The `URLSessionTask` is Key-Value Coding compliant and retains a + // `session` property + guard let session = type.value(forKey: "session") as? URLSession else { + return nil } + + return session.delegate } } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegate+Tracking.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegate+Tracking.swift deleted file mode 100644 index 714a8cec8e..0000000000 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegate+Tracking.swift +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 Foundation - -private var coreKey: UInt8 = 43 - -public extension URLSessionDelegate { - /// Returns the `DatadogCore` for this delegate. - weak var core: DatadogCoreProtocol? { - set { - objc_setAssociatedObject(self, &coreKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - } - get { - return objc_getAssociatedObject(self, &coreKey) as? DatadogCoreProtocol - } - } - - /// Returns the `URLSessionInterceptor` for this delegate. - var interceptor: URLSessionInterceptor? { - let core = self.core ?? CoreRegistry.default - return URLSessionInterceptor.shared(in: core) - } -} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegateSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegateSwizzler.swift index 06b228dd69..48194b9b72 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegateSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskDelegateSwizzler.swift @@ -8,105 +8,42 @@ import Foundation /// Swizzles `URLSessionTaskDelegate` callbacks. internal class URLSessionTaskDelegateSwizzler { - private static var _didFinishCollectingMap: [String: DidFinishCollecting?] = [:] - static var didFinishCollectingMap: [String: DidFinishCollecting?] { - get { - lock.lock() - defer { lock.unlock() } - return _didFinishCollectingMap - } - set { - lock.lock() - defer { lock.unlock() } - _didFinishCollectingMap = newValue - } - } - private static var lock = NSRecursiveLock() - - private static var _didCompleteWithErrorMap: [String: DidCompleteWithError?] = [:] - static var didCompleteWithErrorMap: [String: DidCompleteWithError?] { - get { - lock.lock() - defer { lock.unlock() } - return _didCompleteWithErrorMap - } - set { - lock.lock() - defer { lock.unlock() } - _didCompleteWithErrorMap = newValue - } - } - - static var isBinded: Bool { - lock.lock() - defer { lock.unlock() } - return didFinishCollectingMap.isEmpty == false || didCompleteWithErrorMap.isEmpty == false - } - - static func bindIfNeeded( - delegateClass: AnyClass, - interceptDidFinishCollecting: @escaping (URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void, - interceptDidCompleteWithError: @escaping (URLSession, URLSessionTask, Error?) -> Void - ) throws { - lock.lock() - defer { lock.unlock() } - - let key = MetaTypeExtensions.key(from: delegateClass) - guard didFinishCollectingMap[key] == nil || didCompleteWithErrorMap[key] == nil else { - return - } + private let lock: NSLocking + private var didFinishCollecting: DidFinishCollecting? + private var didCompleteWithError: DidCompleteWithError? - try bind( - delegateClass: delegateClass, - interceptDidFinishCollecting: interceptDidFinishCollecting, - interceptDidCompleteWithError: interceptDidCompleteWithError - ) + init(lock: NSLocking = NSLock()) { + self.lock = lock } - static func bind( - delegateClass: AnyClass, + /// Swizzles methods: + /// - `URLSessionTaskDelegate.urlSession(_:task:didFinishCollecting:)` + /// - `URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)` + func swizzle( + delegateClass: URLSessionTaskDelegate.Type, interceptDidFinishCollecting: @escaping (URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void, interceptDidCompleteWithError: @escaping (URLSession, URLSessionTask, Error?) -> Void ) throws { lock.lock() defer { lock.unlock() } - - let didFinishCollecting = try DidFinishCollecting.build(klass: delegateClass) - let key = MetaTypeExtensions.key(from: delegateClass) - - didFinishCollecting.swizzle(intercept: interceptDidFinishCollecting) - didFinishCollectingMap[key] = didFinishCollecting - - let didCompleteWithError = try DidCompleteWithError.build(klass: delegateClass) - didCompleteWithError.swizzle(intercept: interceptDidCompleteWithError) - didCompleteWithErrorMap[key] = didCompleteWithError + didFinishCollecting = try DidFinishCollecting.build(klass: delegateClass) + didCompleteWithError = try DidCompleteWithError.build(klass: delegateClass) + didFinishCollecting?.swizzle(intercept: interceptDidFinishCollecting) + didCompleteWithError?.swizzle(intercept: interceptDidCompleteWithError) } - static func unbind(delegateClass: AnyClass) { + /// Unswizzles all. + /// + /// This method is called during deinit. + func unswizzle() { lock.lock() - defer { lock.unlock() } - - let key = MetaTypeExtensions.key(from: delegateClass) - didFinishCollectingMap[key]??.unswizzle() - didFinishCollectingMap[key] = nil - - didCompleteWithErrorMap[key]??.unswizzle() - didCompleteWithErrorMap[key] = nil + didFinishCollecting?.unswizzle() + didCompleteWithError?.unswizzle() + lock.unlock() } - static func unbindAll() { - lock.lock() - defer { lock.unlock() } - - didFinishCollectingMap.forEach { _, didFinishCollecting in - didFinishCollecting?.unswizzle() - } - didFinishCollectingMap.removeAll() - - didCompleteWithErrorMap.forEach { _, didCompleteWithError in - didCompleteWithError?.unswizzle() - } - didCompleteWithErrorMap.removeAll() + deinit { + unswizzle() } /// Swizzles `URLSessionTaskDelegate.urlSession(_:task:didFinishCollecting:)` method. diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskSwizzler.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskSwizzler.swift index e37d2c7a85..68ad2ef713 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskSwizzler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTaskSwizzler.swift @@ -6,65 +6,45 @@ import Foundation -/// Swizzles `URLSessionTask` methods. -internal class URLSessionTaskSwizzler { - private static var _resume: Resume? - static var resume: Resume? { - get { - lock.lock() - defer { lock.unlock() } - return _resume - } - set { - lock.lock() - defer { lock.unlock() } - _resume = newValue - } - } - - private static var lock = NSRecursiveLock() +internal final class URLSessionTaskSwizzler { + private let lock: NSLocking + private var taskResume: TaskResume? - static var isBinded: Bool { - lock.lock() - defer { lock.unlock() } - return resume != nil + init(lock: NSLocking = NSLock()) { + self.lock = lock } - static func bindIfNeeded(interceptResume: @escaping (URLSessionTask) -> Void) throws { + /// Swizzles `URLSessionTask.resume()` method. + func swizzle( + interceptResume: @escaping (URLSessionTask) -> Void + ) throws { lock.lock() defer { lock.unlock() } - - guard resume == nil else { - return - } - - try bind(interceptResume: interceptResume) + taskResume = try TaskResume.build() + taskResume?.swizzle(intercept: interceptResume) } - static func bind(interceptResume: @escaping (URLSessionTask) -> Void) throws { + /// Unswizzles all. + /// + /// This method is called during deinit. + func unswizzle() { lock.lock() - defer { lock.unlock() } - - self.resume = try Resume.build() - - resume?.swizzle(intercept: interceptResume) + taskResume?.unswizzle() + lock.unlock() } - static func unbind() { - lock.lock() - defer { lock.unlock() } - resume?.unswizzle() - resume = nil + deinit { + unswizzle() } /// Swizzles `URLSessionTask.resume()` method. - class Resume: MethodSwizzler<@convention(c) (URLSessionTask, Selector) -> Void, @convention(block) (URLSessionTask) -> Void> { + class TaskResume: MethodSwizzler<@convention(c) (URLSessionTask, Selector) -> Void, @convention(block) (URLSessionTask) -> Void> { private static let selector = #selector(URLSessionTask.resume) private let method: Method - static func build() throws -> Resume { - return try Resume(selector: self.selector, klass: URLSessionTask.self) + static func build() throws -> TaskResume { + return try TaskResume(selector: self.selector, klass: URLSessionTask.self) } private init(selector: Selector, klass: AnyClass) throws { diff --git a/DatadogInternal/Sources/Swizzling/MethodSwizzler.swift b/DatadogInternal/Sources/Swizzling/MethodSwizzler.swift index 29fb388268..173c6e80e8 100644 --- a/DatadogInternal/Sources/Swizzling/MethodSwizzler.swift +++ b/DatadogInternal/Sources/Swizzling/MethodSwizzler.swift @@ -171,6 +171,15 @@ open class MethodSwizzler { } } +extension MethodSwizzler: CustomDebugStringConvertible { + public var debugDescription: String { + """ + The MethodSwizzler holds swizzling for: + \(overrides.map { method_getName($0.method) }.description) + """ + } +} + // MARK: - Find Method public func dd_class_getInstanceMethod(_ cls: AnyClass, _ name: Selector) throws -> Method { diff --git a/DatadogInternal/Sources/Utils/MetaTypeExtensions.swift b/DatadogInternal/Sources/Utils/MetaTypeExtensions.swift deleted file mode 100644 index 11079814be..0000000000 --- a/DatadogInternal/Sources/Utils/MetaTypeExtensions.swift +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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-2020 Datadog, Inc. - */ - -import Foundation - -internal enum MetaTypeExtensions { - static func key(from anyClass: Any) -> String { - let fullName = String(reflecting: anyClass) - - // fullName may have infix like "(unknown context at $1130f7094)", remove everything inside parenthesis including parenthesis - // Read more: https://github.com/apple/swift/issues/49336 - var key = "" - var parenthesisCount = 0 - for char in fullName { - if char == "(" { - parenthesisCount += 1 - } else if char == ")" { - parenthesisCount -= 1 - } else if parenthesisCount == 0 { - key.append(char) - } - } - - // replace multiple consecutive . with single . - var sanitizedKey = "" - for char in key { - if sanitizedKey.last == "." && char == "." { - continue - } - sanitizedKey.append(char) - } - - return sanitizedKey - } -} diff --git a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift index 873bc421df..d11f40a2da 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift @@ -24,7 +24,6 @@ class NetworkInstrumentationFeatureTests: XCTestCase { } override func tearDown() { - core?.get(feature: NetworkInstrumentationFeature.self)?.unbindAll() core = nil super.tearDown() } @@ -212,13 +211,13 @@ class NetworkInstrumentationFeatureTests: XCTestCase { let url2: URL = .mockRandom() session - .dataTask(with: URLRequest(url: url2)) + .dataTask(with: URLRequest(url: url2)) { _,_,_ in } .resume() // Then - waitForExpectations(timeout: 5, handler: nil) - _ = server.waitAndReturnRequests(count: 1) + _ = server.waitAndReturnRequests(count: 2) + waitForExpectations(timeout: 5, handler: nil) let dateAfterAllRequests = Date() XCTAssertEqual(handler.interceptions.count, 2, "Interceptor should record metrics for 2 tasks") @@ -233,6 +232,29 @@ class NetworkInstrumentationFeatureTests: XCTestCase { } } + func testGivenURLSessionWithCustomDelegate_whenNotInstrumented_itDoesNotInterceptTasks() throws { + let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200), data: Data())) + + // Given + try URLSessionInstrumentation.enableOrThrow(with: .init(delegateClass: MockDelegate.self), in: core) + let session = server.getInterceptedURLSession() // no custom delegate + + // When + let url1: URL = .mockRandom() + session + .dataTask(with: url1) + .resume() + + let url2: URL = .mockRandom() + session + .dataTask(with: URLRequest(url: url2)) + .resume() + + // Then + _ = server.waitAndReturnRequests(count: 2) + XCTAssertEqual(handler.interceptions.count, 0, "Interceptor should not record tasks") + } + func testGivenURLSessionWithDatadogDelegate_whenTaskCompletesWithSuccess_itPassesAllValuesToTheInterceptor() throws { let notifyInterceptionDidStart = expectation(description: "Notify interception did start") let notifyInterceptionDidComplete = expectation(description: "Notify intercepion did complete") @@ -264,11 +286,10 @@ class NetworkInstrumentationFeatureTests: XCTestCase { .resume() // Then - waitForExpectations(timeout: 5, handler: nil) - _ = server.waitAndReturnRequests(count: 1) + _ = server.waitAndReturnRequests(count: 2) + waitForExpectations(timeout: 5, handler: nil) let dateAfterAllRequests = Date() - XCTAssertEqual(handler.interceptions.count, 2, "Interceptor should record metrics for 2 tasks") try [url1, url2].forEach { url in @@ -307,14 +328,12 @@ class NetworkInstrumentationFeatureTests: XCTestCase { // Given let delegate = MockDelegate() try URLSessionInstrumentation.enableOrThrow(with: .init(delegateClass: MockDelegate.self), in: core) - let session = server.getInterceptedURLSession(delegate: delegate) + let session = server.getInterceptedURLSession() // When - let url1: URL = .mockRandom() - _ = try? await session.data(from: url1, delegate: delegate) - - let url2: URL = .mockRandom() - _ = try? await session.data(for: URLRequest(url: url2), delegate: delegate) + _ = try? await session.data(from: .mockRandom(), delegate: delegate) // intercepted + _ = try? await session.data(for: URLRequest(url: .mockRandom()), delegate: delegate) // intercepted + _ = try? await session.data(for: URLRequest(url: .mockRandom())) // not intercepted // Then await dd_fulfillment( @@ -326,7 +345,7 @@ class NetworkInstrumentationFeatureTests: XCTestCase { enforceOrder: true ) - _ = server.waitAndReturnRequests(count: 2) + _ = server.waitAndReturnRequests(count: 3) let dateAfterAllRequests = Date() @@ -340,6 +359,47 @@ class NetworkInstrumentationFeatureTests: XCTestCase { } } + func testGivenURLSessionTask_withCustomDelegate_itInterceptsRequests() throws { + // pre iOS 15 cannot set delegate per task + guard #available(iOS 15, tvOS 15, *) else { + return + } + + let notifyInterceptionDidComplete = expectation(description: "Notify intercepion did complete") + notifyInterceptionDidComplete.expectedFulfillmentCount = 2 + handler.onInterceptionDidComplete = { _ in notifyInterceptionDidComplete.fulfill() } + + let server = ServerMock( + delivery: .success(response: .mockResponseWith(statusCode: 200), data: .mock(ofSize: 10)), + skipIsMainThreadCheck: true + ) + + // Given + let delegate1 = MockDelegate() + let delegate2 = MockDelegate2() + try URLSessionInstrumentation.enableOrThrow(with: .init(delegateClass: MockDelegate.self), in: core) + + let session = server.getInterceptedURLSession() + + // When + let task1 = session.dataTask(with: URL.mockWith(url: "https://www.foo.com/1")) // intercepted + task1.delegate = delegate1 + task1.resume() + + let task2 = session.dataTask(with: URL.mockWith(url: "https://www.foo.com/2")) // intercepted + task2.delegate = delegate1 + task2.resume() + + let task3 = session.dataTask(with: URL.mockWith(url: "https://www.foo.com/3")) // not intercepted + task3.delegate = delegate2 + task3.resume() + + // Then + _ = server.waitAndReturnRequests(count: 3) + waitForExpectations(timeout: 5, handler: nil) + XCTAssertEqual(handler.interceptions.count, 2, "Interceptor should intercept 2 tasks") + } + // MARK: - Usage @available(*, deprecated) @@ -389,6 +449,23 @@ class NetworkInstrumentationFeatureTests: XCTestCase { XCTAssertNil(delegate.interceptor) } + func testWhenEnableInstrumentationOnTheSameDelegate_thenItPrintsAWarning() { + let dd = DD.mockWith(logger: CoreLoggerMock()) + defer { dd.reset() } + + URLSessionInstrumentation.enable(with: .init(delegateClass: MockDelegate.self), in: core) + URLSessionInstrumentation.enable(with: .init(delegateClass: MockDelegate.self), in: core) + + // Then + XCTAssertEqual( + dd.logger.warnLog?.message, + """ + The delegate class MockDelegate is already instrumented. + The previous instrumentation will be disabled in favor of the new one. + """ + ) + } + // MARK: - URLRequest Interception func testGivenOpenTracing_whenInterceptingRequests_itInjectsTrace() throws { @@ -515,6 +592,8 @@ class NetworkInstrumentationFeatureTests: XCTestCase { @available(*, deprecated) func testGivenDelegateSubclass_whenInterceptingRequests_itDetectFirstPartyHost() throws { let notifyInterceptionDidStart = expectation(description: "Notify interception did start") + notifyInterceptionDidStart.expectedFulfillmentCount = 2 + handler.onInterceptionDidStart = { _ in notifyInterceptionDidStart.fulfill() } let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200), data: .mock(ofSize: 10))) @@ -539,14 +618,23 @@ class NetworkInstrumentationFeatureTests: XCTestCase { .dataTask(with: request) .resume() + session + .dataTask(with: request) { _,_,_ in } + .resume() + // Then waitForExpectations(timeout: 5, handler: nil) _ = server.waitAndReturnRequests(count: 1) + + // release the delegate to unswizzle + session.finishTasksAndInvalidate() } @available(*, deprecated) func testGivenCompositeDelegate_whenInterceptingRequests_itDetectFirstPartyHost() throws { let notifyInterceptionDidStart = expectation(description: "Notify interception did start") + notifyInterceptionDidStart.expectedFulfillmentCount = 2 + handler.onInterceptionDidStart = { _ in notifyInterceptionDidStart.fulfill() } let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200), data: .mock(ofSize: 10))) @@ -579,9 +667,16 @@ class NetworkInstrumentationFeatureTests: XCTestCase { .dataTask(with: request) .resume() + session + .dataTask(with: request) { _,_,_ in } + .resume() + // Then waitForExpectations(timeout: 5, handler: nil) _ = server.waitAndReturnRequests(count: 1) + + // release the delegate to unswizzle + session.finishTasksAndInvalidate() } // MARK: - Thread Safety @@ -604,7 +699,9 @@ class NetworkInstrumentationFeatureTests: XCTestCase { { feature.intercept(task: tasks.randomElement()!, additionalFirstPartyHosts: nil) }, { feature.task(tasks.randomElement()!, didReceive: .mockRandom()) }, { feature.task(tasks.randomElement()!, didFinishCollecting: .mockAny()) }, - { feature.task(tasks.randomElement()!, didCompleteWithError: nil) } + { feature.task(tasks.randomElement()!, didCompleteWithError: nil) }, + { try? feature.bind(configuration: .init(delegateClass: MockDelegate.self)) }, + { feature.unbind(delegateClass: MockDelegate.self) } ], iterations: 50 ) @@ -613,4 +710,7 @@ class NetworkInstrumentationFeatureTests: XCTestCase { class MockDelegate: NSObject, URLSessionDataDelegate { } + + class MockDelegate2: NSObject, URLSessionDataDelegate { + } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionDataDelegateSwizzlerTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionDataDelegateSwizzlerTests.swift index f396ecc5b9..fa2fea5338 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionDataDelegateSwizzlerTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionDataDelegateSwizzlerTests.swift @@ -5,117 +5,96 @@ */ import XCTest -@testable import DatadogInternal - -final class URLSessionDataDelegateSwizzlerTests: XCTestCase { - override func tearDown() { - URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate.self) - URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate1.self) - URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate2.self) - XCTAssertEqual(URLSessionDataDelegateSwizzler.didReceiveMap.count, 0) - super.tearDown() - } +@testable import DatadogInternal - func testSwizzling_whenMultipleDelegatesAreGiven() throws { - let delegate1 = MockDelegate1() - let didReceiveData1 = XCTestExpectation(description: "didReceiveData1") - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate1.self) { _, _, _ in - didReceiveData1.fulfill() +class URLSessionDataDelegateSwizzlerTests: XCTestCase { + func testSwizzling_implementedMethods() throws { + class MockDelegate: NSObject, URLSessionDataDelegate { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { } + func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { } + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { } } - let didReceiveData2 = XCTestExpectation(description: "didReceiveData2") - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate2.self) { _, _, _ in - didReceiveData2.fulfill() - } + let delegate = MockDelegate() + let didReceiveData = expectation(description: "didReceiveData") + didReceiveData.assertForOverFulfill = false - let delegate2 = MockDelegate2() - let session1 = URLSession(configuration: .default, delegate: delegate1, delegateQueue: nil) - let task1 = session1.dataTask(with: URL(string: "https://www.datadoghq.com/")!) + // Given + let swizzler = URLSessionDataDelegateSwizzler() - let session2 = URLSession(configuration: .default, delegate: delegate2, delegateQueue: nil) - let task2 = session2.dataTask(with: URL(string: "https://www.datadoghq.com/")!) + try swizzler.swizzle( + delegateClass: MockDelegate.self, + interceptDidReceive: { _, _, _ in + didReceiveData.fulfill() + } + ) - task1.resume() - task2.resume() + // When + let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // intercepted - wait(for: [didReceiveData1, didReceiveData2], timeout: 5) + wait(for: [didReceiveData], timeout: 5) } - func testSwizzling_whenDidReceiveDataIsImplemented() throws { + func testSwizzling_whenMethodsNotImplemented() throws { class MockDelegate: NSObject, URLSessionDataDelegate { - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - } } let delegate = MockDelegate() - let expectation = XCTestExpectation(description: "didReceiveData") + let didReceiveData = expectation(description: "didReceiveData") + didReceiveData.assertForOverFulfill = false - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self) { _, _, _ in - expectation.fulfill() - } + // Given + let swizzler = URLSessionDataDelegateSwizzler() + try swizzler.swizzle( + delegateClass: MockDelegate.self, + interceptDidReceive: { _, _, _ in + didReceiveData.fulfill() + } + ) + + // When let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) - let task = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - task.resume() + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // intercepted - wait(for: [expectation], timeout: 5) + wait(for: [didReceiveData], timeout: 5) } - func testSwizzling_whenDidReceiveDataNotImplemented() throws { + func testUnSwizzling() throws { class MockDelegate: NSObject, URLSessionDataDelegate { } let delegate = MockDelegate() - let expectation = XCTestExpectation(description: "didReceiveData") - - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self) { _, _, _ in - expectation.fulfill() - } - - let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) - let task = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - task.resume() - - wait(for: [expectation], timeout: 5) - } - - func testBindings() throws { - XCTAssertNil(URLSessionDataDelegateSwizzler.didReceiveMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidReceive: { _, _, _ in }) - XCTAssertNotNil(URLSessionDataDelegateSwizzler.didReceiveMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - - try URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidReceive: { _, _, _ in }) - XCTAssertNotNil(URLSessionDataDelegateSwizzler.didReceiveMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) + let expectation = self.expectation(description: "not expected") + expectation.isInverted = true - URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate.self) - XCTAssertNil(URLSessionDataDelegateSwizzler.didReceiveMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - } + // Given + let swizzler = URLSessionDataDelegateSwizzler() - func testConcurrentBinding() throws { - // swiftlint:disable opening_brace trailing_closure - callConcurrently( - closures: [ - { try? URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidReceive: self.intercept(_:dataTask:didReceive:)) }, - { URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate.self) }, - { try? URLSessionDataDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidReceive: self.intercept(_:dataTask:didReceive:)) }, - { URLSessionDataDelegateSwizzler.unbind(delegateClass: MockDelegate.self) }, - ], - iterations: 50 + try swizzler.swizzle( + delegateClass: MockDelegate.self, + interceptDidReceive: { _, _, _ in + expectation.fulfill() + } ) - // swiftlint:enable opening_brace trailing_closure - } - - func intercept(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - } - class MockDelegate: NSObject, URLSessionDataDelegate { - } + // When + let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // not intercepted - class MockDelegate1: NSObject, URLSessionDataDelegate { - } + swizzler.unswizzle() - class MockDelegate2: NSObject, URLSessionDataDelegate { + waitForExpectations(timeout: 5) } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift index ae956a39ef..75671d8f53 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionSwizzlerTests.swift @@ -5,103 +5,31 @@ */ import XCTest -@testable import DatadogInternal - -final class URLSessionSwizzlerTests: XCTestCase { - override func tearDown() { - URLSessionSwizzler.unbind() - XCTAssertNil(URLSessionSwizzler.dataTaskWithURLRequestAndCompletion as Any?) - super.tearDown() - } +@testable import DatadogInternal - func testSwizzling_dataTaskWithURLRequestAndCompletion() throws { - let didInterceptRequest = XCTestExpectation(description: "interceptURLRequest") - let didInterceptTask = XCTestExpectation(description: "interceptTask") - try URLSessionSwizzler.bind(interceptURLRequest: { request in - didInterceptRequest.fulfill() - return self.interceptRequest(request: request) - }, interceptTask: { _ in - didInterceptTask.fulfill() - }) +class URLSessionSwizzlerTests: XCTestCase { + func testSwizzling_dataTaskWithCompletion() throws { + let didInterceptCompletion = expectation(description: "interceptCompletion") + didInterceptCompletion.expectedFulfillmentCount = 2 - let session = URLSession(configuration: .default) - let request = URLRequest(url: URL(string: "https://www.datadoghq.com/")!) - let task = session.dataTask(with: request) { _, _, _ in } - task.resume() + let swizzler = URLSessionSwizzler() - wait( - for: [ - didInterceptRequest, - didInterceptTask - ], - timeout: 5, - enforceOrder: true + try swizzler.swizzle( + interceptCompletionHandler: { _, _, _ in + didInterceptCompletion.fulfill() + } ) - } - - func testSwizzling_testSwizzling_dataTaskWithURLRequest() throws { - // runs only on iOS 12 or below - // because on iOS 12 and below `URLSession.dataTask(with:)` is implemented using `URLSession.dataTask(with:completionHandler:)` - if #available(iOS 13.0, *) { - return - } - - let didInterceptRequest = XCTestExpectation(description: "interceptURLRequest") - let didInterceptTask = XCTestExpectation(description: "interceptTask") - try URLSessionSwizzler.bind(interceptURLRequest: { request in - didInterceptRequest.fulfill() - return self.interceptRequest(request: request) - }, interceptTask: { _ in - didInterceptTask.fulfill() - }) let session = URLSession(configuration: .default) - let request = URLRequest(url: URL(string: "https://www.datadoghq.com/")!) - let task = session.dataTask(with: request) - task.resume() - - wait( - for: [ - didInterceptRequest, - didInterceptTask - ], - timeout: 5, - enforceOrder: true - ) - } - - func testBindings() { - XCTAssertNil(URLSessionSwizzler.dataTaskWithURLRequestAndCompletion as Any?) - - try? URLSessionSwizzler.bind(interceptURLRequest: self.interceptRequest(request:), interceptTask: self.interceptTask(task:)) - XCTAssertNotNil(URLSessionSwizzler.dataTaskWithURLRequestAndCompletion as Any?) - - try? URLSessionSwizzler.bind(interceptURLRequest: self.interceptRequest(request:), interceptTask: self.interceptTask(task:)) - XCTAssertNotNil(URLSessionSwizzler.dataTaskWithURLRequestAndCompletion as Any?) - - URLSessionSwizzler.unbind() - XCTAssertNil(URLSessionSwizzler.dataTaskWithURLRequestAndCompletion as Any?) - } + let url = URL(string: "https://www.datadoghq.com/")! + session.dataTask(with: url) { _, _, _ in }.resume() // intercepted + session.dataTask(with: URLRequest(url: url)) { _, _, _ in }.resume() // intercepted - func testConcurrentBinding() throws { - // swiftlint:disable opening_brace trailing_closure - callConcurrently( - closures: [ - { try? URLSessionSwizzler.bind(interceptURLRequest: self.interceptRequest(request:), interceptTask: self.interceptTask(task:)) }, - { URLSessionSwizzler.unbind() }, - { try? URLSessionSwizzler.bind(interceptURLRequest: self.interceptRequest(request:), interceptTask: self.interceptTask(task:)) }, - { URLSessionSwizzler.unbind() }, - ], - iterations: 50 - ) - // swiftlint:enable opening_brace trailing_closure - } - - func interceptRequest(request: URLRequest) -> URLRequest { - return request - } + swizzler.unswizzle() + session.dataTask(with: url) { _, _, _ in }.resume() // not intercepted + session.dataTask(with: URLRequest(url: url)) { _, _, _ in }.resume() // not intercepted - func interceptTask(task: URLSessionTask) { + wait(for: [didInterceptCompletion], timeout: 5) } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskDelegateSwizzlerTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskDelegateSwizzlerTests.swift index 73c5c8ee22..69839bd4bc 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskDelegateSwizzlerTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskDelegateSwizzlerTests.swift @@ -5,146 +5,104 @@ */ import XCTest -@testable import DatadogInternal - -final class URLSessionTaskDelegateSwizzlerTests: XCTestCase { - override func tearDown() { - URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate.self) - URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate1.self) - URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate2.self) - XCTAssertEqual(URLSessionTaskDelegateSwizzler.didFinishCollectingMap.count, 0) - - super.tearDown() - } - - func testSwizzling_whenMultipleDelegatesAreGiven() throws { - let delegate1 = MockDelegate1() - let didFinishCollecting1 = XCTestExpectation(description: "didFinishCollecting1") - try URLSessionTaskDelegateSwizzler.bind( - delegateClass: MockDelegate1.self, - interceptDidFinishCollecting: { _, _, _ in - didFinishCollecting1.fulfill() - }, interceptDidCompleteWithError: { _, _, _ in - didFinishCollecting1.fulfill() - } - ) - - let delegate2 = MockDelegate2() - let didFinishCollecting2 = XCTestExpectation(description: "didFinishCollecting2") - try URLSessionTaskDelegateSwizzler.bind( - delegateClass: MockDelegate2.self, - interceptDidFinishCollecting: { _, _, _ in - didFinishCollecting2.fulfill() - }, interceptDidCompleteWithError: { _, _, _ in - didFinishCollecting2.fulfill() - } - ) - - let session = URLSession(configuration: .default, delegate: delegate1, delegateQueue: nil) - let task1 = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - - let session2 = URLSession(configuration: .default, delegate: delegate2, delegateQueue: nil) - let task2 = session2.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - - task1.resume() - task2.resume() - wait(for: [didFinishCollecting1, didFinishCollecting2], timeout: 5) - } +@testable import DatadogInternal - func testSwizzling_whenMethodsAreImplemented() throws { +class URLSessionTaskDelegateSwizzlerTests: XCTestCase { + func testSwizzling_implementedMethods() throws { class MockDelegate: NSObject, URLSessionTaskDelegate { - func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { - } + func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { } + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { } } let delegate = MockDelegate() - let didFinishCollecting = XCTestExpectation(description: "didFinishCollecting") + let didFinishCollecting = expectation(description: "didFinishCollecting") + let didCompleteWithError = expectation(description: "didCompleteWithError") - try URLSessionTaskDelegateSwizzler.bind( + // Given + let swizzler = URLSessionTaskDelegateSwizzler() + + try swizzler.swizzle( delegateClass: MockDelegate.self, interceptDidFinishCollecting: { _, _, _ in didFinishCollecting.fulfill() - }, interceptDidCompleteWithError: { _, _, _ in - didFinishCollecting.fulfill() + }, + interceptDidCompleteWithError: { _, _, _ in + didCompleteWithError.fulfill() } ) + // When let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) - let task = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - task.resume() + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // intercepted - wait(for: [didFinishCollecting], timeout: 5) + wait(for: [didFinishCollecting, didCompleteWithError], timeout: 5) } - func testSwizzling_whenMethodsAreNotImplemented() throws { - class MockDelegate: NSObject, URLSessionTaskDelegate { + func testSwizzling_whenMethodsNotImplemented() throws { + class MockDelegate: NSObject, URLSessionDataDelegate { } let delegate = MockDelegate() - let didFinishCollecting = XCTestExpectation(description: "didFinishCollecting") + let didFinishCollecting = expectation(description: "didFinishCollecting") + let didCompleteWithError = expectation(description: "didCompleteWithError") + + // Given + let swizzler = URLSessionTaskDelegateSwizzler() - try URLSessionTaskDelegateSwizzler.bind( + try swizzler.swizzle( delegateClass: MockDelegate.self, interceptDidFinishCollecting: { _, _, _ in didFinishCollecting.fulfill() - }, interceptDidCompleteWithError: { _, _, _ in - didFinishCollecting.fulfill() + }, + interceptDidCompleteWithError: { _, _, _ in + didCompleteWithError.fulfill() } ) + // When let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) - let task = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - task.resume() + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // intercepted - wait(for: [didFinishCollecting], timeout: 5) + wait(for: [didFinishCollecting, didCompleteWithError], timeout: 5) } - func testBindings() throws { - XCTAssertNil(URLSessionTaskDelegateSwizzler.didFinishCollectingMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - - try URLSessionTaskDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidFinishCollecting: interceptDidFinishCollecting, interceptDidCompleteWithError: interceptDidCompleteWithError) - XCTAssertNotNil(URLSessionTaskDelegateSwizzler.didFinishCollectingMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - XCTAssertNotNil(URLSessionTaskDelegateSwizzler.didCompleteWithErrorMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) + func testUnSwizzling() throws { + class MockDelegate: NSObject, URLSessionDataDelegate { + } - try URLSessionTaskDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidFinishCollecting: interceptDidFinishCollecting, interceptDidCompleteWithError: interceptDidCompleteWithError) - XCTAssertNotNil(URLSessionTaskDelegateSwizzler.didFinishCollectingMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - XCTAssertNotNil(URLSessionTaskDelegateSwizzler.didCompleteWithErrorMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) + let delegate = MockDelegate() + let expectation = self.expectation(description: "not expected") + expectation.isInverted = true - URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate.self) - XCTAssertNil(URLSessionTaskDelegateSwizzler.didFinishCollectingMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - XCTAssertNil(URLSessionTaskDelegateSwizzler.didCompleteWithErrorMap[MetaTypeExtensions.key(from: MockDelegate.self)] as Any?) - } + // Given + let swizzler = URLSessionTaskDelegateSwizzler() - func testConcurrentBinding() throws { - // swiftlint:disable opening_brace trailing_closure - callConcurrently( - closures: [ - { try? URLSessionTaskDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidFinishCollecting: self.intercept(session:task:metrics:), interceptDidCompleteWithError: self.interceptDidCompleteWithError(session:task:error:)) }, - { URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate.self) }, - { try? URLSessionTaskDelegateSwizzler.bind(delegateClass: MockDelegate.self, interceptDidFinishCollecting: self.intercept(session:task:metrics:), interceptDidCompleteWithError: self.interceptDidCompleteWithError(session:task:error:)) }, - { URLSessionTaskDelegateSwizzler.unbind(delegateClass: MockDelegate.self) }, - ], - iterations: 50 + try swizzler.swizzle( + delegateClass: MockDelegate.self, + interceptDidFinishCollecting: { _, _, _ in + expectation.fulfill() + }, + interceptDidCompleteWithError: { _, _, _ in + expectation.fulfill() + } ) - // swiftlint:enable opening_brace trailing_closure - } - - func intercept(session: URLSession, task: URLSessionTask, metrics: URLSessionTaskMetrics) { - } - - func interceptDidFinishCollecting(session: URLSession, task: URLSessionTask, metrics: URLSessionTaskMetrics) { - } - func interceptDidCompleteWithError(session: URLSession, task: URLSessionTask, error: Error?) { - } - - class MockDelegate: NSObject, URLSessionTaskDelegate { - } + // When + let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // not intercepted - class MockDelegate1: NSObject, URLSessionTaskDelegate { - } + swizzler.unswizzle() - class MockDelegate2: NSObject, URLSessionTaskDelegate { + waitForExpectations(timeout: 5) } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskSwizzlerTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskSwizzlerTests.swift index 2728648207..dc8f20b952 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskSwizzlerTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/URLSessionTaskSwizzlerTests.swift @@ -5,56 +5,36 @@ */ import XCTest -@testable import DatadogInternal - -final class URLSessionTaskSwizzlerTests: XCTestCase { - override func tearDown() { - URLSessionTaskSwizzler.unbind() - XCTAssertNil(URLSessionTaskSwizzler.resume as Any?) - - super.tearDown() - } - func testSwizzling() throws { - let expectation = XCTestExpectation(description: "resume") - try URLSessionTaskSwizzler.bind { _ in - expectation.fulfill() - } +@testable import DatadogInternal - let session = URLSession(configuration: .default) - let task = session.dataTask(with: URL(string: "https://www.datadoghq.com/")!) - task.resume() +class URLSessionTaskSwizzlerTests: XCTestCase { + func testSwizzling_taskResume() throws { + let expectation = self.expectation(description: "resume") - wait(for: [expectation], timeout: 5) - } + // Given + let swizzler = URLSessionTaskSwizzler() - func testBindings() { - XCTAssertNil(URLSessionTaskSwizzler.resume as Any?) - - try? URLSessionTaskSwizzler.bind(interceptResume: { _ in }) - XCTAssertNotNil(URLSessionTaskSwizzler.resume as Any?) + try swizzler.swizzle( + interceptResume: { _ in + expectation.fulfill() + } + ) - try? URLSessionTaskSwizzler.bind(interceptResume: { _ in }) - XCTAssertNotNil(URLSessionTaskSwizzler.resume as Any?) + // When + let session = URLSession(configuration: .ephemeral) + let url = URL(string: "https://www.datadoghq.com/")! + session + .dataTask(with: url) + .resume() // intercepted - URLSessionTaskSwizzler.unbind() - XCTAssertNil(URLSessionTaskSwizzler.resume as Any?) - } + swizzler.unswizzle() - func testConcurrentBinding() throws { - // swiftlint:disable opening_brace trailing_closure - callConcurrently( - closures: [ - { try? URLSessionTaskSwizzler.bind(interceptResume: self.intercept(task:)) }, - { URLSessionTaskSwizzler.unbind() }, - { try? URLSessionTaskSwizzler.bind(interceptResume: self.intercept(task:)) }, - { URLSessionTaskSwizzler.unbind() }, - ], - iterations: 50 - ) - // swiftlint:enable opening_brace trailing_closure - } + session + .dataTask(with: url) + .resume() // not intercepted - func intercept(task: URLSessionTask) { + // Then + wait(for: [expectation], timeout: 5) } } diff --git a/DatadogInternal/Tests/Swizzling/MethodSwizzlerTests.swift b/DatadogInternal/Tests/Swizzling/MethodSwizzlerTests.swift index 0f9d7edf3e..42756df053 100644 --- a/DatadogInternal/Tests/Swizzling/MethodSwizzlerTests.swift +++ b/DatadogInternal/Tests/Swizzling/MethodSwizzlerTests.swift @@ -110,6 +110,46 @@ class MethodSwizzlerTests: XCTestCase { XCTAssertEqual(before_imp, after_imp) } + func test_swizzle_count() throws { + class Subclass: BaseClass { + override func methodToSwizzle() -> String { "subclass" } + } + class SubSubclass: Subclass { + override func methodToSwizzle() -> String { "subsubclass" } + } + + // Given + let method1 = try dd_class_getInstanceMethod(BaseClass.self, Swizzler.selector) + let method2 = try dd_class_getInstanceMethod(Subclass.self, Swizzler.selector) + let method3 = try dd_class_getInstanceMethod(SubSubclass.self, Swizzler.selector) + + let swizzler1 = Swizzler(method: method1) + let swizzler2 = Swizzler(method: method2) + let swizzler3 = Swizzler(method: method3) + + // When + swizzler1.swizzle { } + XCTAssertEqual(Swizzling.methods.count, 1) + swizzler2.swizzle { } + XCTAssertEqual(Swizzling.methods.count, 2) + swizzler3.swizzle { } + XCTAssertEqual(Swizzling.methods.count, 3) + + // Then + XCTAssertEqual(Swizzling.description, "[methodToSwizzle, methodToSwizzle, methodToSwizzle]") + + // When + swizzler1.unswizzle() + XCTAssertEqual(Swizzling.methods.count, 2) + swizzler2.unswizzle() + XCTAssertEqual(Swizzling.methods.count, 1) + swizzler3.unswizzle() + XCTAssertEqual(Swizzling.methods.count, 0) + + // Then + XCTAssertEqual(Swizzling.description, "[]") + } + func test_swizzle_concurrently() throws { // swiftlint:disable opening_brace diff --git a/DatadogInternal/Tests/Utils/MetaTypeExtensionsTests.swift b/DatadogInternal/Tests/Utils/MetaTypeExtensionsTests.swift deleted file mode 100644 index de9c473ba8..0000000000 --- a/DatadogInternal/Tests/Utils/MetaTypeExtensionsTests.swift +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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-2020 Datadog, Inc. - */ - -import XCTest -@testable import DatadogInternal - -final class MetaTypeExtensionsTests: XCTestCase { - func testKey() throws { - // Prefix depends on the scheme which may be iOS or tvOS, hence only assert suffix. - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelStruct.self).hasSuffix(".TopLevelStruct")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelStruct.NestedStructInStruct.self).hasSuffix(".TopLevelStruct.NestedStructInStruct")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelStruct.NestedClassInStruct.self).hasSuffix(".TopLevelStruct.NestedClassInStruct")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelStruct.NestedEnumInStruct.self).hasSuffix(".TopLevelStruct.NestedEnumInStruct")) - - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelClass.self).hasSuffix(".TopLevelClass")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelClass.NestedStructInClass.self).hasSuffix(".TopLevelClass.NestedStructInClass")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelClass.NestedClassInClass.self).hasSuffix(".TopLevelClass.NestedClassInClass")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelClass.NestedEnumInClass.self).hasSuffix(".TopLevelClass.NestedEnumInClass")) - - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelEnum.self).hasSuffix(".TopLevelEnum")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelEnum.NestedStructInEnum.self).hasSuffix(".TopLevelEnum.NestedStructInEnum")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelEnum.NestedClassInEnum.self).hasSuffix(".TopLevelEnum.NestedClassInEnum")) - XCTAssertTrue(MetaTypeExtensions.key(from: TopLevelEnum.NestedEnumInEnum.self).hasSuffix(".TopLevelEnum.NestedEnumInEnum")) - } -} - -struct TopLevelStruct { - struct NestedStructInStruct {} - - class NestedClassInStruct {} - - enum NestedEnumInStruct {} -} - -class TopLevelClass { - struct NestedStructInClass {} - - class NestedClassInClass {} - - enum NestedEnumInClass {} -} - -enum TopLevelEnum { - struct NestedStructInEnum {} - - class NestedClassInEnum {} - - enum NestedEnumInEnum {} -} diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/RUM/RUMResourcesScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/RUM/RUMResourcesScenarioTests.swift index 9a954e6adb..e2f41cc72d 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/RUM/RUMResourcesScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/RUM/RUMResourcesScenarioTests.swift @@ -27,13 +27,13 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .composition, + instrumentationMethod: .legacyComposition, initializationMethod: .afterSDK ) ) } - func testRUMURLSessionResourcesScenario_directWithAdditionalFirstyPartyHosts() throws { + func testRUMURLSessionResourcesScenario_legacyWithAdditionalFirstyPartyHosts() throws { try runTest( for: "RUMURLSessionResourcesScenario", expectations: Expectations( @@ -41,13 +41,13 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .directWithAdditionalFirstyPartyHosts, + instrumentationMethod: .legacyWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) } - func testRUMURLSessionResourcesScenario_directWithGlobalFirstPartyHosts() throws { + func testRUMURLSessionResourcesScenario_legacyWithFeatureFirstPartyHosts() throws { try runTest( for: "RUMURLSessionResourcesScenario", expectations: Expectations( @@ -55,7 +55,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .directWithGlobalFirstPartyHosts, + instrumentationMethod: .legacyWithFeatureFirstPartyHosts, initializationMethod: .afterSDK ) ) @@ -69,7 +69,35 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .inheritance, + instrumentationMethod: .legacyInheritance, + initializationMethod: .afterSDK + ) + ) + } + + func testRUMURLSessionResourcesScenario_delegateUsingFeatureFirstPartyHosts() throws { + try runTest( + for: "RUMURLSessionResourcesScenario", + expectations: Expectations( + expectedFirstPartyRequestsViewControllerName: "Runner.SendFirstPartyRequestsViewController", + expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" + ), + urlSessionSetup: .init( + instrumentationMethod: .delegateUsingFeatureFirstPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testRUMURLSessionResourcesScenario_delegateWithAdditionalFirstyPartyHosts() throws { + try runTest( + for: "RUMURLSessionResourcesScenario", + expectations: Expectations( + expectedFirstPartyRequestsViewControllerName: "Runner.SendFirstPartyRequestsViewController", + expectedThirdPartyRequestsViewControllerName: "Runner.SendThirdPartyRequestsViewController" + ), + urlSessionSetup: .init( + instrumentationMethod: .delegateWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) @@ -83,13 +111,13 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .composition, + instrumentationMethod: .legacyComposition, initializationMethod: .afterSDK ) ) } - func testRUMNSURLSessionResourcesScenario_directWithAdditionalFirstyPartyHosts() throws { + func testRUMNSURLSessionResourcesScenario_legacyWithAdditionalFirstyPartyHosts() throws { try runTest( for: "RUMNSURLSessionResourcesScenario", expectations: Expectations( @@ -97,13 +125,13 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .directWithAdditionalFirstyPartyHosts, + instrumentationMethod: .legacyWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) } - func testRUMNSURLSessionResourcesScenario_directWithGlobalFirstPartyHosts() throws { + func testRUMNSURLSessionResourcesScenario_legacyWithFeatureFirstPartyHosts() throws { try runTest( for: "RUMNSURLSessionResourcesScenario", expectations: Expectations( @@ -111,7 +139,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .directWithGlobalFirstPartyHosts, + instrumentationMethod: .legacyWithFeatureFirstPartyHosts, initializationMethod: .afterSDK ) ) @@ -125,7 +153,35 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" ), urlSessionSetup: .init( - instrumentationMethod: .inheritance, + instrumentationMethod: .legacyInheritance, + initializationMethod: .afterSDK + ) + ) + } + + func testRUMNSURLSessionResourcesScenario_delegateUsingFeatureFirstPartyHosts() throws { + try runTest( + for: "RUMNSURLSessionResourcesScenario", + expectations: Expectations( + expectedFirstPartyRequestsViewControllerName: "ObjcSendFirstPartyRequestsViewController", + expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" + ), + urlSessionSetup: .init( + instrumentationMethod: .delegateUsingFeatureFirstPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testRUMNSURLSessionResourcesScenario_delegateWithAdditionalFirstyPartyHosts() throws { + try runTest( + for: "RUMNSURLSessionResourcesScenario", + expectations: Expectations( + expectedFirstPartyRequestsViewControllerName: "ObjcSendFirstPartyRequestsViewController", + expectedThirdPartyRequestsViewControllerName: "ObjcSendThirdPartyRequestsViewController" + ), + urlSessionSetup: .init( + instrumentationMethod: .delegateWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) @@ -152,7 +208,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts { // Requesting this first party by the app should create the RUM Resource and inject tracing headers into the request. let firstPartyPOSTResourceURL = customFirstPartyServerSession.recordingURL // Requesting this first party by the app should create the RUM Error. - let firstPartyBadResourceURL = URL(string: "https://foo.bar")! + let firstPartyBadResourceURL = URL(string: "https://foo.bar/")! // Requesting this third party by the app should create the RUM Resource. let thirdPartyGETResourceURL = URL(string: "https://shopist.io/categories.json")! diff --git a/IntegrationTests/IntegrationScenarios/Scenarios/Tracing/TracingURLSessionScenarioTests.swift b/IntegrationTests/IntegrationScenarios/Scenarios/Tracing/TracingURLSessionScenarioTests.swift index ab791b66ef..2e533a44da 100644 --- a/IntegrationTests/IntegrationScenarios/Scenarios/Tracing/TracingURLSessionScenarioTests.swift +++ b/IntegrationTests/IntegrationScenarios/Scenarios/Tracing/TracingURLSessionScenarioTests.swift @@ -18,17 +18,17 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { try runTest( for: "TracingURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .composition, + instrumentationMethod: .legacyComposition, initializationMethod: .afterSDK ) ) } - func testTracingURLSessionScenario_directWithAdditionalFirstyPartyHosts() throws { + func testTracingURLSessionScenario_legacyWithAdditionalFirstyPartyHosts() throws { try runTest( for: "TracingURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .directWithAdditionalFirstyPartyHosts, + instrumentationMethod: .legacyWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) @@ -38,7 +38,27 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { try runTest( for: "TracingURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .directWithGlobalFirstPartyHosts, + instrumentationMethod: .legacyWithFeatureFirstPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testTracingURLSessionScenario_delegateUsingFeatureFirstPartyHosts() throws { + try runTest( + for: "TracingURLSessionScenario", + urlSessionSetup: .init( + instrumentationMethod: .delegateUsingFeatureFirstPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testTracingURLSessionScenario_delegateWithAdditionalFirstyPartyHosts() throws { + try runTest( + for: "TracingURLSessionScenario", + urlSessionSetup: .init( + instrumentationMethod: .delegateWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) @@ -48,7 +68,7 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { try runTest( for: "TracingURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .inheritance, + instrumentationMethod: .legacyInheritance, initializationMethod: .afterSDK ) ) @@ -58,17 +78,47 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { try runTest( for: "TracingNSURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .composition, + instrumentationMethod: .legacyComposition, + initializationMethod: .afterSDK + ) + ) + } + + func testTracingNSURLSessionScenario_legacyWithFeatureFirstPartyHosts() throws { + try runTest( + for: "TracingNSURLSessionScenario", + urlSessionSetup: .init( + instrumentationMethod: .legacyWithFeatureFirstPartyHosts, initializationMethod: .afterSDK ) ) } - func testTracingNSURLSessionScenario_directWithAdditionalFirstyPartyHosts() throws { + func testTracingNSURLSessionScenario_legacyWithAdditionalFirstyPartyHosts() throws { + try runTest( + for: "TracingNSURLSessionScenario", + urlSessionSetup: .init( + instrumentationMethod: .legacyWithAdditionalFirstyPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testTracingNSURLSessionScenario_delegateUsingFeatureFirstPartyHosts() throws { + try runTest( + for: "TracingNSURLSessionScenario", + urlSessionSetup: .init( + instrumentationMethod: .delegateUsingFeatureFirstPartyHosts, + initializationMethod: .afterSDK + ) + ) + } + + func testTracingNSURLSessionScenario_delegateWithAdditionalFirstyPartyHosts() throws { try runTest( for: "TracingNSURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .directWithAdditionalFirstyPartyHosts, + instrumentationMethod: .delegateWithAdditionalFirstyPartyHosts, initializationMethod: .afterSDK ) ) @@ -78,7 +128,7 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { try runTest( for: "TracingNSURLSessionScenario", urlSessionSetup: .init( - instrumentationMethod: .inheritance, + instrumentationMethod: .legacyInheritance, initializationMethod: .afterSDK ) ) @@ -103,7 +153,7 @@ class TracingURLSessionScenarioTests: IntegrationTests, TracingCommonAsserts { // Requesting this first party by the app should create the `SpanEvent`. let firstPartyPOSTResourceURL = customFirstPartyServerSession.recordingURL // Requesting this first party by the app should create the `SpanEvent` with error. - let firstPartyBadResourceURL = URL(string: "https://foo.bar")! + let firstPartyBadResourceURL = URL(string: "https://foo.bar/")! // Requesting this third party by the app should NOT create the `SpanEvent`. let thirdPartyGETResourceURL = URL(string: "https://bitrise.io")! diff --git a/IntegrationTests/Runner/Environment.swift b/IntegrationTests/Runner/Environment.swift index d6ca220603..ddac1ddca1 100644 --- a/IntegrationTests/Runner/Environment.swift +++ b/IntegrationTests/Runner/Environment.swift @@ -45,16 +45,22 @@ internal struct URLSessionSetup: Codable { enum InstrumentationMethod: CaseIterable, Codable { /// Use `DDURLSessionDelegate` class directly. /// and define `firstPartyHosts` in feature configuration. - case directWithGlobalFirstPartyHosts + case legacyWithFeatureFirstPartyHosts /// Use `DDURLSessionDelegate` class directly - /// and define `firstPartyHosts` in `URLSession` delegate's configuration. - case directWithAdditionalFirstyPartyHosts + /// and define `firstPartyHosts` with delegate's configuration. + case legacyWithAdditionalFirstyPartyHosts /// Use `DDURLSessionDelegate` by inheriting it in a subclass (see: `InheritedURLSessionDelegate`). /// and define `firstPartyHosts` in feature configuration. - case inheritance + case legacyInheritance /// Use `DDURLSessionDelegate` by compositing it custom delegate class (see: `CompositedURLSessionDelegate`). /// and define `firstPartyHosts` in feature configuration. - case composition + case legacyComposition + /// Use a custom delegate. + /// and define `firstPartyHosts` in feature configuration. + case delegateUsingFeatureFirstPartyHosts + /// Use a custom delegate. + /// and define `firstPartyHosts` with delegate's configuration. + case delegateWithAdditionalFirstyPartyHosts } /// A method of instrumenting `URLSession` with `DDURLSessionDelegate`. diff --git a/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift b/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift index 57a14cf67f..d49d6944f0 100644 --- a/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/RUM/RUMScenarios.swift @@ -181,7 +181,7 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { config.uiKitViewsPredicate = DefaultUIKitRUMViewsPredicate() switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts, .inheritance, .composition: + case .legacyWithFeatureFirstPartyHosts, .legacyInheritance, .legacyComposition, .delegateUsingFeatureFirstPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace( hosts: [ @@ -193,7 +193,7 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { ), resourceAttributesProvider: rumResourceAttributesProvider(request:response:data:error:) ) - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts, .delegateWithAdditionalFirstyPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace(hosts: [], sampleRate: 100), // hosts will be set through `DDURLSessionDelegate` resourceAttributesProvider: rumResourceAttributesProvider(request:response:data:error:) @@ -207,16 +207,12 @@ class RUMResourcesBaseScenario: URLSessionBaseScenario { /// sent with Swift `URLSession` from two VCs. The first VC calls first party resources, the second one calls third parties. final class RUMURLSessionResourcesScenario: RUMResourcesBaseScenario, TestScenario { static let storyboardName = "URLSessionScenario" - - override func configureFeatures() { super.configureFeatures() } } /// Scenario which uses RUM resources instrumentation to track bunch of network requests /// sent with Objective-c `NSURLSession` from two VCs. The first VC calls first party resources, the second one calls third parties. final class RUMNSURLSessionResourcesScenario: RUMResourcesBaseScenario, TestScenario { static let storyboardName = "NSURLSessionScenario" - - override func configureFeatures() { super.configureFeatures() } } /// Scenario which uses RUM manual instrumentation API to send bunch of RUM events. Each event contains some diff --git a/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift b/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift index b082ac08a3..9dc5a24ae6 100644 --- a/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/Tracing/TracingScenarios.swift @@ -47,7 +47,7 @@ class TracingURLSessionBaseScenario: URLSessionBaseScenario { } switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts, .inheritance, .composition: + case .legacyWithFeatureFirstPartyHosts, .legacyInheritance, .legacyComposition, .delegateUsingFeatureFirstPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace( hosts: [ @@ -58,7 +58,7 @@ class TracingURLSessionBaseScenario: URLSessionBaseScenario { sampleRate: 100 ) ) - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts, .delegateWithAdditionalFirstyPartyHosts: config.urlSessionTracking = .init( firstPartyHostsTracing: .trace(hosts: [], sampleRate: 100) // hosts will be set through `DDURLSessionDelegate` ) diff --git a/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift b/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift index c3af654fc1..637b7ffa6f 100644 --- a/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift +++ b/IntegrationTests/Runner/Scenarios/URLSession/URLSessionScenarios.swift @@ -20,22 +20,30 @@ private class InheritedURLSessionDelegate: DDURLSessionDelegate { /// An example of instrumenting existing `URLSessionDelegate` with `DDURLSessionDelegate` through composition. private class CompositedURLSessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate, __URLSessionDelegateProviding { // MARK: - __URLSessionDelegateProviding conformance + let ddURLSessionDelegate = DatadogURLSessionDelegate() // MARK: - __URLSessionDelegateProviding handling - func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + ddURLSessionDelegate.urlSession(session, task: task, didFinishCollecting: metrics) // forward to DD /* run custom logic */ } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + ddURLSessionDelegate.urlSession(session, task: task, didCompleteWithError: error) // forward to DD /* run custom logic */ } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + ddURLSessionDelegate.urlSession(session, dataTask: dataTask, didReceive: data) // forward to DD /* run custom logic */ } } +/// An example of instrumenting existing `URLSessionDelegate` with `DDURLSessionDelegate` through inheritance. +private class CustomURLSessionDelegate: NSObject, URLSessionDataDelegate { + +} + /// Base scenario for `URLSession` and `NSURLSession` instrumentation. It makes /// both Swift and Objective-C tests share the same endpoints and SDK configuration. /// @@ -141,9 +149,9 @@ class URLSessionBaseScenario: NSObject { let delegate: URLSessionDataDelegate switch setup.instrumentationMethod { - case .directWithGlobalFirstPartyHosts: + case .legacyWithFeatureFirstPartyHosts: delegate = DDURLSessionDelegate() - case .directWithAdditionalFirstyPartyHosts: + case .legacyWithAdditionalFirstyPartyHosts: delegate = DDURLSessionDelegate( additionalFirstPartyHosts: [ customGETResourceURL.host!, @@ -151,11 +159,25 @@ class URLSessionBaseScenario: NSObject { badResourceURL.host! ] ) - case .inheritance: + case .legacyInheritance: delegate = InheritedURLSessionDelegate() - case .composition: + case .legacyComposition: delegate = CompositedURLSessionDelegate() - URLSessionInstrumentation.enable(with: .init(delegateClass: CompositedURLSessionDelegate.self)) + case .delegateUsingFeatureFirstPartyHosts: + URLSessionInstrumentation.enable(with: .init(delegateClass: CustomURLSessionDelegate.self)) + delegate = CustomURLSessionDelegate() + case .delegateWithAdditionalFirstyPartyHosts: + URLSessionInstrumentation.enable( + with: .init( + delegateClass: CustomURLSessionDelegate.self, + firstPartyHostsTracing: .trace(hosts: [ + customGETResourceURL.host!, + customPOSTRequest.url!.host!, + badResourceURL.host! + ]) + ) + ) + delegate = CustomURLSessionDelegate() } return URLSession( diff --git a/TestUtilities/Mocks/FoundationMocks.swift b/TestUtilities/Mocks/FoundationMocks.swift index 96446a41ef..c8e1e5017e 100644 --- a/TestUtilities/Mocks/FoundationMocks.swift +++ b/TestUtilities/Mocks/FoundationMocks.swift @@ -720,6 +720,7 @@ extension URLSessionTaskTransactionMetrics { private class URLSessionDataTaskMock: URLSessionDataTask { private let _originalRequest: URLRequest override var originalRequest: URLRequest? { _originalRequest } + override var currentRequest: URLRequest? { _originalRequest } private let _response: URLResponse override var response: URLResponse? { _response }