diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index d6f45df09d5..5b4568d0fd4 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -389,8 +389,6 @@ 7B6C5ED6264E62CA0010D138 /* SentryTransactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6C5ED5264E62CA0010D138 /* SentryTransactionTests.swift */; }; 7B6C5EDA264E8D860010D138 /* SentryFramesTrackingIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6C5ED9264E8D860010D138 /* SentryFramesTrackingIntegration.h */; }; 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6C5EDB264E8DA80010D138 /* SentryFramesTrackingIntegration.m */; }; - 7B6C5F8126034354007F7DFF /* SentryWatchdogTerminationLogic.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6C5F8026034354007F7DFF /* SentryWatchdogTerminationLogic.h */; }; - 7B6C5F8726034395007F7DFF /* SentryWatchdogTerminationLogic.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6C5F8626034395007F7DFF /* SentryWatchdogTerminationLogic.m */; }; 7B6CC50224EE5A42001816D7 /* SentryHubTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6CC50124EE5A42001816D7 /* SentryHubTests.swift */; }; 7B6D1261265F784000C9BE4B /* PrivateSentrySDKOnly.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D1260265F784000C9BE4B /* PrivateSentrySDKOnly.m */; }; 7B6D135C27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6D135B27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift */; }; @@ -427,10 +425,8 @@ 7B96572226830D2400C66E25 /* SentryScopeSyncC.c in Sources */ = {isa = PBXBuildFile; fileRef = 7B96572126830D2400C66E25 /* SentryScopeSyncC.c */; }; 7B965728268321CD00C66E25 /* SentryCrashScopeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B965727268321CD00C66E25 /* SentryCrashScopeObserverTests.swift */; }; 7B984A9F28E572AF001F4BEE /* CrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B984A9E28E572AF001F4BEE /* CrashReport.swift */; }; - 7B98D7BC25FB607300C5A389 /* SentryWatchdogTerminationTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B98D7BB25FB607300C5A389 /* SentryWatchdogTerminationTracker.h */; }; 7B98D7CB25FB64EC00C5A389 /* SentryWatchdogTerminationTrackingIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B98D7CA25FB64EC00C5A389 /* SentryWatchdogTerminationTrackingIntegration.h */; }; 7B98D7CF25FB650F00C5A389 /* SentryWatchdogTerminationTrackingIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B98D7CE25FB650F00C5A389 /* SentryWatchdogTerminationTrackingIntegration.m */; }; - 7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B98D7D225FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m */; }; 7B98D7E025FB73B900C5A389 /* SentryWatchdogTerminationTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B98D7DF25FB73B900C5A389 /* SentryWatchdogTerminationTrackerTests.swift */; }; 7B98D7EC25FB7C4900C5A389 /* SentryAppStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B98D7EB25FB7C4900C5A389 /* SentryAppStateTests.swift */; }; 7BA0C04628055F8E003E0326 /* SentryTransportAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BA0C04528055F8E003E0326 /* SentryTransportAdapter.h */; }; @@ -968,10 +964,13 @@ F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */; }; F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */; }; F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */; }; + F419CB5A2F11976C00B28982 /* SentryWatchdogTerminationLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F419CB592F11976C00B28982 /* SentryWatchdogTerminationLogicTests.swift */; }; F426748D2EB11E7900E09150 /* SentryReplayApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */; }; F429D37F2E8532A300DBF387 /* HttpDateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429D37D2E8532A300DBF387 /* HttpDateParser.swift */; }; F429D39A2E85360F00DBF387 /* RetryAfterHeaderParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429D3992E85360F00DBF387 /* RetryAfterHeaderParser.swift */; }; F429D3AA2E8562EF00DBF387 /* RateLimitParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429D3A82E8562EF00DBF387 /* RateLimitParser.swift */; }; + F43CC2F22F0C45CC005996DF /* SentryWatchdogTerminationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43CC2F02F0C45CC005996DF /* SentryWatchdogTerminationLogic.swift */; }; + F43CC2F32F0C45CC005996DF /* SentryWatchdogTerminationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43CC2F12F0C45CC005996DF /* SentryWatchdogTerminationTracker.swift */; }; F443DB272E09BE8C009A9045 /* LoadValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */; }; F44544012EDF5A6200EFE964 /* SentryAutoSessionTrackingIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44544002EDF5A5C00EFE964 /* SentryAutoSessionTrackingIntegration.swift */; }; F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */; }; @@ -993,6 +992,7 @@ F46DA6C32E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */; }; F46E0BA82E6A19F900DAA75C /* SentryCrashWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46E0BA72E6A19F900DAA75C /* SentryCrashWrapperTests.swift */; }; F4763A072EB3AC370006DF1E /* SentryThreadInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8AFCE72E8434A0007A0E18 /* SentryThreadInspector.swift */; }; + F48594832F11A0670035B6EE /* SentryWatchdogTerminationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F485947D2F11A0610035B6EE /* SentryWatchdogTerminationConstants.swift */; }; F48E2E0A2E6637840073CB22 /* TestSentryCrashWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E2E092E6637840073CB22 /* TestSentryCrashWrapper.swift */; }; F48F21F52EF1F05000E33FC1 /* SentryAppStartMeasurement+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = F48F21F42EF1F04C00E33FC1 /* SentryAppStartMeasurement+Private.h */; }; F48F74F32E5F9959009D4E7D /* SentryCrashBinaryImageCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F48F74F22E5F9959009D4E7D /* SentryCrashBinaryImageCacheTests.m */; }; @@ -1703,8 +1703,6 @@ 7B6C5ED5264E62CA0010D138 /* SentryTransactionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionTests.swift; sourceTree = ""; }; 7B6C5ED9264E8D860010D138 /* SentryFramesTrackingIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryFramesTrackingIntegration.h; path = include/SentryFramesTrackingIntegration.h; sourceTree = ""; }; 7B6C5EDB264E8DA80010D138 /* SentryFramesTrackingIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFramesTrackingIntegration.m; sourceTree = ""; }; - 7B6C5F8026034354007F7DFF /* SentryWatchdogTerminationLogic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryWatchdogTerminationLogic.h; path = include/SentryWatchdogTerminationLogic.h; sourceTree = ""; }; - 7B6C5F8626034395007F7DFF /* SentryWatchdogTerminationLogic.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryWatchdogTerminationLogic.m; sourceTree = ""; }; 7B6CC50124EE5A42001816D7 /* SentryHubTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryHubTests.swift; sourceTree = ""; }; 7B6D1260265F784000C9BE4B /* PrivateSentrySDKOnly.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrivateSentrySDKOnly.m; sourceTree = ""; }; 7B6D135B27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEnvelopeRateLimitDelegate.swift; sourceTree = ""; }; @@ -1746,10 +1744,8 @@ 7B965727268321CD00C66E25 /* SentryCrashScopeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashScopeObserverTests.swift; sourceTree = ""; }; 7B9660B12783500E0014A767 /* ThreadSanitizer.sup */ = {isa = PBXFileReference; lastKnownFileType = text; path = ThreadSanitizer.sup; sourceTree = ""; }; 7B984A9E28E572AF001F4BEE /* CrashReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReport.swift; sourceTree = ""; }; - 7B98D7BB25FB607300C5A389 /* SentryWatchdogTerminationTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryWatchdogTerminationTracker.h; path = include/SentryWatchdogTerminationTracker.h; sourceTree = ""; }; 7B98D7CA25FB64EC00C5A389 /* SentryWatchdogTerminationTrackingIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryWatchdogTerminationTrackingIntegration.h; path = include/SentryWatchdogTerminationTrackingIntegration.h; sourceTree = ""; }; 7B98D7CE25FB650F00C5A389 /* SentryWatchdogTerminationTrackingIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryWatchdogTerminationTrackingIntegration.m; sourceTree = ""; }; - 7B98D7D225FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryWatchdogTerminationTracker.m; sourceTree = ""; }; 7B98D7DF25FB73B900C5A389 /* SentryWatchdogTerminationTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryWatchdogTerminationTrackerTests.swift; sourceTree = ""; }; 7B98D7EB25FB7C4900C5A389 /* SentryAppStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAppStateTests.swift; sourceTree = ""; }; 7BA0C04528055F8E003E0326 /* SentryTransportAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTransportAdapter.h; path = include/SentryTransportAdapter.h; sourceTree = ""; }; @@ -2356,10 +2352,13 @@ F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Tags.swift"; sourceTree = ""; }; F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+User.swift"; sourceTree = ""; }; F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Context.swift"; sourceTree = ""; }; + F419CB592F11976C00B28982 /* SentryWatchdogTerminationLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryWatchdogTerminationLogicTests.swift; sourceTree = ""; }; F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayApiTests.swift; sourceTree = ""; }; F429D37D2E8532A300DBF387 /* HttpDateParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpDateParser.swift; sourceTree = ""; }; F429D3992E85360F00DBF387 /* RetryAfterHeaderParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryAfterHeaderParser.swift; sourceTree = ""; }; F429D3A82E8562EF00DBF387 /* RateLimitParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimitParser.swift; sourceTree = ""; }; + F43CC2F02F0C45CC005996DF /* SentryWatchdogTerminationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryWatchdogTerminationLogic.swift; sourceTree = ""; }; + F43CC2F12F0C45CC005996DF /* SentryWatchdogTerminationTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryWatchdogTerminationTracker.swift; sourceTree = ""; }; F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadValidatorTests.swift; sourceTree = ""; }; F44544002EDF5A5C00EFE964 /* SentryAutoSessionTrackingIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAutoSessionTrackingIntegration.swift; sourceTree = ""; }; F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashDynamicLinker+Test.h"; sourceTree = ""; }; @@ -2380,6 +2379,7 @@ F465D2122E87E36F00FE2DD9 /* RateLimits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimits.swift; sourceTree = ""; }; F46DA6C22E1DBCA000DF6E3B /* SentryScopePersistentStore+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Helper.swift"; sourceTree = ""; }; F46E0BA72E6A19F900DAA75C /* SentryCrashWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashWrapperTests.swift; sourceTree = ""; }; + F485947D2F11A0610035B6EE /* SentryWatchdogTerminationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryWatchdogTerminationConstants.swift; sourceTree = ""; }; F48E2E092E6637840073CB22 /* TestSentryCrashWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryCrashWrapper.swift; sourceTree = ""; }; F48F21F42EF1F04C00E33FC1 /* SentryAppStartMeasurement+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryAppStartMeasurement+Private.h"; sourceTree = ""; }; F48F74F22E5F9959009D4E7D /* SentryCrashBinaryImageCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashBinaryImageCacheTests.m; sourceTree = ""; }; @@ -3677,10 +3677,6 @@ 7BE0DC34272AE78C004FA8B7 /* WatchdogTerminations */ = { isa = PBXGroup; children = ( - 7B98D7BB25FB607300C5A389 /* SentryWatchdogTerminationTracker.h */, - 7B98D7D225FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m */, - 7B6C5F8026034354007F7DFF /* SentryWatchdogTerminationLogic.h */, - 7B6C5F8626034395007F7DFF /* SentryWatchdogTerminationLogic.m */, 7B98D7CA25FB64EC00C5A389 /* SentryWatchdogTerminationTrackingIntegration.h */, 7B98D7CE25FB650F00C5A389 /* SentryWatchdogTerminationTrackingIntegration.m */, D452FC512DDB4AF100AFF56F /* Processors */, @@ -3805,6 +3801,7 @@ 7BE0DC3D272AE9CB004FA8B7 /* WatchdogTerminations */ = { isa = PBXGroup; children = ( + F419CB592F11976C00B28982 /* SentryWatchdogTerminationLogicTests.swift */, D452FE6B2DDC873400AFF56F /* Processors */, 7BFE7A0927A1B6B000D2B66E /* SentryWatchdogTerminationTrackingIntegrationTests.swift */, 0A2D7BB929152CBF008727AF /* SentryWatchdogTerminationScopeObserverTests.swift */, @@ -4269,6 +4266,9 @@ D452FCBC2DDB6FA800AFF56F /* WatchdogTerminations */ = { isa = PBXGroup; children = ( + F485947D2F11A0610035B6EE /* SentryWatchdogTerminationConstants.swift */, + F43CC2F02F0C45CC005996DF /* SentryWatchdogTerminationLogic.swift */, + F43CC2F12F0C45CC005996DF /* SentryWatchdogTerminationTracker.swift */, FAAB95FE2EA301600030A2DB /* SentryWatchdogTerminationScopeObserver.swift */, D452FCBD2DDB6FC200AFF56F /* Processors */, ); @@ -5166,7 +5166,6 @@ 7B4E23BE251A2BD500060D68 /* SentryCrashIntegrationSessionHandler.h in Headers */, D81A346C291AECC7005A27A9 /* PrivateSentrySDKOnly.h in Headers */, D88817DA26D72AB800BF2251 /* SentryTraceContext.h in Headers */, - 7B6C5F8126034354007F7DFF /* SentryWatchdogTerminationLogic.h in Headers */, 63FE708520DA4C1000CDBAE8 /* SentryCrashReportFilter.h in Headers */, 84354E1129BF944900CDBB8B /* SentryProfileTimeseries.h in Headers */, D8ACE3CD2762187D00F5A213 /* SentryNSDataSwizzling.h in Headers */, @@ -5387,7 +5386,6 @@ 84A7890A2C0E9F6400FF0803 /* SentrySpan+Private.h in Headers */, 7BC5B6FA290BCDE500D99477 /* SentryHttpStatusCodeRange+Private.h in Headers */, 9286059529A5096600F96038 /* SentryGeo.h in Headers */, - 7B98D7BC25FB607300C5A389 /* SentryWatchdogTerminationTracker.h in Headers */, 8E4E7C7D25DAB287006AB9E2 /* SentryTracer.h in Headers */, 7BC8523724588115005A70F0 /* SentryDataCategory.h in Headers */, FAAB96482EA6843E0030A2DB /* SentryANRTrackerInternalDelegate.h in Headers */, @@ -5803,7 +5801,6 @@ 84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */, F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */, 63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */, - 7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */, 8E564AE8267AF22600FE117D /* SentryNetworkTrackingIntegration.m in Sources */, 63AA75EF1EB8B3C400D153DE /* SentryClient.m in Sources */, D4B0DC7F2DA9257A00DE61B6 /* SentryRenderVideoResult.swift in Sources */, @@ -5871,7 +5868,6 @@ 8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */, F451FAA62E0B304E0050ACF2 /* LoadValidator.swift in Sources */, D81988C02BEBFFF70020E36C /* SentryReplayRecording.swift in Sources */, - 7B6C5F8726034395007F7DFF /* SentryWatchdogTerminationLogic.m in Sources */, 63FE708D20DA4C1000CDBAE8 /* SentryCrashReportFilterBasic.m in Sources */, F40E42352EA1887000E53876 /* SentryFramesTracker.swift in Sources */, 63FE718120DA4C1100CDBAE8 /* SentryCrashDoctor.m in Sources */, @@ -5971,6 +5967,7 @@ 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */, F4FE9E082E6248E40014FED5 /* SentryCrashWrapper.swift in Sources */, FA914E592ECF968500C54BDD /* UserFeedbackIntegration.swift in Sources */, + F48594832F11A0670035B6EE /* SentryWatchdogTerminationConstants.swift in Sources */, 849B8F9A2C6E906900148E1F /* SentryUserFeedbackConfiguration.swift in Sources */, 63295AF71EF3C7DB002D4490 /* SentryNSDictionarySanitize.m in Sources */, 7B8ECBFC26498958005FE2EF /* SentryDefaultAppStateManager.m in Sources */, @@ -5993,6 +5990,8 @@ FA67DD012DDBD4EA00896B02 /* SentryMXCallStackTree.swift in Sources */, FAAB29F12E3D252300ACD577 /* SentrySession.swift in Sources */, FA6252042EB52DD900BFC967 /* SentryHub.swift in Sources */, + F43CC2F22F0C45CC005996DF /* SentryWatchdogTerminationLogic.swift in Sources */, + F43CC2F32F0C45CC005996DF /* SentryWatchdogTerminationTracker.swift in Sources */, FA67DD022DDBD4EA00896B02 /* SentryViewScreenshotProvider.swift in Sources */, FA67DD032DDBD4EA00896B02 /* SentryTransactionNameSource.swift in Sources */, FA67DD042DDBD4EA00896B02 /* SwiftDescriptor.swift in Sources */, @@ -6483,6 +6482,7 @@ D875ED0B276CC84700422FAC /* SentryFileIOTrackerTests.swift in Sources */, D808FB92281BF6EC009A2A33 /* SentryUIEventTrackingIntegrationTests.swift in Sources */, 7BC6EC04255C235F0059822A /* SentryFrameTests.swift in Sources */, + F419CB5A2F11976C00B28982 /* SentryWatchdogTerminationLogicTests.swift in Sources */, D4A0C2332E9E3D1400791353 /* SentryInfoPlistKeyTests.swift in Sources */, D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */, 63FE720420DA66EC00CDBAE8 /* SentryCrashString_Tests.m in Sources */, diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 77064d1d914..bb916737434 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -34,7 +34,6 @@ #import "SentryTransportFactory.h" #import "SentryUseNSExceptionCallstackWrapper.h" #import "SentryUser.h" -#import "SentryWatchdogTerminationTracker.h" #if SENTRY_HAS_UIKIT # import @@ -943,7 +942,7 @@ - (BOOL)isWatchdogTermination:(SentryEvent *)event isFatalEvent:(BOOL)isFatalEve SentryException *exception = event.exceptions[0]; return exception.mechanism != nil && - [exception.mechanism.type isEqualToString:SentryWatchdogTerminationMechanismType]; + [exception.mechanism.type isEqualToString:SentryWatchdogTerminationConstants.MechanismType]; } - (void)applyCultureContextToEvent:(SentryEvent *)event diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m index 5c8288a18db..3073ddeae2a 100644 --- a/Sources/Sentry/SentryCrashIntegration.m +++ b/Sources/Sentry/SentryCrashIntegration.m @@ -13,7 +13,6 @@ #import "SentrySpan+Private.h" #import "SentrySwift.h" #import "SentryTracer.h" -#import "SentryWatchdogTerminationLogic.h" #import #import #import diff --git a/Sources/Sentry/SentryCrashIntegrationSessionHandler.m b/Sources/Sentry/SentryCrashIntegrationSessionHandler.m index 0f2ba8de087..4952a65c443 100644 --- a/Sources/Sentry/SentryCrashIntegrationSessionHandler.m +++ b/Sources/Sentry/SentryCrashIntegrationSessionHandler.m @@ -5,7 +5,6 @@ #import "SentryLogC.h" #import "SentrySDK+Private.h" #import "SentrySwift.h" -#import "SentryWatchdogTerminationLogic.h" @interface SentryCrashIntegrationSessionHandler () diff --git a/Sources/Sentry/SentryWatchdogTerminationLogic.m b/Sources/Sentry/SentryWatchdogTerminationLogic.m deleted file mode 100644 index bb9c6dee469..00000000000 --- a/Sources/Sentry/SentryWatchdogTerminationLogic.m +++ /dev/null @@ -1,117 +0,0 @@ -#import -#import - -#if SENTRY_HAS_UIKIT - -# import -# import - -@interface SentryWatchdogTerminationLogic () - -@property (nonatomic, strong) SentryOptions *options; -@property (nonatomic, strong) SentryCrashWrapper *crashAdapter; -@property (nonatomic, strong) SentryAppStateManager *appStateManager; - -@end - -@implementation SentryWatchdogTerminationLogic - -- (instancetype)initWithOptions:(SentryOptions *)options - crashAdapter:(SentryCrashWrapper *)crashAdapter - appStateManager:(SentryAppStateManager *)appStateManager -{ - if (self = [super init]) { - self.options = options; - self.crashAdapter = crashAdapter; - self.appStateManager = appStateManager; - } - return self; -} - -- (BOOL)isWatchdogTermination -{ - if (!self.options.enableWatchdogTerminationTracking) { - return NO; - } - - SentryAppState *_Nullable nullablePreviousAppState = - [self.appStateManager loadPreviousAppState]; - // If there is no previous app state, we can't do anything. - if (nullablePreviousAppState == nil) { - return NO; - } - SentryAppState *_Nonnull previousAppState = (SentryAppState *_Nonnull)nullablePreviousAppState; - - SentryAppState *currentAppState = [self.appStateManager buildCurrentAppState]; - if (self.crashAdapter.isSimulatorBuild) { - return NO; - } - - // If the release name is different we assume it's an upgrade - if (currentAppState.releaseName != nil && previousAppState.releaseName != nil - && ![currentAppState.releaseName - isEqualToString:SENTRY_UNWRAP_NULLABLE(NSString, previousAppState.releaseName)]) { - return NO; - } - - // The OS was upgraded - if (![currentAppState.osVersion isEqualToString:previousAppState.osVersion]) { - return NO; - } - - // The app may have been terminated due to device reboot - if (previousAppState.systemBootTimestamp != currentAppState.systemBootTimestamp) { - return NO; - } - - // This value can change when installing test builds using Xcode or when installing an app - // on a device using ad-hoc distribution. - if (![currentAppState.vendorId - isEqualToString:SENTRY_UNWRAP_NULLABLE(NSString, previousAppState.vendorId)]) { - return NO; - } - - // Restarting the app in development is a termination we can't catch and would falsely - // report watchdog termiations. - if (previousAppState.isDebugging) { - return NO; - } - - // The app was terminated normally - if (previousAppState.wasTerminated) { - return NO; - } - - // The app crashed on the previous run. No Watchdog Termination. - if (self.crashAdapter.crashedLastLaunch) { - return NO; - } - - // The SDK wasn't running, so *any* crash after the SDK got closed would be seen as a Watchdog - // Termination. - if (!previousAppState.isSDKRunning) { - return NO; - } - - // Was the app in foreground/active ? - // If the app was in background we can't reliably tell if it was a Watchdog Termination or not. - if (!previousAppState.isActive) { - return NO; - } - - if (previousAppState.isANROngoing) { - return NO; - } - - // When calling SentrySDK.start twice we would wrongly report a Watchdog Termination. We can - // only report a Watchdog Termination when the SDK is started the first time. - if (SentrySDKInternal.startInvocations != 1) { - return NO; - } - - return YES; -} - -@end - -#endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryWatchdogTerminationTracker.m b/Sources/Sentry/SentryWatchdogTerminationTracker.m deleted file mode 100644 index 27efb9a38bf..00000000000 --- a/Sources/Sentry/SentryWatchdogTerminationTracker.m +++ /dev/null @@ -1,133 +0,0 @@ -#import "SentryDateUtils.h" -#import "SentryEvent+Private.h" -#import "SentrySwift.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@interface SentryWatchdogTerminationTracker () - -@property (nonatomic, strong) SentryOptions *options; -@property (nonatomic, strong) SentryWatchdogTerminationLogic *watchdogTerminationLogic; -@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; -@property (nonatomic, strong) SentryAppStateManager *appStateManager; -@property (nonatomic, strong) SentryFileManager *fileManager; -@property (nonatomic, strong) SentryScopePersistentStore *scopePersistentStore; - -@end - -@implementation SentryWatchdogTerminationTracker - -- (instancetype)initWithOptions:(SentryOptions *)options - watchdogTerminationLogic:(SentryWatchdogTerminationLogic *)watchdogTerminationLogic - appStateManager:(SentryAppStateManager *)appStateManager - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - fileManager:(SentryFileManager *)fileManager - scopePersistentStore:(SentryScopePersistentStore *)scopePersistentStore -{ - if (self = [super init]) { - self.options = options; - self.watchdogTerminationLogic = watchdogTerminationLogic; - self.appStateManager = appStateManager; - self.dispatchQueue = dispatchQueueWrapper; - self.fileManager = fileManager; - self.scopePersistentStore = scopePersistentStore; - } - return self; -} - -- (void)start -{ -#if SENTRY_HAS_UIKIT - [self.appStateManager start]; - - [self.dispatchQueue dispatchAsyncWithBlock:^{ - if ([self.watchdogTerminationLogic isWatchdogTermination]) { - SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentryLevelFatal]; - - [self addBreadcrumbsToEvent:event]; - [self addContextToEvent:event]; - event.user = [self.scopePersistentStore readPreviousUserFromDisk]; - event.dist = [self.scopePersistentStore readPreviousDistFromDisk]; - event.environment = [self.scopePersistentStore readPreviousEnvironmentFromDisk]; - event.tags = [self.scopePersistentStore readPreviousTagsFromDisk]; - event.extra = [self.scopePersistentStore readPreviousExtrasFromDisk]; - event.fingerprint = [self.scopePersistentStore readPreviousFingerprintFromDisk]; - // Termination events always have fatal level, so we are not reading from disk - - SentryException *exception = - [[SentryException alloc] initWithValue:SentryWatchdogTerminationExceptionValue - type:SentryWatchdogTerminationExceptionType]; - SentryMechanism *mechanism = - [[SentryMechanism alloc] initWithType:SentryWatchdogTerminationMechanismType]; - mechanism.handled = @(NO); - exception.mechanism = mechanism; - event.exceptions = @[ exception ]; - - // We don't need to update the releaseName of the event to the previous app state as we - // assume it's not a watchdog termination when the releaseName changed between app - // starts. - [SentrySDKInternal captureFatalEvent:event]; - } - }]; -#else // !SENTRY_HAS_UIKIT - SENTRY_LOG_INFO( - @"NO UIKit -> SentryWatchdogTerminationTracker will not track Watchdog Terminations."); - return; -#endif // SENTRY_HAS_UIKIT -} - -- (void)addBreadcrumbsToEvent:(SentryEvent *)event -{ - // Set to empty list so no breadcrumbs of the current scope are added - event.breadcrumbs = @[]; - - // Load the previous breadcrumbs from disk, which are already serialized - event.serializedBreadcrumbs = [self.fileManager readPreviousBreadcrumbs]; - if (event.serializedBreadcrumbs.count > self.options.maxBreadcrumbs) { - event.serializedBreadcrumbs = [event.serializedBreadcrumbs - subarrayWithRange:NSMakeRange( - event.serializedBreadcrumbs.count - self.options.maxBreadcrumbs, - self.options.maxBreadcrumbs)]; - } - - NSDictionary *lastBreadcrumb = event.serializedBreadcrumbs.lastObject; - if (lastBreadcrumb && [lastBreadcrumb objectForKey:@"timestamp"]) { - NSString *timestampIso8601String = [lastBreadcrumb objectForKey:@"timestamp"]; - event.timestamp = sentry_fromIso8601String(timestampIso8601String); - } -} - -- (void)addContextToEvent:(SentryEvent *)event -{ - // Load the previous context from disk, or create an empty one if it doesn't exist - NSDictionary *> *previousContext = - [self.scopePersistentStore readPreviousContextFromDisk]; - NSMutableDictionary *context = - [[NSMutableDictionary alloc] initWithDictionary:previousContext ?: @{}]; - - // We only report watchdog terminations if the app was in the foreground. So, we can - // already set it. We can't set it in the client because the client uses the current - // application state, and the app could be in the background when executing this code. - NSMutableDictionary *appContext = - [[NSMutableDictionary alloc] initWithDictionary:event.context[@"app"] ?: @{}]; - appContext[@"in_foreground"] = @(YES); - context[@"app"] = appContext; - - event.context = context; -} - -- (void)stop -{ -#if SENTRY_HAS_UIKIT - [self.appStateManager stop]; -#endif // SENTRY_HAS_UIKIT -} - -@end diff --git a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m index 89e49ff36c0..a0e576af295 100644 --- a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m +++ b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m @@ -9,8 +9,6 @@ # import # import # import -# import -# import NS_ASSUME_NONNULL_BEGIN @interface SentryWatchdogTerminationTrackingIntegration () diff --git a/Sources/Sentry/include/SentryWatchdogTerminationLogic.h b/Sources/Sentry/include/SentryWatchdogTerminationLogic.h deleted file mode 100644 index ee0add889ba..00000000000 --- a/Sources/Sentry/include/SentryWatchdogTerminationLogic.h +++ /dev/null @@ -1,26 +0,0 @@ -#import "SentryDefines.h" - -#if SENTRY_HAS_UIKIT - -@class SentryAppState; -@class SentryAppStateManager; -@class SentryCrashWrapper; -@class SentryFileManager; -@class SentryOptions; - -NS_ASSUME_NONNULL_BEGIN - -@interface SentryWatchdogTerminationLogic : NSObject -SENTRY_NO_INIT - -- (instancetype)initWithOptions:(SentryOptions *)options - crashAdapter:(SentryCrashWrapper *)crashAdapter - appStateManager:(SentryAppStateManager *)appStateManager; - -- (BOOL)isWatchdogTermination; - -@end - -NS_ASSUME_NONNULL_END - -#endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/SentryWatchdogTerminationTracker.h b/Sources/Sentry/include/SentryWatchdogTerminationTracker.h deleted file mode 100644 index 50e3e329f7e..00000000000 --- a/Sources/Sentry/include/SentryWatchdogTerminationTracker.h +++ /dev/null @@ -1,38 +0,0 @@ -#import "SentryDefines.h" - -@class SentryAppStateManager; -@class SentryDispatchQueueWrapper; -@class SentryFileManager; -@class SentryOptions; -@class SentryWatchdogTerminationLogic; -@class SentryScopePersistentStore; - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const SentryWatchdogTerminationExceptionType = @"WatchdogTermination"; -static NSString *const SentryWatchdogTerminationExceptionValue - = @"The OS watchdog terminated your app, possibly because it overused RAM."; -static NSString *const SentryWatchdogTerminationMechanismType = @"watchdog_termination"; - -/** - * Detect watchdog terminations based on heuristics described in a blog post: - * https://engineering.fb.com/2015/08/24/ios/reducing-fooms-in-the-facebook-ios-app/ If a watchdog - * termination is detected, the SDK sends it as crash event. Only works for iOS, tvOS and - * macCatalyst. - */ -@interface SentryWatchdogTerminationTracker : NSObject -SENTRY_NO_INIT - -- (instancetype)initWithOptions:(SentryOptions *)options - watchdogTerminationLogic:(SentryWatchdogTerminationLogic *)watchdogTerminationLogic - appStateManager:(SentryAppStateManager *)appStateManager - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper - fileManager:(SentryFileManager *)fileManager - scopePersistentStore:(SentryScopePersistentStore *)scopeStore; - -- (void)start; -- (void)stop; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Swift/AppState/SentryAppStateManager.swift b/Sources/Swift/AppState/SentryAppStateManager.swift index beefe493994..6af2d614268 100644 --- a/Sources/Swift/AppState/SentryAppStateManager.swift +++ b/Sources/Swift/AppState/SentryAppStateManager.swift @@ -54,6 +54,38 @@ import UIKit } #if (os(iOS) || os(tvOS) || os(visionOS)) && !SENTRY_NO_UIKIT + +#if SENTRY_TEST || SENTRY_TEST_CI + /// Test-only initializer to allow injecting a custom `buildCurrentAppState` closure for testing + /// scenarios where the current app state needs specific values (e.g., nil vendorId). + init(releaseName: String?, crashWrapper: SentryCrashWrapper, fileManager: SentryFileManager?, sysctlWrapper: SentrySysctl, customBuildCurrentAppState: @escaping () -> SentryAppState) { + self.releaseName = releaseName + self.crashWrapper = crashWrapper + self.fileManager = fileManager + let lock = NSRecursiveLock() + _buildCurrentAppState = customBuildCurrentAppState + let updateAppState: (@escaping (SentryAppState) -> Void) -> Void = { block in + lock.synchronized { + let appState = fileManager?.readAppState() + if let appState { + block(appState) + fileManager?.store(appState) + } + } + } + _updateAppState = updateAppState + helper = SentryDefaultAppStateManager(storeCurrent: { + fileManager?.store(customBuildCurrentAppState()) + }, updateTerminated: { + updateAppState { $0.wasTerminated = true } + }, updateSDKNotRunning: { + updateAppState { $0.isSDKRunning = false } + }, updateActive: { active in + updateAppState { $0.isActive = active } + }) + } +#endif + var startCount: Int { helper.startCount } diff --git a/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationConstants.swift b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationConstants.swift new file mode 100644 index 00000000000..b61d1cb5adf --- /dev/null +++ b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationConstants.swift @@ -0,0 +1,8 @@ +import Foundation + +@_spi(Private) @objc public +final class SentryWatchdogTerminationConstants: NSObject { + @objc public static let ExceptionType: String = "WatchdogTermination" + @objc public static let ExceptionValue: String = "The OS watchdog terminated your app, possibly because it overused RAM." + @objc public static let MechanismType: String = "watchdog_termination" +} diff --git a/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogic.swift b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogic.swift new file mode 100644 index 00000000000..322fd7f5b97 --- /dev/null +++ b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogic.swift @@ -0,0 +1,104 @@ +@_implementationOnly import _SentryPrivate +import Foundation + +#if (os(iOS) || os(tvOS) || os(visionOS)) && !SENTRY_NO_UIKIT +@_spi(Private) @objc +public final class SentryWatchdogTerminationLogic: NSObject { + + private let options: Options + private let crashAdapter: SentryCrashWrapper + private let appStateManager: SentryAppStateManager + + @objc public init(options: Options, crashAdapter: SentryCrashWrapper, appStateManager: SentryAppStateManager) { + self.options = options + self.crashAdapter = crashAdapter + self.appStateManager = appStateManager + super.init() + } + + // swiftlint:disable:next cyclomatic_complexity + @objc public func isWatchdogTermination() -> Bool { + guard options.enableWatchdogTerminationTracking else { + return false + } + + guard let previousAppState = appStateManager.loadPreviousAppState() else { + // If there is no previous app state, we can't do anything. + return false + } + + let currentAppState = appStateManager.buildCurrentAppState() + + if crashAdapter.isSimulatorBuild { + return false + } + + // If the release name is different we assume it's an upgrade + if let currentRelease = currentAppState.releaseName, + let previousRelease = previousAppState.releaseName, + currentRelease != previousRelease { + return false + } + + // The OS was upgraded + if currentAppState.osVersion != previousAppState.osVersion { + return false + } + + // The app may have been terminated due to device reboot + if previousAppState.systemBootTimestamp != currentAppState.systemBootTimestamp { + return false + } + + // This value can change when installing test builds using Xcode or when installing an app + // on a device using ad-hoc distribution. + // SentryAppState return nil id vendorId is missing when loaded from a JSON file, so + // any case where vendorId is nil, is automatically handled + guard let currentVendorId = currentAppState.vendorId, + let previousVendorId = previousAppState.vendorId, + currentVendorId == previousVendorId else { + return false + } + + // Restarting the app in development is a termination we can't catch and would falsely + // report watchdog terminations. + if previousAppState.isDebugging { + return false + } + + // The app was terminated normally + if previousAppState.wasTerminated { + return false + } + + // The app crashed on the previous run. No Watchdog Termination. + if crashAdapter.crashedLastLaunch { + return false + } + + // The SDK wasn't running, so *any* crash after the SDK got closed would be seen as a Watchdog + // Termination. + if !previousAppState.isSDKRunning { + return false + } + + // Was the app in foreground/active ? + // If the app was in background we can't reliably tell if it was a Watchdog Termination or not. + if !previousAppState.isActive { + return false + } + + if previousAppState.isANROngoing { + return false + } + + // When calling SentrySDK.start twice we would wrongly report a Watchdog Termination. We can + // only report a Watchdog Termination when the SDK is started the first time. + if SentrySDKInternal.startInvocations != 1 { + return false + } + + return true + } +} +#endif diff --git a/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationTracker.swift b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationTracker.swift new file mode 100644 index 00000000000..e35e55ea641 --- /dev/null +++ b/Sources/Swift/Integrations/WatchdogTerminations/SentryWatchdogTerminationTracker.swift @@ -0,0 +1,115 @@ +@_implementationOnly import _SentryPrivate +import Foundation + +#if (os(iOS) || os(tvOS) || os(visionOS)) && !SENTRY_NO_UIKIT +/** + * Detect watchdog terminations based on heuristics described in a blog post: + * https://engineering.fb.com/2015/08/24/ios/reducing-fooms-in-the-facebook-ios-app/ If a watchdog + * termination is detected, the SDK sends it as crash event. Only works for iOS, tvOS and + * macCatalyst. + */ +@_spi(Private) @objc public +final class SentryWatchdogTerminationTracker: NSObject { + + @objc public static let ExceptionType: String = "WatchdogTermination" + @objc public static let ExceptionValue: String = "The OS watchdog terminated your app, possibly because it overused RAM." + @objc public static let MechanismType: String = "watchdog_termination" + + private let options: Options + private let watchdogTerminationLogic: SentryWatchdogTerminationLogic + private let appStateManager: SentryAppStateManager + private let dispatchQueue: SentryDispatchQueueWrapper + private let fileManager: SentryFileManager + private let scopePersistentStore: SentryScopePersistentStore + + @objc public init(options: Options, + watchdogTerminationLogic: SentryWatchdogTerminationLogic, + appStateManager: SentryAppStateManager, + dispatchQueueWrapper: SentryDispatchQueueWrapper, + fileManager: SentryFileManager, + scopePersistentStore: SentryScopePersistentStore) { + self.options = options + self.watchdogTerminationLogic = watchdogTerminationLogic + self.appStateManager = appStateManager + self.dispatchQueue = dispatchQueueWrapper + self.fileManager = fileManager + self.scopePersistentStore = scopePersistentStore + } + + @objc public func start() { + appStateManager.start() + + dispatchQueue.dispatchAsync { + self.captureStartEvent() + } + } + + private func captureStartEvent() { + guard self.watchdogTerminationLogic.isWatchdogTermination() else { + return + } + + let event = Event(level: .fatal) + + self.addBreadcrumbs(to: event) + self.addContext(to: event) + event.user = self.scopePersistentStore.readPreviousUserFromDisk() + event.dist = self.scopePersistentStore.readPreviousDistFromDisk() + event.environment = self.scopePersistentStore.readPreviousEnvironmentFromDisk() + event.tags = self.scopePersistentStore.readPreviousTagsFromDisk() + event.extra = self.scopePersistentStore.readPreviousExtrasFromDisk() + event.fingerprint = self.scopePersistentStore.readPreviousFingerprintFromDisk() + // Termination events always have fatal level, so we are not reading from disk + + let exception = Exception( + value: SentryWatchdogTerminationConstants.ExceptionValue, + type: SentryWatchdogTerminationConstants.ExceptionType) + let mechanism = Mechanism(type: SentryWatchdogTerminationConstants.MechanismType) + mechanism.handled = false + exception.mechanism = mechanism + event.exceptions = [exception] + + // We don't need to update the releaseName of the event to the previous app state as we + // assume it's not a watchdog termination when the releaseName changed between app + // starts. + SentrySDKInternal.captureFatalEvent(event) + } + + private func addBreadcrumbs(to event: Event) { + // Set to empty list so no breadcrumbs of the current scope are added + event.breadcrumbs = [] + + // Load the previous breadcrumbs from disk, which are already serialized + var serializedBreadcrumbs = fileManager.readPreviousBreadcrumbs() + if serializedBreadcrumbs.count > options.maxBreadcrumbs { + let start = serializedBreadcrumbs.count - Int(options.maxBreadcrumbs) + serializedBreadcrumbs = Array(serializedBreadcrumbs[start...]) + } + event.serializedBreadcrumbs = serializedBreadcrumbs + + if let lastBreadcrumb = serializedBreadcrumbs.last as? [String: Any], + let timestampString = lastBreadcrumb["timestamp"] as? String { + event.timestamp = sentry_fromIso8601String(timestampString) + } + } + + private func addContext(to event: Event) { + // Load the previous context from disk, or create an empty one if it doesn't exist + let previousContext = scopePersistentStore.readPreviousContextFromDisk() ?? [:] + var context = previousContext + + // We only report watchdog terminations if the app was in the foreground. So, we can + // already set it. We can't set it in the client because the client uses the current + // application state, and the app could be in the background when executing this code. + var appContext = (event.context?["app"] as? [String: Any]) ?? [:] + appContext["in_foreground"] = true + context["app"] = appContext + + event.context = context + } + + @objc public func stop() { + appStateManager.stop() + } +} +#endif diff --git a/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogicTests.swift b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogicTests.swift new file mode 100644 index 00000000000..9743f8c32d2 --- /dev/null +++ b/Tests/SentryTests/Integrations/WatchdogTerminations/SentryWatchdogTerminationLogicTests.swift @@ -0,0 +1,377 @@ +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + +@_spi(Private) @testable import Sentry +@_spi(Private) import SentryTestUtils +import XCTest + +class SentryWatchdogTerminationLogicTests: XCTestCase { + + private static let dsn = TestConstants.dsnForTestCase(type: SentryWatchdogTerminationLogicTests.self) + + private struct Fixture { + let options: Options + let crashWrapper: TestSentryCrashWrapper + let fileManager: SentryFileManager + let sysctl: TestSysctl + let dispatchQueue: TestSentryDispatchQueueWrapper + + init() throws { + sysctl = TestSysctl() + SentryDependencyContainer.sharedInstance().sysctlWrapper = sysctl + + options = Options() + options.dsn = SentryWatchdogTerminationLogicTests.dsn + options.enableWatchdogTerminationTracking = true + options.releaseName = "1.0.0" + + dispatchQueue = TestSentryDispatchQueueWrapper() + let dateProvider = TestCurrentDateProvider() + fileManager = try XCTUnwrap(SentryFileManager(options: options, dateProvider: dateProvider, dispatchQueueWrapper: dispatchQueue)) + + crashWrapper = TestSentryCrashWrapper(processInfoWrapper: ProcessInfo.processInfo) + } + + func getSut(customCurrentAppState: SentryAppState? = nil) -> SentryWatchdogTerminationLogic { + let appStateManager: SentryAppStateManager + + if let customState = customCurrentAppState { + appStateManager = SentryAppStateManager( + releaseName: options.releaseName, + crashWrapper: crashWrapper, + fileManager: fileManager, + sysctlWrapper: sysctl, + customBuildCurrentAppState: { customState } + ) + } else { + appStateManager = SentryAppStateManager( + releaseName: options.releaseName, + crashWrapper: crashWrapper, + fileManager: fileManager, + sysctlWrapper: sysctl + ) + } + + return SentryWatchdogTerminationLogic( + options: options, + crashAdapter: crashWrapper, + appStateManager: appStateManager + ) + } + + func createAppState( + releaseName: String? = "1.0.0", + osVersion: String = "17.0", + vendorId: String? = TestData.someUUID, + isDebugging: Bool = false, + isActive: Bool = true, + wasTerminated: Bool = false, + isSDKRunning: Bool = true, + isANROngoing: Bool = false + ) -> SentryAppState { + let appState = SentryAppState( + releaseName: releaseName, + osVersion: osVersion, + vendorId: vendorId, + isDebugging: isDebugging, + systemBootTimestamp: sysctl.systemBootTimestamp + ) + appState.isActive = isActive + appState.wasTerminated = wasTerminated + appState.isSDKRunning = isSDKRunning + appState.isANROngoing = isANROngoing + return appState + } + + func storePreviousAppState(_ appState: SentryAppState) { + fileManager.store(appState) + fileManager.moveAppStateToPreviousAppState() + } + } + + private var fixture: Fixture! + + override func setUpWithError() throws { + try super.setUpWithError() + fixture = try Fixture() + SentrySDKInternal.startInvocations = 1 + } + + override func tearDown() { + super.tearDown() + fixture.fileManager.deleteAllFolders() + clearTestState() + } + + // MARK: - Watchdog Termination Tracking Disabled + + func testIsWatchdogTermination_whenTrackingDisabled_shouldReturnFalse() { + // -- Arrange -- + fixture.options.enableWatchdogTerminationTracking = false + let sut = fixture.getSut() + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - No Previous App State + + func testIsWatchdogTermination_whenNoPreviousAppState_shouldReturnFalse() { + // -- Arrange -- + let sut = fixture.getSut() + // No previous app state stored + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Simulator Build + + func testIsWatchdogTermination_whenSimulatorBuild_shouldReturnFalse() { + // -- Arrange -- + fixture.crashWrapper.internalIsSimulatorBuild = true + fixture.storePreviousAppState(fixture.createAppState()) + let sut = fixture.getSut() + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Release Name Changes + + func testIsWatchdogTermination_whenDifferentReleaseName_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(releaseName: "0.9.0")) + let currentAppState = fixture.createAppState(releaseName: "1.0.0") + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - OS VersVersion Changes + + func testIsWatchdogTermination_whenDifferentOSVersion_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(osVersion: "16.0")) + let currentAppState = fixture.createAppState(osVersion: "17.0") + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - System Boot Timestamp Changes + + func testIsWatchdogTermination_whenDifferentBootTimestamp_shouldReturnFalse() { + // -- Arrange -- + let previousAppState = SentryAppState( + releaseName: "1.0.0", + osVersion: "17.0", + vendorId: TestData.someUUID, + isDebugging: false, + systemBootTimestamp: fixture.sysctl.systemBootTimestamp.addingTimeInterval(-3_600) + ) + previousAppState.isActive = true + fixture.storePreviousAppState(previousAppState) + let sut = fixture.getSut() + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Vendor ID Changes + + func testIsWatchdogTermination_whenDifferentVendorId_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(vendorId: "different-vendor-id")) + let currentAppState = fixture.createAppState(vendorId: TestData.someUUID) + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + func testIsWatchdogTermination_whenPreviousVendorIdNil_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(vendorId: nil)) + let currentAppState = fixture.createAppState(vendorId: TestData.someUUID) + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + func testIsWatchdogTermination_whenCurrentVendorIdNil_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(vendorId: TestData.someUUID)) + let currentAppState = fixture.createAppState(vendorId: nil) + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + func testIsWatchdogTermination_whenBothVendorIdsNil_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(vendorId: nil)) + let currentAppState = fixture.createAppState(vendorId: nil) + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Debugging + + func testIsWatchdogTermination_whenWasDebugging_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(isDebugging: true)) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Normal Termination + + func testIsWatchdogTermination_whenWasTerminated_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(wasTerminated: true)) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Crash Last Launch + + func testIsWatchdogTermination_whenCrashedLastLaunch_shouldReturnFalse() { + // -- Arrange -- + fixture.crashWrapper.internalCrashedLastLaunch = true + fixture.storePreviousAppState(fixture.createAppState()) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - SDK Not Running + + func testIsWatchdogTermination_whenSDKNotRunning_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(isSDKRunning: false)) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - App Not Active + + func testIsWatchdogTermination_whenAppNotActive_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(isActive: false)) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - ANR Ongoing + + func testIsWatchdogTermination_whenANROngoing_shouldReturnFalse() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState(isANROngoing: true)) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - SDK Started Multiple Times + + func testIsWatchdogTermination_whenSDKStartedMultipleTimes_shouldReturnFalse() { + // -- Arrange -- + SentrySDKInternal.startInvocations = 2 + fixture.storePreviousAppState(fixture.createAppState()) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertFalse(result) + } + + // MARK: - Valid Watchdog Termination + + func testIsWatchdogTermination_whenAllConditionsMet_shouldReturnTrue() { + // -- Arrange -- + fixture.storePreviousAppState(fixture.createAppState()) + let currentAppState = fixture.createAppState() + let sut = fixture.getSut(customCurrentAppState: currentAppState) + + // -- Act -- + let result = sut.isWatchdogTermination() + + // -- Assert -- + XCTAssertTrue(result) + } +} + +#endif diff --git a/Tests/SentryTests/Protocol/TestData.swift b/Tests/SentryTests/Protocol/TestData.swift index 2da2e043ab9..e223184bff4 100644 --- a/Tests/SentryTests/Protocol/TestData.swift +++ b/Tests/SentryTests/Protocol/TestData.swift @@ -274,8 +274,8 @@ class TestData { static var oomEvent: Event { let event = Event(level: SentryLevel.fatal) - let exception = Exception(value: SentryWatchdogTerminationExceptionValue, type: SentryWatchdogTerminationExceptionType) - exception.mechanism = Mechanism(type: SentryWatchdogTerminationMechanismType) + let exception = Exception(value: SentryWatchdogTerminationConstants.ExceptionValue, type: SentryWatchdogTerminationConstants.ExceptionType) + exception.mechanism = Mechanism(type: SentryWatchdogTerminationConstants.MechanismType) event.exceptions = [exception] return event } diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 172d4266ec9..033c0021d5e 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -175,8 +175,6 @@ #import "SentryUseNSExceptionCallstackWrapper.h" #import "SentryViewHierarchyIntegration.h" #import "SentryWatchdogTerminationBreadcrumbProcessor.h" -#import "SentryWatchdogTerminationLogic.h" -#import "SentryWatchdogTerminationTracker.h" #import "SentryWatchdogTerminationTrackingIntegration.h" #import "SentryWeakMap.h" #import "TestSentrySpan.h"