diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 087e9969e25..e3931838eeb 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -291,6 +291,10 @@ 7B14089824878F950035403D /* SentryCrashStackEntryMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B14089724878F950035403D /* SentryCrashStackEntryMapper.m */; }; 7B14089A248791660035403D /* SentryCrashStackEntryMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B140899248791660035403D /* SentryCrashStackEntryMapperTests.swift */; }; 7B16FD022654F86B008177D3 /* SentrySysctlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B16FD012654F86B008177D3 /* SentrySysctlTests.swift */; }; + 7B18DE4028D9F748004845C6 /* SentryNSNotificationCenterWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B18DE3F28D9F748004845C6 /* SentryNSNotificationCenterWrapper.h */; }; + 7B18DE4228D9F794004845C6 /* SentryNSNotificationCenterWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B18DE4128D9F794004845C6 /* SentryNSNotificationCenterWrapper.m */; }; + 7B18DE4428D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */; }; + 7B18DE4A28DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B18DE4928DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift */; }; 7B26BBFB24C0A66D00A79CCC /* SentrySdkInfoNilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B26BBFA24C0A66D00A79CCC /* SentrySdkInfoNilTests.m */; }; 7B2A70D827D5F080008B0D15 /* SentryANRTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2A70D727D5F07F008B0D15 /* SentryANRTrackerTests.swift */; }; 7B2A70DB27D607CF008B0D15 /* SentryThreadWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B2A70DA27D607CF008B0D15 /* SentryThreadWrapper.h */; }; @@ -998,6 +1002,10 @@ 7B14089724878F950035403D /* SentryCrashStackEntryMapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashStackEntryMapper.m; sourceTree = ""; }; 7B140899248791660035403D /* SentryCrashStackEntryMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashStackEntryMapperTests.swift; sourceTree = ""; }; 7B16FD012654F86B008177D3 /* SentrySysctlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySysctlTests.swift; sourceTree = ""; }; + 7B18DE3F28D9F748004845C6 /* SentryNSNotificationCenterWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSNotificationCenterWrapper.h; path = include/SentryNSNotificationCenterWrapper.h; sourceTree = ""; }; + 7B18DE4128D9F794004845C6 /* SentryNSNotificationCenterWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSNotificationCenterWrapper.m; sourceTree = ""; }; + 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNSNotificationCenterWrapper.swift; sourceTree = ""; }; + 7B18DE4928DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSNotificationCenterWrapperTests.swift; sourceTree = ""; }; 7B26BBFA24C0A66D00A79CCC /* SentrySdkInfoNilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySdkInfoNilTests.m; sourceTree = ""; }; 7B2A70D727D5F07F008B0D15 /* SentryANRTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerTests.swift; sourceTree = ""; }; 7B2A70DA27D607CF008B0D15 /* SentryThreadWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryThreadWrapper.h; path = include/SentryThreadWrapper.h; sourceTree = ""; }; @@ -1790,6 +1798,8 @@ D84793242788737D00BE8E99 /* SentryByteCountFormatter.m */, 7B2A70DA27D607CF008B0D15 /* SentryThreadWrapper.h */, 7B2A70DC27D6083D008B0D15 /* SentryThreadWrapper.m */, + 7B18DE3F28D9F748004845C6 /* SentryNSNotificationCenterWrapper.h */, + 7B18DE4128D9F794004845C6 /* SentryNSNotificationCenterWrapper.m */, ); name = Helper; sourceTree = ""; @@ -2377,6 +2387,8 @@ 7BF9EF872722D13000B5BBEF /* SentryTestObjCRuntimeWrapper.m */, D85D3BE9278DF63D001B2889 /* SentryByteCountFormatterTests.swift */, 7B2A70DE27D60904008B0D15 /* SentryTestThreadWrapper.swift */, + 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */, + 7B18DE4928DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift */, ); path = Helper; sourceTree = ""; @@ -2911,6 +2923,7 @@ 7B14089624878F090035403D /* SentryCrashStackEntryMapper.h in Headers */, 63FE714920DA4C1100CDBAE8 /* SentryCrashStackCursor_Backtrace.h in Headers */, 7BFC169B2524995700FF6266 /* SentryMessage.h in Headers */, + 7B18DE4028D9F748004845C6 /* SentryNSNotificationCenterWrapper.h in Headers */, 03F84D1E27DD414C008FE43F /* SentryBacktrace.hpp in Headers */, 63AA76991EB9C1C200D153DE /* SentryDefines.h in Headers */, 0A4EDEA928D3461B00FA67CB /* SentryPerformanceTracker+Private.h in Headers */, @@ -3259,6 +3272,7 @@ 63FE718120DA4C1100CDBAE8 /* SentryCrashDoctor.m in Sources */, 63FE713720DA4C1100CDBAE8 /* SentryCrashCPU_x86_64.c in Sources */, 8ECC674725C23A20000E2BF6 /* SentrySpanContext.m in Sources */, + 7B18DE4228D9F794004845C6 /* SentryNSNotificationCenterWrapper.m in Sources */, 639FCFA91EBC80CC00778193 /* SentryFrame.m in Sources */, 8E564AEA267AF22600FE117D /* SentryNetworkTracker.m in Sources */, 15360CED2433A15500112302 /* SentryInstallation.m in Sources */, @@ -3454,6 +3468,7 @@ 7BD4BD4D27EB31820071F4FF /* SentryClientReportTests.swift in Sources */, 7B98D7EC25FB7C4900C5A389 /* SentryAppStateTests.swift in Sources */, 8EE017A126704CD500470616 /* SentryUIViewControllerPerformanceTrackerTests.swift in Sources */, + 7B18DE4428D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift in Sources */, 7B5B94352657AD21002E474B /* SentryFramesTrackingIntegrationTests.swift in Sources */, 7BAF3DC8243DB90E008A5414 /* TestTransport.swift in Sources */, 7B0A54562523178700A71716 /* SentryScopeSwiftTests.swift in Sources */, @@ -3520,6 +3535,7 @@ 8E4A038525F76A7600000D77 /* Logger.swift in Sources */, D8F6A24E288553A800320515 /* SentryPredicateDescriptorTests.swift in Sources */, 035E73CE27D5790A005EEB11 /* SentryThreadMetadataCacheTests.mm in Sources */, + 7B18DE4A28DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift in Sources */, 7BEFB044270B0F630025F808 /* SentryTracerObjCTests.m in Sources */, 7B0002322477F0520035FEF1 /* SentrySessionTests.m in Sources */, 7BC6EC08255C36DE0059822A /* SentryStacktraceTests.swift in Sources */, diff --git a/Sources/Sentry/SentryAutoSessionTrackingIntegration.m b/Sources/Sentry/SentryAutoSessionTrackingIntegration.m index bbba87feac1..9b93327e8e1 100644 --- a/Sources/Sentry/SentryAutoSessionTrackingIntegration.m +++ b/Sources/Sentry/SentryAutoSessionTrackingIntegration.m @@ -1,5 +1,6 @@ #import "SentryAutoSessionTrackingIntegration.h" #import "SentryDefaultCurrentDateProvider.h" +#import "SentryDependencyContainer.h" #import "SentryLog.h" #import "SentryOptions.h" #import "SentrySDK.h" @@ -24,7 +25,8 @@ - (BOOL)installWithOptions:(SentryOptions *)options SentrySessionTracker *tracker = [[SentrySessionTracker alloc] initWithOptions:options - currentDateProvider:[SentryDefaultCurrentDateProvider sharedInstance]]; + currentDateProvider:[SentryDefaultCurrentDateProvider sharedInstance] + notificationCenter:[SentryDependencyContainer sharedInstance].notificationCenterWrapper]; [tracker start]; self.tracker = tracker; diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index c6aff3dd3b7..d81ec2040b2 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -107,6 +108,16 @@ - (SentryDispatchQueueWrapper *)dispatchQueueWrapper } } +- (SentryNSNotificationCenterWrapper *)notificationCenterWrapper +{ + @synchronized(sentryDependencyContainerLock) { + if (_notificationCenterWrapper == nil) { + _notificationCenterWrapper = [[SentryNSNotificationCenterWrapper alloc] init]; + } + return _notificationCenterWrapper; + } +} + - (id)random { if (_random == nil) { diff --git a/Sources/Sentry/SentryNSNotificationCenterWrapper.m b/Sources/Sentry/SentryNSNotificationCenterWrapper.m new file mode 100644 index 00000000000..d658f727c98 --- /dev/null +++ b/Sources/Sentry/SentryNSNotificationCenterWrapper.m @@ -0,0 +1,66 @@ +#import "SentryNSNotificationCenterWrapper.h" + +#if SENTRY_HAS_UIKIT +# import +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST +# import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@implementation SentryNSNotificationCenterWrapper + +#if SENTRY_HAS_UIKIT ++ (NSNotificationName)didBecomeActiveNotificationName +{ + return UIApplicationDidBecomeActiveNotification; +} + ++ (NSNotificationName)willResignActiveNotificationName +{ + return UIApplicationWillResignActiveNotification; +} + ++ (NSNotificationName)willTerminateNotificationName +{ + return UIApplicationWillTerminateNotification; +} + +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST ++ (NSNotificationName)didBecomeActiveNotificationName +{ + return NSApplicationDidBecomeActiveNotification; +} + ++ (NSNotificationName)willResignActiveNotificationName +{ + return NSApplicationWillResignActiveNotification; +} + ++ (NSNotificationName)willTerminateNotificationName +{ + return NSApplicationWillTerminateNotification; +} +#endif + +- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName +{ + [NSNotificationCenter.defaultCenter addObserver:observer + selector:aSelector + name:aName + object:nil]; +} + +- (void)removeObserver:(id)observer name:(NSNotificationName)aName +{ + [NSNotificationCenter.defaultCenter removeObserver:observer name:aName object:nil]; +} + +- (void)removeObserver:(id)observer +{ + [NSNotificationCenter.defaultCenter removeObserver:observer]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryOutOfMemoryTracker.m b/Sources/Sentry/SentryOutOfMemoryTracker.m index fe08a3bad34..2b30ad7a93d 100644 --- a/Sources/Sentry/SentryOutOfMemoryTracker.m +++ b/Sources/Sentry/SentryOutOfMemoryTracker.m @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -52,25 +53,28 @@ - (instancetype)initWithOptions:(SentryOptions *)options - (void)start { #if SENTRY_HAS_UIKIT - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(didBecomeActive) - name:UIApplicationDidBecomeActiveNotification - object:nil]; + [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:UIApplicationWillResignActiveNotification - object:nil]; + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(willResignActive) + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName + object:nil]; - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(willTerminate) - name:UIApplicationWillTerminateNotification - object:nil]; + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(willTerminate) + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName + object:nil]; [self.dispatchQueue dispatchAsyncWithBlock:^{ if ([self.outOfMemoryLogic isOOM]) { @@ -106,22 +110,25 @@ - (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:UIApplicationDidBecomeActiveNotification - object:nil]; + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName + object:nil]; [NSNotificationCenter.defaultCenter removeObserver:self name:SentryHybridSdkDidBecomeActiveNotificationName object:nil]; - [NSNotificationCenter.defaultCenter removeObserver:self - name:UIApplicationWillResignActiveNotification - object:nil]; + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName + object:nil]; - [NSNotificationCenter.defaultCenter removeObserver:self - name:UIApplicationWillTerminateNotification - object:nil]; + [NSNotificationCenter.defaultCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName + object:nil]; [self.appStateManager removeCurrentAppState]; #endif } diff --git a/Sources/Sentry/SentrySessionTracker.m b/Sources/Sentry/SentrySessionTracker.m index 7d5f399e4b2..313badc2ecb 100644 --- a/Sources/Sentry/SentrySessionTracker.m +++ b/Sources/Sentry/SentrySessionTracker.m @@ -5,6 +5,7 @@ #import "SentryHub+Private.h" #import "SentryInternalNotificationNames.h" #import "SentryLog.h" +#import "SentryNSNotificationCenterWrapper.h" #import "SentrySDK+Private.h" #if SENTRY_HAS_UIKIT @@ -21,16 +22,7 @@ @property (atomic, strong) NSDate *lastInForeground; @property (nonatomic, assign) BOOL wasDidBecomeActiveCalled; @property (nonatomic, assign) BOOL subscribedToNotifications; - -#if SENTRY_HAS_UIKIT -@property (nonatomic, copy) NSNotificationName didBecomeActiveNotificationName; -@property (nonatomic, copy) NSNotificationName willResignActiveNotificationName; -@property (nonatomic, copy) NSNotificationName willTerminateNotificationName; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST -@property (nonatomic, copy) NSNotificationName didBecomeActiveNotificationName; -@property (nonatomic, copy) NSNotificationName willResignActiveNotificationName; -@property (nonatomic, copy) NSNotificationName willTerminateNotificationName; -#endif +@property (nonatomic, strong) SentryNSNotificationCenterWrapper *notificationCenter; @end @@ -38,21 +30,13 @@ @implementation SentrySessionTracker - (instancetype)initWithOptions:(SentryOptions *)options currentDateProvider:(id)currentDateProvider + notificationCenter:(SentryNSNotificationCenterWrapper *)notificationCenter; { if (self = [super init]) { self.options = options; self.currentDateProvider = currentDateProvider; self.wasDidBecomeActiveCalled = NO; - -#if SENTRY_HAS_UIKIT - self.didBecomeActiveNotificationName = UIApplicationDidBecomeActiveNotification; - self.willResignActiveNotificationName = UIApplicationWillResignActiveNotification; - self.willTerminateNotificationName = UIApplicationWillTerminateNotification; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - self.didBecomeActiveNotificationName = NSApplicationDidBecomeActiveNotification; - self.willResignActiveNotificationName = NSApplicationWillResignActiveNotification; - self.willTerminateNotificationName = NSApplicationWillTerminateNotification; -#endif + self.notificationCenter = notificationCenter; } return self; } @@ -79,25 +63,24 @@ - (void)start // ending the cached session. [self endCachedSession]; - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(didBecomeActive) - name:self.didBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(didBecomeActive) - name:SentryHybridSdkDidBecomeActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(willResignActive) - name:self.willResignActiveNotificationName - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(willTerminate) - name:self.willTerminateNotificationName - object:nil]; + [self.notificationCenter + addObserver:self + selector:@selector(didBecomeActive) + name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName]; + + [self.notificationCenter addObserver:self + selector:@selector(didBecomeActive) + name:SentryHybridSdkDidBecomeActiveNotificationName]; + + [self.notificationCenter + addObserver:self + selector:@selector(willResignActive) + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName]; + + [self.notificationCenter + addObserver:self + selector:@selector(willTerminate) + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName]; #else SENTRY_LOG_DEBUG(@"NO UIKit -> SentrySessionTracker will not track sessions automatically."); #endif @@ -108,19 +91,17 @@ - (void)stop #if SENTRY_HAS_UIKIT || TARGET_OS_OSX || TARGET_OS_MACCATALYST // Remove the observers with the most specific detail possible, see // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1413994-removeobserver - [NSNotificationCenter.defaultCenter removeObserver:self - name:self.didBecomeActiveNotificationName - object:nil]; - [NSNotificationCenter.defaultCenter + [self.notificationCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName]; + [self.notificationCenter removeObserver:self + name:SentryHybridSdkDidBecomeActiveNotificationName]; + [self.notificationCenter + removeObserver:self + name:SentryNSNotificationCenterWrapper.willResignActiveNotificationName]; + [self.notificationCenter removeObserver:self - name:SentryHybridSdkDidBecomeActiveNotificationName - object:nil]; - [NSNotificationCenter.defaultCenter removeObserver:self - name:self.willResignActiveNotificationName - object:nil]; - [NSNotificationCenter.defaultCenter removeObserver:self - name:self.willTerminateNotificationName - object:nil]; + name:SentryNSNotificationCenterWrapper.willTerminateNotificationName]; #endif } @@ -129,7 +110,7 @@ - (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]; + [self.notificationCenter removeObserver:self]; } /** diff --git a/Sources/Sentry/include/SentryDependencyContainer.h b/Sources/Sentry/include/SentryDependencyContainer.h index a3f3852de52..aa8e3bd9507 100644 --- a/Sources/Sentry/include/SentryDependencyContainer.h +++ b/Sources/Sentry/include/SentryDependencyContainer.h @@ -4,7 +4,8 @@ #import @class SentryAppStateManager, SentryCrashWrapper, SentryThreadWrapper, SentrySwizzleWrapper, - SentryDispatchQueueWrapper, SentryDebugImageProvider, SentryANRTracker; + SentryDispatchQueueWrapper, SentryDebugImageProvider, SentryANRTracker, + SentryNSNotificationCenterWrapper; #if SENTRY_HAS_UIKIT @class SentryScreenshot, SentryUIApplication, SentryViewHierarchy; @@ -29,6 +30,7 @@ SENTRY_NO_INIT @property (nonatomic, strong) id random; @property (nonatomic, strong) SentrySwizzleWrapper *swizzleWrapper; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper; +@property (nonatomic, strong) SentryNSNotificationCenterWrapper *notificationCenterWrapper; @property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider; @property (nonatomic, strong) SentryANRTracker *anrTracker; diff --git a/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h new file mode 100644 index 00000000000..2c6dd57ae86 --- /dev/null +++ b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h @@ -0,0 +1,29 @@ +#import "SentryDefines.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A wrapper around NSNotificationCenter functions for testability. + * + * Testing with NSNotificationCenter in CI leads to flaky tests for some classes. Therefore, we can + * use a wrapper around NSNotificationCenter to not depend on it. Instead, we call the methods + * NSNotificationCenter would call with Dynamic and ensure that sut properly subscribes to + * NSNotificationCenter. + */ +@interface SentryNSNotificationCenterWrapper : NSObject + +#if SENTRY_HAS_UIKIT || TARGET_OS_OSX || TARGET_OS_MACCATALYST +@property (nonatomic, readonly, copy, class) NSNotificationName didBecomeActiveNotificationName; +@property (nonatomic, readonly, copy, class) NSNotificationName willResignActiveNotificationName; +@property (nonatomic, readonly, copy, class) NSNotificationName willTerminateNotificationName; +#endif + +- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName; + +- (void)removeObserver:(id)observer name:(NSNotificationName)aName; + +- (void)removeObserver:(id)observer; + +NS_ASSUME_NONNULL_END + +@end diff --git a/Sources/Sentry/include/SentrySessionTracker.h b/Sources/Sentry/include/SentrySessionTracker.h index 08478ceb250..e6fb84e890d 100644 --- a/Sources/Sentry/include/SentrySessionTracker.h +++ b/Sources/Sentry/include/SentrySessionTracker.h @@ -2,7 +2,7 @@ #import "SentryDefines.h" #import -@class SentryEvent, SentryOptions, SentryCurrentDateProvider; +@class SentryEvent, SentryOptions, SentryCurrentDateProvider, SentryNSNotificationCenterWrapper; /** * Tracks sessions for release health. For more info see: @@ -13,7 +13,9 @@ NS_SWIFT_NAME(SessionTracker) SENTRY_NO_INIT - (instancetype)initWithOptions:(SentryOptions *)options - currentDateProvider:(id)currentDateProvider; + currentDateProvider:(id)currentDateProvider + notificationCenter:(SentryNSNotificationCenterWrapper *)notificationCenter; + - (void)start; - (void)stop; @end diff --git a/Tests/SentryTests/Helper/SentryNSNotificationCenterWrapperTests.swift b/Tests/SentryTests/Helper/SentryNSNotificationCenterWrapperTests.swift new file mode 100644 index 00000000000..7a536934730 --- /dev/null +++ b/Tests/SentryTests/Helper/SentryNSNotificationCenterWrapperTests.swift @@ -0,0 +1,66 @@ +import XCTest + +class SentryNSNotificationCenterWrapperTests: XCTestCase { + + private var sut: SentryNSNotificationCenterWrapper! + + private var didBecomeActiveExpectation: XCTestExpectation! + private var willResignActiveExpectation: XCTestExpectation! + + private let didBecomeActiveNotification = SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName + private let willResignActiveNotification = SentryNSNotificationCenterWrapper.willResignActiveNotificationName + + override func setUp() { + super.setUp() + + sut = SentryNSNotificationCenterWrapper() + + didBecomeActiveExpectation = expectation(description: "didBecomeActive") + willResignActiveExpectation = expectation(description: "willResignActive") + willResignActiveExpectation.isInverted = true + } + + override func tearDown() { + sut.removeObserver(self) + + super.tearDown() + } + + func testAddObserver() { + sut.addObserver(self, selector: #selector(didBecomeActive), name: didBecomeActiveNotification) + + NotificationCenter.default.post(Notification(name: didBecomeActiveNotification)) + + wait(for: [didBecomeActiveExpectation, willResignActiveExpectation], timeout: 0.5) + } + + func testRemoveSpecificObserver() { + sut.addObserver(self, selector: #selector(didBecomeActive), name: didBecomeActiveNotification) + sut.addObserver(self, selector: #selector(willResignActive), name: willResignActiveNotification) + + sut.removeObserver(self, name: willResignActiveNotification) + NotificationCenter.default.post(Notification(name: didBecomeActiveNotification)) + + wait(for: [didBecomeActiveExpectation, willResignActiveExpectation], timeout: 0.5) + } + + func testRemoveObserver() { + didBecomeActiveExpectation.isInverted = true + + sut.addObserver(self, selector: #selector(didBecomeActive), name: didBecomeActiveNotification) + sut.addObserver(self, selector: #selector(willResignActive), name: willResignActiveNotification) + + sut.removeObserver(self) + NotificationCenter.default.post(Notification(name: didBecomeActiveNotification)) + + wait(for: [didBecomeActiveExpectation, willResignActiveExpectation], timeout: 0.5) + } + + func didBecomeActive() { + didBecomeActiveExpectation.fulfill() + } + + func willResignActive() { + willResignActiveExpectation.fulfill() + } +} diff --git a/Tests/SentryTests/Helper/TestNSNotificationCenterWrapper.swift b/Tests/SentryTests/Helper/TestNSNotificationCenterWrapper.swift new file mode 100644 index 00000000000..f2f883af29a --- /dev/null +++ b/Tests/SentryTests/Helper/TestNSNotificationCenterWrapper.swift @@ -0,0 +1,19 @@ +import Foundation + +class TestNSNotificationCenterWrapper: SentryNSNotificationCenterWrapper { + + var addObserverInvocations = Invocations<(observer: Any, selector: Selector, name: NSNotification.Name)>() + override func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name) { + addObserverInvocations.record((observer, aSelector, aName)) + } + + var addObserverWithNotificationInvocations = Invocations<(observer: Any, name: NSNotification.Name)>() + override func removeObserver(_ observer: Any, name aName: NSNotification.Name) { + addObserverWithNotificationInvocations.record((observer, aName)) + } + + var removeObserverInvocations = Invocations() + override func removeObserver(_ observer: Any) { + removeObserverInvocations.record(observer) + } +} diff --git a/Tests/SentryTests/Integrations/NotificationCenterTestCase.swift b/Tests/SentryTests/Integrations/NotificationCenterTestCase.swift index f850f2e80f1..0c23d29d0ff 100644 --- a/Tests/SentryTests/Integrations/NotificationCenterTestCase.swift +++ b/Tests/SentryTests/Integrations/NotificationCenterTestCase.swift @@ -15,10 +15,10 @@ class NotificationCenterTestCase: XCTestCase { let willTerminateNotification = UIApplication.willTerminateNotification let didFinishLaunchingNotification = UIApplication.didFinishLaunchingNotification #elseif os(macOS) - static let didBecomeActiveNotification = NSApplication.didBecomeActiveNotification - static let willResignActiveNotification = NSApplication.willResignActiveNotification - static let willTerminateNotification = NSApplication.willTerminateNotification - static let didFinishLaunchingNotification = NSApplication.didFinishLaunchingNotification + let didBecomeActiveNotification = NSApplication.didBecomeActiveNotification + let willResignActiveNotification = NSApplication.willResignActiveNotification + let willTerminateNotification = NSApplication.willTerminateNotification + let didFinishLaunchingNotification = NSApplication.didFinishLaunchingNotification #endif func goToForeground() { diff --git a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift index 38003e1f105..67997459f24 100644 --- a/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Session/SentrySessionTrackerTests.swift @@ -1,7 +1,7 @@ @testable import Sentry import XCTest -class SentrySessionTrackerTests: NotificationCenterTestCase { +class SentrySessionTrackerTests: XCTestCase { private static let dsnAsString = TestConstants.dsnAsString(username: "SentrySessionTrackerTests") private static let dsn = TestConstants.dsn(username: "SentrySessionTrackerTests") @@ -12,6 +12,8 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { let currentDateProvider = TestCurrentDateProvider() let client: TestClient! let sentryCrash: TestSentryCrashWrapper + + let notificationCenter = TestNSNotificationCenterWrapper() init() { options = Options() @@ -26,7 +28,7 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { } func getSut() -> SessionTracker { - return SessionTracker(options: options, currentDateProvider: currentDateProvider) + return SessionTracker(options: options, currentDateProvider: currentDateProvider, notificationCenter: notificationCenter) } func setNewHubToSDK() { @@ -246,8 +248,6 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { // Background task is launched advanceTime(bySeconds: 30) - didEnterBackground() - advanceTime(bySeconds: 9) // user opens app goToForeground() @@ -262,9 +262,7 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { goToBackground() // Background task is launched - advanceTime(bySeconds: 1) - didEnterBackground() - advanceTime(bySeconds: 1) + advanceTime(bySeconds: 2) // user opens app goToForeground() @@ -344,23 +342,57 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { assertSessionsSent(count: 2) } + func testStart_AddsObservers() { + sut.start() + + let invocations = fixture.notificationCenter.addObserverInvocations + let notificationNames = invocations.invocations.map { $0.name } + + assertNotificationNames(notificationNames) + } + + func testStop_RemovesObservers() { + sut.stop() + + let invocations = fixture.notificationCenter.addObserverWithNotificationInvocations + let notificationNames = invocations.invocations.map { $0.name } + + assertNotificationNames(notificationNames) + } + private func advanceTime(bySeconds: TimeInterval) { fixture.currentDateProvider.setDate(date: fixture.currentDateProvider.date().addingTimeInterval(bySeconds)) } + private func goToForeground() { + Dynamic(sut).didBecomeActive() + } + + private func goToBackground() { + willResignActive() + } + + private func willResignActive() { + Dynamic(sut).willResignActive() + } + + private func hybridSdkDidBecomeActive() { + Dynamic(sut).didBecomeActive() + } + private func goToBackground(forSeconds: TimeInterval) { goToBackground() advanceTime(bySeconds: forSeconds) goToForeground() } - internal override func terminateApp() { - super.terminateApp() - sut.stop() + private func willTerminate() { + Dynamic(sut).willTerminate() } - private func resumeAppInBackground() { - didEnterBackground() + private func terminateApp() { + willTerminate() + sut.stop() } private func launchBackgroundTaskAppNotRunning() { @@ -369,7 +401,6 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { sut = fixture.getSut() sut.start() - didEnterBackground() } private func captureError() { @@ -526,4 +557,15 @@ class SentrySessionTrackerTests: NotificationCenterTestCase { XCTFail("No session sent with event.") } } + + private func assertNotificationNames(_ notificationNames: [NSNotification.Name]) { + XCTAssertEqual(4, notificationNames.count) + + XCTAssertEqual([ + SentryNSNotificationCenterWrapper.didBecomeActiveNotificationName, + NSNotification.Name(rawValue: SentryHybridSdkDidBecomeActiveNotificationName), + SentryNSNotificationCenterWrapper.willResignActiveNotificationName, + SentryNSNotificationCenterWrapper.willTerminateNotificationName + ], notificationNames) + } } diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 0edee8f0eb7..eed14a88a96 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -88,6 +88,7 @@ #import "SentryInAppLogic.h" #import "SentryInitializeForGettingSubclassesNotCalled.h" #import "SentryInstallation.h" +#import "SentryInternalNotificationNames.h" #import "SentryLevelMapper.h" #import "SentryLog+TestInit.h" #import "SentryLog.h" @@ -98,6 +99,7 @@ #import "SentryMigrateSessionInit.h" #import "SentryNSDataTracker.h" #import "SentryNSError.h" +#import "SentryNSNotificationCenterWrapper.h" #import "SentryNSURLRequest.h" #import "SentryNSURLRequestBuilder.h" #import "SentryNSURLSessionTaskSearch.h"