diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj index 7f2fe9a967c..62ed0d3c93f 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj @@ -19,6 +19,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 0A94157D28F6B893006A5DD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 63AA759B1EB8AEF500D153DE; + remoteInfo = Sentry; + }; + 0A94157F28F6B893006A5DD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 63AA76651EB8CB2F00D153DE; + remoteInfo = SentryTests; + }; 7B64385C26A6C0A6000D0F65 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7BB6224126A56C4E00D0E75E /* Project object */; @@ -101,6 +115,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0A94157928F6B893006A5DD1 /* Products */ = { + isa = PBXGroup; + children = ( + 0A94157E28F6B893006A5DD1 /* Sentry.framework */, + 0A94158028F6B893006A5DD1 /* SentryTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 7B64385826A6C0A6000D0F65 /* iOS-SwiftUI-UITests */ = { isa = PBXGroup; children = ( @@ -242,6 +265,10 @@ productRefGroup = 7BB6224A26A56C4E00D0E75E /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 0A94157928F6B893006A5DD1 /* Products */; + ProjectRef = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + }, { ProductGroup = 84D4FEAB28ECD52E00EDAAFE /* Products */; ProjectRef = 84D4FEAA28ECD52E00EDAAFE /* Sentry.xcodeproj */; @@ -256,6 +283,20 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 0A94157E28F6B893006A5DD1 /* Sentry.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Sentry.framework; + remoteRef = 0A94157D28F6B893006A5DD1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0A94158028F6B893006A5DD1 /* SentryTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = SentryTests.xctest; + remoteRef = 0A94157F28F6B893006A5DD1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 84D4FEB228ECD52E00EDAAFE /* Sentry.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 058fb8510b7..5f225cfca8b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 0A56DA5F28ABA01B00C400D5 /* SentryTransactionContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A56DA5E28ABA01B00C400D5 /* SentryTransactionContext+Private.h */; }; 0A6EEADD28A657970076B469 /* UIViewRecursiveDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6EEADC28A657970076B469 /* UIViewRecursiveDescriptionTests.swift */; }; 0A8F0A392886CC70000B15F6 /* SentryPermissionsObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AABE2EE288592750057ED69 /* SentryPermissionsObserver.h */; }; + 0A94158228F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A94158128F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift */; }; 0A9BF4E228A114940068D266 /* SentryViewHierarchyIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A9BF4E128A114940068D266 /* SentryViewHierarchyIntegration.m */; }; 0A9BF4E428A114B50068D266 /* SentryViewHierarchyIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A9BF4E328A114B50068D266 /* SentryViewHierarchyIntegration.h */; }; 0A9BF4E928A125390068D266 /* TestSentryViewHierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9BF4E628A123270068D266 /* TestSentryViewHierarchy.swift */; }; @@ -757,6 +758,7 @@ 0A5370A028A3EC2400B2DCDE /* SentryViewHierarchyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewHierarchyTests.swift; sourceTree = ""; }; 0A56DA5E28ABA01B00C400D5 /* SentryTransactionContext+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryTransactionContext+Private.h"; path = "include/SentryTransactionContext+Private.h"; sourceTree = ""; }; 0A6EEADC28A657970076B469 /* UIViewRecursiveDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewRecursiveDescriptionTests.swift; sourceTree = ""; }; + 0A94158128F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAppStateManagerTests.swift; sourceTree = ""; }; 0A9BF4E128A114940068D266 /* SentryViewHierarchyIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryViewHierarchyIntegration.m; sourceTree = ""; }; 0A9BF4E328A114B50068D266 /* SentryViewHierarchyIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryViewHierarchyIntegration.h; path = include/SentryViewHierarchyIntegration.h; sourceTree = ""; }; 0A9BF4E628A123270068D266 /* TestSentryViewHierarchy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryViewHierarchy.swift; sourceTree = ""; }; @@ -2400,6 +2402,7 @@ 7B85BD8D24C5C3A6000A4225 /* SentryFileManagerTestExtension.swift */, 7B4C817124D1BC2B0076ACE4 /* SentryFileManager+TestProperties.h */, 7B98D7EB25FB7C4900C5A389 /* SentryAppStateTests.swift */, + 0A94158128F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift */, 69BEE6F62620729E006DF9DF /* UrlSessionDelegateSpy.swift */, 7BD86ECA264A6DB5005439DB /* TestSysctl.swift */, 7B16FD012654F86B008177D3 /* SentrySysctlTests.swift */, @@ -3612,6 +3615,7 @@ 7B88F30224BC5C6D00ADF90A /* SentrySdkInfoTests.swift in Sources */, 7BC8523B2458849E005A70F0 /* SentryDataCategoryMapperTests.swift in Sources */, 63FE721220DA66EC00CDBAE8 /* SentryCrashMach_Tests.m in Sources */, + 0A94158228F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift in Sources */, 7B6D98E924C6D336005502FA /* SentrySdkInfo+Equality.m in Sources */, 7BDB03BF25136A7D00BAE198 /* TestSentryDispatchQueueWrapper.swift in Sources */, 7B6438A726A70DDB000D0F65 /* UIViewControllerSentryTests.swift in Sources */, diff --git a/Sources/Sentry/SentryAppStartTracker.m b/Sources/Sentry/SentryAppStartTracker.m index 433acb0c406..cc9927c0f69 100644 --- a/Sources/Sentry/SentryAppStartTracker.m +++ b/Sources/Sentry/SentryAppStartTracker.m @@ -109,6 +109,10 @@ - (void)start if (PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) { [self buildAppStartMeasurement]; } + +# if SENTRY_HAS_UIKIT + [self.appStateManager start]; +# endif } - (void)buildAppStartMeasurement @@ -259,6 +263,10 @@ - (void)stop [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + +# if SENTRY_HAS_UIKIT + [self.appStateManager stop]; +# endif } - (void)dealloc diff --git a/Sources/Sentry/SentryAppStateManager.m b/Sources/Sentry/SentryAppStateManager.m index b3537e44e5e..23889da8f66 100644 --- a/Sources/Sentry/SentryAppStateManager.m +++ b/Sources/Sentry/SentryAppStateManager.m @@ -5,10 +5,13 @@ #import #import #import +#import #import #import #if SENTRY_HAS_UIKIT +# import +# import # import #endif @@ -20,6 +23,8 @@ @property (nonatomic, strong) SentryFileManager *fileManager; @property (nonatomic, strong) id currentDate; @property (nonatomic, strong) SentrySysctl *sysctl; +@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; +@property (nonatomic) NSInteger startCount; @end @@ -30,6 +35,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options fileManager:(SentryFileManager *)fileManager currentDateProvider:(id)currentDateProvider sysctl:(SentrySysctl *)sysctl + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper { if (self = [super init]) { self.options = options; @@ -37,12 +43,138 @@ - (instancetype)initWithOptions:(SentryOptions *)options self.fileManager = fileManager; self.currentDate = currentDateProvider; self.sysctl = sysctl; + self.dispatchQueue = dispatchQueueWrapper; + self.startCount = 0; } return self; } #if SENTRY_HAS_UIKIT +- (void)start +{ + if (self.startCount == 0) { + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(didBecomeActive) + name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(didBecomeActive) + name:SentryHybridSdkDidBecomeActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(willResignActive) + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(willTerminate) + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName + object:nil]; + + [self storeCurrentAppState]; + } + + self.startCount += 1; +} + +- (void)stop +{ + if (self.startCount <= 0) { + return; + } + + self.startCount -= 1; + + if (self.startCount == 0) { + // Remove the observers with the most specific detail possible, see + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1413994-removeobserver + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryHybridSdkDidBecomeActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName + object:nil]; + + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName + object:nil]; + + [self deleteAppState]; + } +} + +- (void)dealloc +{ + // In dealloc it's safe to unsubscribe for all, see + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1413994-removeobserver + [NSNotificationCenter.defaultCenter removeObserver:self]; + [self deleteAppState]; +} + +/** + * It is called when an app is receiving events / it is in the foreground and when we receive a + * SentryHybridSdkDidBecomeActiveNotification. + * + * This also works when using SwiftUI or Scenes, as UIKit posts a didBecomeActiveNotification + * regardless of whether your app uses scenes, see + * https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive. + */ +- (void)didBecomeActive +{ + [self updateAppStateInBackground:^(SentryAppState *appState) { appState.isActive = YES; }]; +} + +/** + * The app is about to lose focus / going to the background. This is only called when an app was + * receiving events / was is in the foreground. + */ +- (void)willResignActive +{ + [self updateAppStateInBackground:^(SentryAppState *appState) { appState.isActive = NO; }]; +} + +- (void)willTerminate +{ + // The app is terminating so it is fine to do this on the main thread. + // Furthermore, so users can manually post UIApplicationWillTerminateNotification and then call + // exit(0), to avoid getting false OOM when using exit(0), see GH-1252. + [self updateAppState:^(SentryAppState *appState) { appState.wasTerminated = YES; }]; +} + +- (void)updateAppStateInBackground:(void (^)(SentryAppState *))block +{ + // We accept the tradeoff that the app state might not be 100% up to date over blocking the main + // thread. + [self.dispatchQueue dispatchAsyncWithBlock:^{ [self updateAppState:block]; }]; +} + +- (void)updateAppState:(void (^)(SentryAppState *))block +{ + @synchronized(self) { + SentryAppState *appState = [self.fileManager readAppState]; + if (nil != appState) { + block(appState); + [self.fileManager storeAppState:appState]; + } + } +} + - (SentryAppState *)buildCurrentAppState { // Is the current process being traced or not? If it is a debugger is attached. @@ -72,17 +204,6 @@ - (void)deleteAppState [self.fileManager deleteAppState]; } -- (void)updateAppState:(void (^)(SentryAppState *))block -{ - @synchronized(self) { - SentryAppState *appState = [self.fileManager readAppState]; - if (nil != appState) { - block(appState); - [self.fileManager storeAppState:appState]; - } - } -} - #endif @end diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 248a34cb066..17df37182b1 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -63,11 +63,12 @@ - (SentryAppStateManager *)appStateManager if (_appStateManager == nil) { SentryOptions *options = [[[SentrySDK currentHub] getClient] options]; _appStateManager = [[SentryAppStateManager alloc] - initWithOptions:options - crashWrapper:self.crashWrapper - fileManager:self.fileManager - currentDateProvider:[SentryDefaultCurrentDateProvider sharedInstance] - sysctl:[[SentrySysctl alloc] init]]; + initWithOptions:options + crashWrapper:self.crashWrapper + fileManager:self.fileManager + currentDateProvider:[SentryDefaultCurrentDateProvider sharedInstance] + sysctl:[[SentrySysctl alloc] init] + dispatchQueueWrapper:self.dispatchQueueWrapper]; } return _appStateManager; } diff --git a/Sources/Sentry/SentryOutOfMemoryTracker.m b/Sources/Sentry/SentryOutOfMemoryTracker.m index 15ca05e6c6f..65b9b372774 100644 --- a/Sources/Sentry/SentryOutOfMemoryTracker.m +++ b/Sources/Sentry/SentryOutOfMemoryTracker.m @@ -7,11 +7,9 @@ #import #import #import -#import #import #import #import -#import #import #import #import @@ -53,28 +51,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options - (void)start { #if SENTRY_HAS_UIKIT - [NSNotificationCenter.defaultCenter - addObserver:self - selector:@selector(didBecomeActive) - name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(didBecomeActive) - name:SentryHybridSdkDidBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter - addObserver:self - selector:@selector(willResignActive) - name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter - addObserver:self - selector:@selector(willTerminate) - name:SentryNSNotificationCenterWrapper.willTerminateNotificationName - object:nil]; + [self.appStateManager start]; [self.dispatchQueue dispatchAsyncWithBlock:^{ if ([self.outOfMemoryLogic isOOM]) { @@ -95,10 +72,7 @@ - (void)start // assume it's not an OOM when the releaseName changed between app starts. [SentrySDK captureCrashEvent:event]; } - - [self.appStateManager storeCurrentAppState]; }]; - #else SENTRY_LOG_INFO(@"NO UIKit -> SentryOutOfMemoryTracker will not track OOM."); return; @@ -108,74 +82,8 @@ - (void)start - (void)stop { #if SENTRY_HAS_UIKIT - // Remove the observers with the most specific detail possible, see - // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1413994-removeobserver - [NSNotificationCenter.defaultCenter - removeObserver:self - name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter - removeObserver:self - name:SentryHybridSdkDidBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter - removeObserver:self - name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter - removeObserver:self - name:SentryNSNotificationCenterWrapper.willTerminateNotificationName - object:nil]; - [self.appStateManager deleteAppState]; + [self.appStateManager stop]; #endif } -#if SENTRY_HAS_UIKIT -- (void)dealloc -{ - [self stop]; - // In dealloc it's safe to unsubscribe for all, see - // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1413994-removeobserver - [NSNotificationCenter.defaultCenter removeObserver:self]; -} - -/** - * It is called when an App. is receiving events / It is in the foreground and when we receive a - * SentryHybridSdkDidBecomeActiveNotification. - */ -- (void)didBecomeActive -{ - [self updateAppState:^(SentryAppState *appState) { appState.isActive = YES; }]; -} - -/** - * The app is about to lose focus / going to the background. This is only called when an app was - * receiving events / was is in the foreground. - */ -- (void)willResignActive -{ - [self updateAppState:^(SentryAppState *appState) { appState.isActive = NO; }]; -} - -- (void)willTerminate -{ - // The app is terminating so it is fine to do this on the main thread. - // Furthermore, so users can manually post UIApplicationWillTerminateNotification and then call - // exit(0), to avoid getting false OOM when using exit(0), see GH-1252. - [self.appStateManager - updateAppState:^(SentryAppState *appState) { appState.wasTerminated = YES; }]; -} - -- (void)updateAppState:(void (^)(SentryAppState *))block -{ - // We accept the tradeoff that the app state might not be 100% up to date over blocking the main - // thread. - [self.dispatchQueue dispatchAsyncWithBlock:^{ [self.appStateManager updateAppState:block]; }]; -} - -#endif - @end diff --git a/Sources/Sentry/SentrySessionTracker.m b/Sources/Sentry/SentrySessionTracker.m index 313badc2ecb..f881a110b44 100644 --- a/Sources/Sentry/SentrySessionTracker.m +++ b/Sources/Sentry/SentrySessionTracker.m @@ -135,6 +135,10 @@ - (void)endCachedSession * SentryHybridSdkDidBecomeActiveNotification. There is no guarantee that this method is called once * or twice. We need to ensure that we execute it only once. * + * This also works when using SwiftUI or Scenes, as UIKit posts a didBecomeActiveNotification + * regardless of whether your app uses scenes, see + * https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive. + * * We can't start the session in this method because we don't know if a background task or a hybrid * SDK initialized the SDK. Hybrid SDKs must only post this notification if they are running in the * foreground because the auto session tracking logic doesn't support background tasks. Posting the diff --git a/Sources/Sentry/include/SentryAppStateManager.h b/Sources/Sentry/include/SentryAppStateManager.h index bacc527457b..3b9e9ac4efc 100644 --- a/Sources/Sentry/include/SentryAppStateManager.h +++ b/Sources/Sentry/include/SentryAppStateManager.h @@ -1,7 +1,8 @@ #import "SentryCurrentDateProvider.h" #import "SentryDefines.h" -@class SentryOptions, SentryCrashWrapper, SentryAppState, SentryFileManager, SentrySysctl; +@class SentryOptions, SentryCrashWrapper, SentryAppState, SentryFileManager, SentrySysctl, + SentryDispatchQueueWrapper; NS_ASSUME_NONNULL_BEGIN @@ -12,10 +13,14 @@ SENTRY_NO_INIT crashWrapper:(SentryCrashWrapper *)crashWrapper fileManager:(SentryFileManager *)fileManager currentDateProvider:(id)currentDateProvider - sysctl:(SentrySysctl *)sysctl; + sysctl:(SentrySysctl *)sysctl + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; #if SENTRY_HAS_UIKIT +- (void)start; +- (void)stop; + /** * Builds the current app state. * diff --git a/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift b/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift new file mode 100644 index 00000000000..98b5f854aa6 --- /dev/null +++ b/Tests/SentryTests/Helper/SentryAppStateManagerTests.swift @@ -0,0 +1,115 @@ +import XCTest + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +class SentryAppStateManagerTests: XCTestCase { + private static let dsnAsString = TestConstants.dsnAsString(username: "SentryOutOfMemoryTrackerTests") + private static let dsn = TestConstants.dsn(username: "SentryOutOfMemoryTrackerTests") + + private class Fixture { + + let options: Options + let fileManager: SentryFileManager + let currentDate = TestCurrentDateProvider() + + init() { + options = Options() + options.dsn = SentryAppStateManagerTests.dsnAsString + options.releaseName = TestData.appState.releaseName + + fileManager = try! SentryFileManager(options: options, andCurrentDateProvider: currentDate) + } + + func getSut() -> SentryAppStateManager { + return SentryAppStateManager( + options: options, + crashWrapper: TestSentryCrashWrapper.sharedInstance(), + fileManager: fileManager, + currentDateProvider: currentDate, + sysctl: TestSysctl(), + dispatchQueueWrapper: TestSentryDispatchQueueWrapper() + ) + } + } + + private var fixture: Fixture! + private var sut: SentryAppStateManager! + + override func setUp() { + super.setUp() + + fixture = Fixture() + sut = fixture.getSut() + } + + override func tearDown() { + super.tearDown() + fixture.fileManager.deleteAppState() + } + + func testStartStoresAppState() { + XCTAssertNil(fixture.fileManager.readAppState()) + + sut.start() + XCTAssertNotNil(fixture.fileManager.readAppState()) + } + + func testStartOnlyRunsLogicWhenStartCountBecomesOne() { + XCTAssertNil(fixture.fileManager.readAppState()) + + sut.start() + XCTAssertNotNil(fixture.fileManager.readAppState()) + + fixture.fileManager.deleteAppState() + + sut.start() + XCTAssertNil(fixture.fileManager.readAppState()) + } + + func testStopDeletesAppState() { + XCTAssertNil(fixture.fileManager.readAppState()) + + sut.start() + XCTAssertNotNil(fixture.fileManager.readAppState()) + + sut.stop() + XCTAssertNil(fixture.fileManager.readAppState()) + } + + func testStopOnlyRunsLogicWhenStartCountBecomesZero() { + XCTAssertNil(fixture.fileManager.readAppState()) + + sut.start() + XCTAssertNotNil(fixture.fileManager.readAppState()) + + sut.start() + + sut.stop() + XCTAssertNotNil(fixture.fileManager.readAppState()) + + sut.stop() + XCTAssertNil(fixture.fileManager.readAppState()) + } + + func testStoreAndDeleteAppState() { + XCTAssertNil(fixture.fileManager.readAppState()) + + sut.storeCurrentAppState() + XCTAssertNotNil(fixture.fileManager.readAppState()) + + sut.deleteAppState() + XCTAssertNil(fixture.fileManager.readAppState()) + } + + func testUpdateAppState() { + sut.storeCurrentAppState() + + XCTAssertEqual(fixture.fileManager.readAppState()!.wasTerminated, false) + + sut.updateAppState { state in + state.wasTerminated = true + } + + XCTAssertEqual(fixture.fileManager.readAppState()!.wasTerminated, true) + } +} +#endif diff --git a/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryLogicTests.swift b/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryLogicTests.swift index 1035741cacd..f34f3cdb6d4 100644 --- a/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryLogicTests.swift +++ b/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryLogicTests.swift @@ -28,7 +28,7 @@ class SentryOutOfMemoryLogicTests: XCTestCase { } func getSut() -> SentryOutOfMemoryLogic { - let appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl) + let appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl, dispatchQueueWrapper: self.dispatchQueue) return SentryOutOfMemoryLogic(options: options, crashAdapter: crashWrapper, appStateManager: appStateManager) } } diff --git a/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryTrackerTests.swift b/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryTrackerTests.swift index 7454e29ef05..fa72aeacfaf 100644 --- a/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryTrackerTests.swift +++ b/Tests/SentryTests/Integrations/OutOfMemory/SentryOutOfMemoryTrackerTests.swift @@ -36,7 +36,7 @@ class SentryOutOfMemoryTrackerTests: NotificationCenterTestCase { } func getSut(fileManager: SentryFileManager) -> SentryOutOfMemoryTracker { - let appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl) + let appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl, dispatchQueueWrapper: self.dispatchQueue) let logic = SentryOutOfMemoryLogic(options: options, crashAdapter: crashWrapper, appStateManager: appStateManager) return SentryOutOfMemoryTracker(options: options, outOfMemoryLogic: logic, appStateManager: appStateManager, dispatchQueueWrapper: dispatchQueue, fileManager: fileManager) } @@ -62,6 +62,8 @@ class SentryOutOfMemoryTrackerTests: NotificationCenterTestCase { } func testStart_StoresAppState() { + XCTAssertNil(fixture.fileManager.readAppState()) + sut.start() let actual = fixture.fileManager.readAppState() diff --git a/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift index 985f00f09dc..5616a5f7e1c 100644 --- a/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift @@ -14,6 +14,7 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase { let fileManager: SentryFileManager let crashWrapper = TestSentryCrashWrapper.sharedInstance() let appStateManager: SentryAppStateManager + let dispatchQueue = TestSentryDispatchQueueWrapper() let appStartDuration: TimeInterval = 0.4 var runtimeInitTimestamp: Date @@ -26,7 +27,7 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase { fileManager = try! SentryFileManager(options: options, andCurrentDateProvider: currentDate) - appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl) + appStateManager = SentryAppStateManager(options: options, crashWrapper: crashWrapper, fileManager: fileManager, currentDateProvider: currentDate, sysctl: sysctl, dispatchQueueWrapper: dispatchQueue) runtimeInitTimestamp = currentDate.date().addingTimeInterval(0.2) didFinishLaunchingTimestamp = currentDate.date().addingTimeInterval(0.3) @@ -55,7 +56,7 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase { fixture.fileManager.deleteAllFolders() clearTestState() } - + func testFirstStart_IsColdStart() { startApp()