diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f954b2cbe..3a564e05589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Record changes to network connectivity in breadcrumbs (#3232) + ## 8.12.0 ### Fixes diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 9674588648d..94d432adcd7 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1595,6 +1595,7 @@ 84AF45A529A7FFA500FBB177 /* SentryProfiledTracerConcurrency.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfiledTracerConcurrency.mm; sourceTree = ""; }; 84B7FA3B29B2866200AD93B1 /* SentryTestUtils-ObjC-BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTestUtils-ObjC-BridgingHeader.h"; sourceTree = ""; }; 84B7FA4729B2995A00AD93B1 /* DeploymentTargets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DeploymentTargets.xcconfig; sourceTree = ""; }; + 84C404B02ABA9F9C007F69C5 /* SentryReachability+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryReachability+Private.h"; sourceTree = ""; }; 84C47B2B2A09239100DAEB8A /* .codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .codecov.yml; sourceTree = ""; }; 84E4F5692914F020004C7358 /* Brewfile */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = text; path = Brewfile; sourceTree = ""; tabWidth = 2; }; 84F993C32A62A74000EC0190 /* SentryCurrentDateProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCurrentDateProvider.m; sourceTree = ""; }; @@ -1888,6 +1889,7 @@ 84AC61D129F7541E009EEF61 /* SentryDispatchSourceWrapper.m */, 0AAE202028ED9BCC00D0CD80 /* SentryReachability.h */, 0AAE201D28ED9B9400D0CD80 /* SentryReachability.m */, + 84C404B02ABA9F9C007F69C5 /* SentryReachability+Private.h */, ); name = Networking; sourceTree = ""; diff --git a/Sources/Sentry/SentryBreadcrumbTracker.m b/Sources/Sentry/SentryBreadcrumbTracker.m index 8efbdaa9aee..0f189e99468 100644 --- a/Sources/Sentry/SentryBreadcrumbTracker.m +++ b/Sources/Sentry/SentryBreadcrumbTracker.m @@ -6,6 +6,7 @@ #import "SentryDependencyContainer.h" #import "SentryHub.h" #import "SentryLog.h" +#import "SentryReachability.h" #import "SentryScope.h" #import "SentrySwift.h" #import "SentrySwizzle.h" @@ -15,7 +16,7 @@ # import #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST # import -#endif +#endif // !TARGET_OS_WATCH NS_ASSUME_NONNULL_BEGIN @@ -24,6 +25,9 @@ @interface SentryBreadcrumbTracker () +#if !TARGET_OS_WATCH + +#endif // !TARGET_OS_WATCH @property (nonatomic, weak) id delegate; @@ -31,16 +35,21 @@ @implementation SentryBreadcrumbTracker -- (instancetype)init +#if !TARGET_OS_WATCH +- (void)dealloc { - return [super init]; + [SentryDependencyContainer.sharedInstance.reachability removeObserver:self]; } +#endif // !TARGET_OS_WATCH - (void)startWithDelegate:(id)delegate { _delegate = delegate; [self addEnabledCrumb]; [self trackApplicationUIKitNotifications]; +#if !TARGET_OS_WATCH + [self trackNetworkConnectivityChanges]; +#endif // !TARGET_OS_WATCH } - (void)startSwizzle @@ -56,7 +65,7 @@ - (void)stop #if SENTRY_HAS_UIKIT [SentryDependencyContainer.sharedInstance.swizzleWrapper removeSwizzleSendActionForKey:SentryBreadcrumbTrackerSwizzleSendAction]; -#endif +#endif // SENTRY_HAS_UIKIT _delegate = nil; } @@ -70,10 +79,10 @@ - (void)trackApplicationUIKitNotifications // Will resign Active notification is the nearest one to // UIApplicationDidEnterBackgroundNotification NSNotificationName backgroundNotificationName = NSApplicationWillResignActiveNotification; -#else +#else // TARGET_OS_WATCH SENTRY_LOG_DEBUG(@"NO UIKit, OSX and Catalyst -> [SentryBreadcrumbTracker " @"trackApplicationUIKitNotifications] does nothing."); -#endif +#endif // !TARGET_OS_WATCH // not available for macOS #if SENTRY_HAS_UIKIT @@ -90,9 +99,9 @@ - (void)trackApplicationUIKitNotifications crumb.message = @"Low memory"; [self.delegate addBreadcrumb:crumb]; }]; -#endif +#endif // SENTRY_HAS_UIKIT -#if SENTRY_HAS_UIKIT || TARGET_OS_OSX || TARGET_OS_MACCATALYST +#if !TARGET_OS_WATCH [NSNotificationCenter.defaultCenter addObserverForName:backgroundNotificationName object:nil queue:nil @@ -114,9 +123,25 @@ - (void)trackApplicationUIKitNotifications withDataKey:@"state" withDataValue:@"foreground"]; }]; -#endif +#endif // !TARGET_OS_WATCH } +#if !TARGET_OS_WATCH +- (void)trackNetworkConnectivityChanges +{ + [SentryDependencyContainer.sharedInstance.reachability + addObserver:self + withCallback:^(BOOL connected, NSString *_Nonnull typeDescription) { + SentryBreadcrumb *crumb = + [[SentryBreadcrumb alloc] initWithLevel:kSentryLevelInfo + category:@"device.connectivity"]; + crumb.type = @"connectivity"; + crumb.data = [NSDictionary dictionaryWithObject:typeDescription forKey:@"connectivity"]; + [self.delegate addBreadcrumb:crumb]; + }]; +} +#endif // !TARGET_OS_WATCH + - (void)addBreadcrumbWithType:(NSString *)type withCategory:(NSString *)category withLevel:(SentryLevel)level @@ -155,7 +180,7 @@ + (BOOL)avoidSender:(id)sender forTarget:(id)target action:(NSString *)action } return NO; } -#endif +#endif // SENTRY_HAS_UIKIT - (void)swizzleSendAction { @@ -183,9 +208,9 @@ - (void)swizzleSendAction } forKey:SentryBreadcrumbTrackerSwizzleSendAction]; -#else +#else // !SENTRY_HAS_UIKIT SENTRY_LOG_DEBUG(@"NO UIKit -> [SentryBreadcrumbTracker swizzleSendAction] does nothing."); -#endif +#endif // SENTRY_HAS_UIKIT } - (void)swizzleViewDidAppear @@ -223,9 +248,9 @@ - (void)swizzleViewDidAppear }), mode, swizzleViewDidAppearKey); # pragma clang diagnostic pop -#else +#else // !SENTRY_HAS_UIKIT SENTRY_LOG_DEBUG(@"NO UIKit -> [SentryBreadcrumbTracker swizzleViewDidAppear] does nothing."); -#endif +#endif // SENTRY_HAS_UIKIT } #if SENTRY_HAS_UIKIT @@ -287,7 +312,7 @@ + (NSDictionary *)fetchInfoAboutViewController:(UIViewController *)controller return info; } -#endif +#endif // SENTRY_HAS_UIKIT @end diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 5f566ce23da..778755882f6 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -35,6 +35,10 @@ # import "SentryUIDeviceWrapper.h" #endif // TARGET_OS_IOS +#if !TARGET_OS_WATCH +# import "SentryReachability.h" +#endif // !TARGET_OS_WATCH + @implementation SentryDependencyContainer static SentryDependencyContainer *instance; @@ -368,4 +372,18 @@ - (SentryMXManager *)metricKitManager #endif // SENTRY_HAS_METRIC_KIT +#if !TARGET_OS_WATCH +- (SentryReachability *)reachability +{ + if (_reachability == nil) { + @synchronized(sentryDependencyContainerLock) { + if (_reachability == nil) { + _reachability = [[SentryReachability alloc] init]; + } + } + } + return _reachability; +} +#endif // !TARGET_OS_WATCH + @end diff --git a/Sources/Sentry/SentryHttpTransport.m b/Sources/Sentry/SentryHttpTransport.m index 9be24103666..0ad49345c73 100644 --- a/Sources/Sentry/SentryHttpTransport.m +++ b/Sources/Sentry/SentryHttpTransport.m @@ -28,6 +28,9 @@ @interface SentryHttpTransport () +#if !TARGET_OS_WATCH + +#endif // !TARGET_OS_WATCH @property (nonatomic, strong) SentryFileManager *fileManager; @property (nonatomic, strong) id requestManager; @@ -37,9 +40,6 @@ @property (nonatomic, strong) SentryEnvelopeRateLimit *envelopeRateLimit; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; @property (nonatomic, strong) dispatch_group_t dispatchGroup; -#if !TARGET_OS_WATCH -@property (nonatomic, strong) SentryReachability *reachability; -#endif // !TARGET_OS_WATCH #if TEST || TESTCI @property (nullable, nonatomic, strong) void (^startFlushCallback)(void); @@ -73,9 +73,6 @@ - (id)initWithOptions:(SentryOptions *)options rateLimits:(id)rateLimits envelopeRateLimit:(SentryEnvelopeRateLimit *)envelopeRateLimit dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper -#if !TARGET_OS_WATCH - reachability:(SentryReachability *)reachability -#endif // !TARGET_OS_WATCH { if (self = [super init]) { self.options = options; @@ -95,33 +92,33 @@ - (id)initWithOptions:(SentryOptions *)options [self sendAllCachedEnvelopes]; #if !TARGET_OS_WATCH - self.reachability = reachability; __weak SentryHttpTransport *weakSelf = self; - [self.reachability monitorURL:[NSURL URLWithString:@"https://sentry.io"] - usingCallback:^(BOOL connected, NSString *_Nonnull typeDescription) { - if (weakSelf == nil) { - SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything."); - return; - } - - if (connected) { - SENTRY_LOG_DEBUG(@"Internet connection is back."); - [weakSelf sendAllCachedEnvelopes]; - } else { - SENTRY_LOG_DEBUG(@"Lost internet connection."); - } - }]; -#endif + [SentryDependencyContainer.sharedInstance.reachability + addObserver:self + withCallback:^(BOOL connected, NSString *_Nonnull typeDescription) { + if (weakSelf == nil) { + SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything."); + return; + } + + if (connected) { + SENTRY_LOG_DEBUG(@"Internet connection is back."); + [weakSelf sendAllCachedEnvelopes]; + } else { + SENTRY_LOG_DEBUG(@"Lost internet connection."); + } + }]; +#endif // !TARGET_OS_WATCH } return self; } +#if !TARGET_OS_WATCH - (void)dealloc { -#if !TARGET_OS_WATCH - [self.reachability stopMonitoring]; -#endif + [SentryDependencyContainer.sharedInstance.reachability removeObserver:self]; } +#endif // !TARGET_OS_WATCH - (void)sendEnvelope:(SentryEnvelope *)envelope { diff --git a/Sources/Sentry/SentryReachability+Private.h b/Sources/Sentry/SentryReachability+Private.h new file mode 100644 index 00000000000..d65755bead7 --- /dev/null +++ b/Sources/Sentry/SentryReachability+Private.h @@ -0,0 +1,15 @@ +#import "SentryReachability.h" + +#if !TARGET_OS_WATCH + +void SentryConnectivityCallback(__unused SCNetworkReachabilityRef target, + SCNetworkReachabilityFlags flags, __unused void *info); + +@interface +SentryReachability () + +@property SCNetworkReachabilityRef sentry_reachability_ref; + +@end + +#endif // !TARGET_OS_WATCH diff --git a/Sources/Sentry/SentryReachability.m b/Sources/Sentry/SentryReachability.m index 1ec317392fb..3af2292bc13 100644 --- a/Sources/Sentry/SentryReachability.m +++ b/Sources/Sentry/SentryReachability.m @@ -24,21 +24,21 @@ // THE SOFTWARE. // -#import "SentryReachability.h" +#import "SentryLog.h" +#import "SentryReachability+Private.h" #if !TARGET_OS_WATCH static const SCNetworkReachabilityFlags kSCNetworkReachabilityFlagsUninitialized = UINT32_MAX; -static SCNetworkReachabilityRef sentry_reachability_ref; static NSMutableDictionary *sentry_reachability_change_blocks; static SCNetworkReachabilityFlags sentry_current_reachability_state = kSCNetworkReachabilityFlagsUninitialized; -static NSString *const SentryConnectivityCellular = @"cellular"; -static NSString *const SentryConnectivityWiFi = @"wifi"; -static NSString *const SentryConnectivityNone = @"none"; +NSString *const SentryConnectivityCellular = @"cellular"; +NSString *const SentryConnectivityWiFi = @"wifi"; +NSString *const SentryConnectivityNone = @"none"; /** * Check whether the connectivity change should be noted or ignored. @@ -54,23 +54,15 @@ # else // !SENTRY_HAS_UIKIT const SCNetworkReachabilityFlags importantFlags = kSCNetworkReachabilityFlagsReachable; # endif // SENTRY_HAS_UIKIT - __block BOOL shouldReport = YES; + // Check if the reported state is different from the last known state (if any) SCNetworkReachabilityFlags newFlags = flags & importantFlags; - SCNetworkReachabilityFlags oldFlags = sentry_current_reachability_state & importantFlags; - if (newFlags != oldFlags) { - // When first subscribing to be notified of changes, the callback is - // invoked immmediately even if nothing has changed. So this block - // ignores the very first check, reporting all others. - if (sentry_current_reachability_state == kSCNetworkReachabilityFlagsUninitialized) { - shouldReport = NO; - } - // Cache the reachability state to report the previous value representation - sentry_current_reachability_state = flags; - } else { - shouldReport = NO; + if (newFlags == sentry_current_reachability_state) { + return NO; } - return shouldReport; + + sentry_current_reachability_state = newFlags; + return YES; } /** @@ -117,11 +109,20 @@ + (void)initialize - (void)dealloc { - [self stopMonitoring]; + for (id observer in sentry_reachability_change_blocks.allKeys) { + [self removeObserver:observer]; + } } -- (void)monitorURL:(NSURL *)URL usingCallback:(SentryConnectivityChangeBlock)block +- (void)addObserver:(id)observer + withCallback:(SentryConnectivityChangeBlock)block; { + sentry_reachability_change_blocks[[observer description]] = block; + + if (sentry_current_reachability_state != kSCNetworkReachabilityFlagsUninitialized) { + return; + } + static dispatch_once_t once_t; static dispatch_queue_t reachabilityQueue; dispatch_once(&once_t, ^{ @@ -129,33 +130,33 @@ - (void)monitorURL:(NSURL *)URL usingCallback:(SentryConnectivityChangeBlock)blo = dispatch_queue_create("io.sentry.cocoa.connectivity", DISPATCH_QUEUE_SERIAL); }); - sentry_reachability_change_blocks[[self keyForInstance]] = block; - - const char *nodename = URL.host.UTF8String; - if (!nodename) { + _sentry_reachability_ref = SCNetworkReachabilityCreateWithName(NULL, "sentry.io"); + if (!_sentry_reachability_ref) { // Can be null if a bad hostname was specified return; } - sentry_reachability_ref = SCNetworkReachabilityCreateWithName(NULL, nodename); - if (sentry_reachability_ref) { // Can be null if a bad hostname was specified - SCNetworkReachabilitySetCallback(sentry_reachability_ref, SentryConnectivityCallback, NULL); - SCNetworkReachabilitySetDispatchQueue(sentry_reachability_ref, reachabilityQueue); - } + SENTRY_LOG_DEBUG(@"registering callback for reachability ref %@", _sentry_reachability_ref); + SCNetworkReachabilitySetCallback(_sentry_reachability_ref, SentryConnectivityCallback, NULL); + SCNetworkReachabilitySetDispatchQueue(_sentry_reachability_ref, reachabilityQueue); } -- (void)stopMonitoring +- (void)removeObserver:(id)observer { - [sentry_reachability_change_blocks removeObjectForKey:[self keyForInstance]]; - if (sentry_reachability_ref) { - SCNetworkReachabilitySetCallback(sentry_reachability_ref, NULL, NULL); - SCNetworkReachabilitySetDispatchQueue(sentry_reachability_ref, NULL); + [sentry_reachability_change_blocks removeObjectForKey:[observer description]]; + if (sentry_reachability_change_blocks.allValues.count > 0) { + return; } + sentry_current_reachability_state = kSCNetworkReachabilityFlagsUninitialized; -} -- (NSString *)keyForInstance -{ - return [self description]; + if (_sentry_reachability_ref == nil) { + SENTRY_LOG_WARN(@"No reachability ref to unregister."); + return; + } + + SENTRY_LOG_DEBUG(@"removing callback for reachability ref %@", _sentry_reachability_ref); + SCNetworkReachabilitySetCallback(_sentry_reachability_ref, NULL, NULL); + SCNetworkReachabilitySetDispatchQueue(_sentry_reachability_ref, NULL); } @end diff --git a/Sources/Sentry/SentryTransportFactory.m b/Sources/Sentry/SentryTransportFactory.m index 03bf69815b9..8e93c478f21 100644 --- a/Sources/Sentry/SentryTransportFactory.m +++ b/Sources/Sentry/SentryTransportFactory.m @@ -61,11 +61,7 @@ @implementation SentryTransportFactory requestBuilder:[[SentryNSURLRequestBuilder alloc] init] rateLimits:rateLimits envelopeRateLimit:envelopeRateLimit - dispatchQueueWrapper:dispatchQueueWrapper -#if !TARGET_OS_WATCH - reachability:[[SentryReachability alloc] init] -#endif // !TARGET_OS_WATCH - ]; + dispatchQueueWrapper:dispatchQueueWrapper]; } @end diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index 213a898d323..052d4faa678 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -34,6 +34,10 @@ @class SentryUIDeviceWrapper; #endif // TARGET_OS_IOS +#if !TARGET_OS_WATCH +@class SentryReachability; +#endif // !TARGET_OS_WATCH + NS_ASSUME_NONNULL_BEGIN @interface SentryDependencyContainer : NSObject @@ -76,6 +80,10 @@ SENTRY_NO_INIT @property (nonatomic, strong) SentryUIDeviceWrapper *uiDeviceWrapper; #endif // TARGET_OS_IOS +#if !TARGET_OS_WATCH +@property (nonatomic, strong) SentryReachability *reachability; +#endif // !TARGET_OS_WATCH + - (SentryANRTracker *)getANRTracker:(NSTimeInterval)timeout; #if SENTRY_HAS_METRIC_KIT diff --git a/Sources/Sentry/include/SentryHttpTransport.h b/Sources/Sentry/include/SentryHttpTransport.h index 62b933e8ea2..cc6ca0adaf0 100644 --- a/Sources/Sentry/include/SentryHttpTransport.h +++ b/Sources/Sentry/include/SentryHttpTransport.h @@ -8,10 +8,6 @@ @class SentryOptions, SentryDispatchQueueWrapper, SentryNSURLRequestBuilder; -#if !TARGET_OS_WATCH -@class SentryReachability; -#endif // !TARGET_OS_WATCH - NS_ASSUME_NONNULL_BEGIN @interface SentryHttpTransport @@ -24,11 +20,7 @@ SENTRY_NO_INIT requestBuilder:(SentryNSURLRequestBuilder *)requestBuilder rateLimits:(id)rateLimits envelopeRateLimit:(SentryEnvelopeRateLimit *)envelopeRateLimit - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper -#if !TARGET_OS_WATCH - reachability:(SentryReachability *)reachability -#endif // !TARGET_OS_WATCH - ; + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; @end diff --git a/Sources/Sentry/include/SentryReachability.h b/Sources/Sentry/include/SentryReachability.h index 46fc6501938..bfd62b80600 100644 --- a/Sources/Sentry/include/SentryReachability.h +++ b/Sources/Sentry/include/SentryReachability.h @@ -24,6 +24,7 @@ // THE SOFTWARE. // +#import "SentryDefines.h" #import #if !TARGET_OS_WATCH @@ -38,6 +39,10 @@ NSString *SentryConnectivityFlagRepresentation(SCNetworkReachabilityFlags flags) BOOL SentryConnectivityShouldReportChange(SCNetworkReachabilityFlags flags); +SENTRY_EXTERN NSString *const SentryConnectivityCellular; +SENTRY_EXTERN NSString *const SentryConnectivityWiFi; +SENTRY_EXTERN NSString *const SentryConnectivityNone; + /** * Function signature to connectivity monitoring callback of @c SentryReachability * @param connected @c YES if the monitored URL is reachable @@ -45,6 +50,9 @@ BOOL SentryConnectivityShouldReportChange(SCNetworkReachabilityFlags flags); */ typedef void (^SentryConnectivityChangeBlock)(BOOL connected, NSString *typeDescription); +@protocol SentryReachabilityObserver +@end + /** * Monitors network connectivity using @c SCNetworkReachability callbacks, * providing a customizable callback block invoked when connectivity changes. @@ -53,18 +61,15 @@ typedef void (^SentryConnectivityChangeBlock)(BOOL connected, NSString *typeDesc /** * Invoke a block each time network connectivity changes - * @param URL The URL monitored for changes. Should be equivalent to - * @c BugsnagConfiguration.notifyURL . * @param block The block called when connectivity changes */ -- (void)monitorURL:(NSURL *)URL usingCallback:(SentryConnectivityChangeBlock)block; +- (void)addObserver:(id)observer + withCallback:(SentryConnectivityChangeBlock)block; /** * Stop monitoring the URL previously configured with @c monitorURL:usingCallback: */ -- (void)stopMonitoring; - -- (NSString *)keyForInstance; +- (void)removeObserver:(id)observer; @end diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashObjCApple.h b/Sources/SentryCrash/Recording/Tools/SentryCrashObjCApple.h index 441c108dd1f..c665cdf51c4 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashObjCApple.h +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashObjCApple.h @@ -288,8 +288,8 @@ typedef struct __CFRuntimeBase { # define __CF_BIG_ENDIAN__ 0 #endif -#define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__)*3) -#define CF_RC_BITS (!!(__CF_LITTLE_ENDIAN__)*3) +#define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__) * 3) +#define CF_RC_BITS (!!(__CF_LITTLE_ENDIAN__) * 3) /* Bit manipulation macros */ /* Bits are numbered from 31 on left to 0 on right */ @@ -299,7 +299,7 @@ typedef struct __CFRuntimeBase { /* In the following, N1 and N2 specify an inclusive range N2..N1 with N1 >= N2 */ #define __CFBitfieldMask(N1, N2) ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1)) -#define __CFBitfieldGetValue(V, N1, N2) (((V)&__CFBitfieldMask(N1, N2)) >> (N2)) +#define __CFBitfieldGetValue(V, N1, N2) (((V) & __CFBitfieldMask(N1, N2)) >> (N2)) // ====================================================================== #pragma mark - CF-1153.18/CFString.c - diff --git a/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift b/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift index 270cf3bb613..85b5a71b5a2 100644 --- a/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift @@ -29,6 +29,31 @@ class SentryBreadcrumbTrackerTests: XCTestCase { XCTAssertEqual(0, dict?.count) } + func testNetworkConnectivityChangeBreadcrumbs() throws { + let testReachability = TestSentryReachability() + SentryDependencyContainer.sharedInstance().reachability = testReachability + let sut = SentryBreadcrumbTracker() + sut.start(with: delegate) + let states = [SentryConnectivityCellular, + SentryConnectivityWiFi, + SentryConnectivityNone, + SentryConnectivityWiFi, + SentryConnectivityCellular, + SentryConnectivityWiFi + ] + states.forEach { + testReachability.setReachabilityState(state: $0) + } + sut.stop() + XCTAssertEqual(delegate.addCrumbInvocations.count, states.count + 1) // 1 breadcrumb for the tracker start + try states.enumerated().forEach { + let crumb = delegate.addCrumbInvocations.invocations[$0.offset + 1] + XCTAssertEqual(crumb.type, "connectivity") + XCTAssertEqual(crumb.category, "device.connectivity") + XCTAssertEqual(try XCTUnwrap(crumb.data?["connectivity"] as? String), $0.element) + } + } + func testSwizzlingStarted_ViewControllerAppears_AddsUILifeCycleBreadcrumb() { let scope = Scope() let client = TestClient(options: Options()) @@ -48,13 +73,13 @@ class SentryBreadcrumbTrackerTests: XCTestCase { let crumbs = delegate.addCrumbInvocations.invocations - // one breadcrumb for starting the tracker, and a second one for the swizzled viewDidAppear - guard crumbs.count == 2 else { - XCTFail("Expected exactly 2 breadcrumbs, got: \(crumbs)") + // one breadcrumb for starting the tracker, one for the first reachability breadcrumb and one final one for the swizzled viewDidAppear + guard crumbs.count == 3 else { + XCTFail("Expected exactly 3 breadcrumbs, got: \(crumbs)") return } - let lifeCycleCrumb = crumbs[1] + let lifeCycleCrumb = crumbs[2] XCTAssertEqual("navigation", lifeCycleCrumb.type) XCTAssertEqual("ui.lifecycle", lifeCycleCrumb.category) XCTAssertEqual("false", lifeCycleCrumb.data?["beingPresented"] as? String) diff --git a/Tests/SentryTests/Networking/SentryHttpTransportTests.swift b/Tests/SentryTests/Networking/SentryHttpTransportTests.swift index c90570274e4..9c19ef37ea4 100644 --- a/Tests/SentryTests/Networking/SentryHttpTransportTests.swift +++ b/Tests/SentryTests/Networking/SentryHttpTransportTests.swift @@ -45,6 +45,8 @@ class SentryHttpTransportTests: XCTestCase { let queue = DispatchQueue(label: "SentryHttpTransportTests", qos: .userInitiated, attributes: [.concurrent, .initiallyInactive]) init() { + SentryDependencyContainer.sharedInstance().reachability = reachability + currentDateProvider = TestCurrentDateProvider() SentryDependencyContainer.sharedInstance().dateProvider = currentDateProvider @@ -111,8 +113,7 @@ class SentryHttpTransportTests: XCTestCase { requestBuilder: requestBuilder, rateLimits: rateLimits, envelopeRateLimit: EnvelopeRateLimit(rateLimits: rateLimits), - dispatchQueueWrapper: dispatchQueueWrapper, - reachability: reachability + dispatchQueueWrapper: dispatchQueueWrapper ) #else // os(watchOS) diff --git a/Tests/SentryTests/Networking/SentryReachabilityTests.m b/Tests/SentryTests/Networking/SentryReachabilityTests.m index 79c33ad8545..bf733289d0a 100644 --- a/Tests/SentryTests/Networking/SentryReachabilityTests.m +++ b/Tests/SentryTests/Networking/SentryReachabilityTests.m @@ -1,6 +1,12 @@ +#import "SentryReachability+Private.h" #import "SentryReachability.h" #import +@interface TestSentryReachabilityObserver : NSObject +@end +@implementation TestSentryReachabilityObserver +@end + #if !TARGET_OS_WATCH @interface SentryConnectivityTest : XCTestCase @property (strong, nonatomic) SentryReachability *reachability; @@ -20,30 +26,62 @@ - (void)tearDown - (void)testConnectivityRepresentations { - XCTAssertEqualObjects(@"none", SentryConnectivityFlagRepresentation(0)); - XCTAssertEqualObjects( - @"none", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsDirect)); + XCTAssertEqualObjects(SentryConnectivityNone, SentryConnectivityFlagRepresentation(0)); + XCTAssertEqualObjects(SentryConnectivityNone, + SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsDirect)); # if SENTRY_HAS_UIKIT // kSCNetworkReachabilityFlagsIsWWAN does not exist on macOS - XCTAssertEqualObjects( - @"none", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsWWAN)); - XCTAssertEqualObjects(@"cellular", + XCTAssertEqualObjects(SentryConnectivityNone, + SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsWWAN)); + XCTAssertEqualObjects(SentryConnectivityCellular, SentryConnectivityFlagRepresentation( kSCNetworkReachabilityFlagsIsWWAN | kSCNetworkReachabilityFlagsReachable)); # endif // SENTRY_HAS_UIKIT - XCTAssertEqualObjects( - @"wifi", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsReachable)); - XCTAssertEqualObjects(@"wifi", + XCTAssertEqualObjects(SentryConnectivityWiFi, + SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsReachable)); + XCTAssertEqualObjects(SentryConnectivityWiFi, SentryConnectivityFlagRepresentation( kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsIsDirect)); } -- (void)testUniqueKeyForInstances +- (void)testMultipleReachabilityObservers { - SentryReachability *anotherReachability = [[SentryReachability alloc] init]; - XCTAssertNotEqualObjects( - [self.reachability keyForInstance], [anotherReachability keyForInstance]); - XCTAssertEqualObjects([self.reachability keyForInstance], [self.reachability keyForInstance]); + SentryReachability *reachability = [[SentryReachability alloc] init]; + + XCTestExpectation *aExp = + [self expectationWithDescription: + @"reachability state change for observer monitoring https://sentry.io"]; + aExp.expectedFulfillmentCount = 5; + TestSentryReachabilityObserver *a = [[TestSentryReachabilityObserver alloc] init]; + [reachability addObserver:a + withCallback:^(__unused BOOL connected, + NSString *_Nonnull __unused typeDescription) { [aExp fulfill]; }]; + + SentryConnectivityCallback(reachability.sentry_reachability_ref, + kSCNetworkReachabilityFlagsReachable, nil); // ignored, as it's the first callback + SentryConnectivityCallback( + reachability.sentry_reachability_ref, kSCNetworkReachabilityFlagsInterventionRequired, nil); + + XCTestExpectation *bExp = + [self expectationWithDescription: + @"reachability state change for observer monitoring https://google.io"]; + bExp.expectedFulfillmentCount = 2; + TestSentryReachabilityObserver *b = [[TestSentryReachabilityObserver alloc] init]; + [reachability addObserver:b + withCallback:^(__unused BOOL connected, + NSString *_Nonnull __unused typeDescription) { [bExp fulfill]; }]; + + SentryConnectivityCallback( + reachability.sentry_reachability_ref, kSCNetworkReachabilityFlagsReachable, nil); + SentryConnectivityCallback( + reachability.sentry_reachability_ref, kSCNetworkReachabilityFlagsInterventionRequired, nil); + + [reachability removeObserver:b]; + + SentryConnectivityCallback( + reachability.sentry_reachability_ref, kSCNetworkReachabilityFlagsReachable, nil); + + [self waitForExpectationsWithTimeout:1.f handler:nil]; } @end diff --git a/Tests/SentryTests/Networking/TestSentryReachability.swift b/Tests/SentryTests/Networking/TestSentryReachability.swift index e56304086a3..263604c2b93 100644 --- a/Tests/SentryTests/Networking/TestSentryReachability.swift +++ b/Tests/SentryTests/Networking/TestSentryReachability.swift @@ -5,16 +5,20 @@ import SentryTestUtils class TestSentryReachability: SentryReachability { var block: SentryConnectivityChangeBlock? - override func monitorURL(_ URL: URL, usingCallback block: @escaping SentryConnectivityChangeBlock) { + override func add(_ observer: SentryReachabilityObserver, withCallback block: @escaping SentryConnectivityChangeBlock) { self.block = block } + func setReachabilityState(state: String) { + block?(state != SentryConnectivityNone, state) + } + func triggerNetworkReachable() { - block?(true, "wifi") + block?(true, SentryConnectivityWiFi) } var stopMonitoringInvocations = Invocations() - override func stopMonitoring() { + override func remove(_ observer: SentryReachabilityObserver) { stopMonitoringInvocations.record(Void()) } } diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 2fa263f2e0e..f22a91dc3dd 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -801,10 +801,8 @@ - (void)testChanging_enableTracing_afterSetting_tracesSampleRate - (void)testChanging_enableTracing_afterSetting_tracesSampler { SentryOptions *options = [[SentryOptions alloc] init]; - options.tracesSampler = ^NSNumber *(SentrySamplingContext *__unused samplingContext) - { - return @0.1; - }; + options.tracesSampler + = ^NSNumber *(SentrySamplingContext *__unused samplingContext) { return @0.1; }; options.enableTracing = NO; XCTAssertNil(options.tracesSampleRate); options.enableTracing = FALSE; @@ -1161,10 +1159,8 @@ - (void)testDefaultInitialScope - (void)testInitialScope { - SentryScope * (^initialScope)(SentryScope *) = ^SentryScope *(SentryScope *scope) - { - return scope; - }; + SentryScope * (^initialScope)(SentryScope *) + = ^SentryScope *(SentryScope *scope) { return scope; }; SentryOptions *options = [self getValidOptions:@{ @"initialScope" : initialScope }]; XCTAssertIdentical(initialScope, options.initialScope); }