diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a48f6b9..4db719ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms. ## Enhancements +* Convert `event.app` from `NSDictionary` to a structured class + [#520](https://github.com/bugsnag/bugsnag-cocoa/pull/520) + * Make `BugsnagClient` a public interface [#517](https://github.com/bugsnag/bugsnag-cocoa/pull/517) diff --git a/OSX/Bugsnag.xcodeproj/project.pbxproj b/OSX/Bugsnag.xcodeproj/project.pbxproj index 5486d5759..23fb67977 100644 --- a/OSX/Bugsnag.xcodeproj/project.pbxproj +++ b/OSX/Bugsnag.xcodeproj/project.pbxproj @@ -179,6 +179,7 @@ E79E6BDA1F4E3850002B35F9 /* BSG_RFC3339DateTool.m in Sources */ = {isa = PBXBuildFile; fileRef = E79E6B6E1F4E3850002B35F9 /* BSG_RFC3339DateTool.m */; }; E79E6BDB1F4E3850002B35F9 /* BSG_KSCrashReportFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B711F4E3850002B35F9 /* BSG_KSCrashReportFilter.h */; }; E79E6BDC1F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B721F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h */; }; + E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */; }; E7AB4B9E2423E184004F015A /* BugsnagOnBreadcrumbTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */; }; E7CE78BB1FD94E77001D07E0 /* KSCrashReportConverter_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78991FD94E60001D07E0 /* KSCrashReportConverter_Tests.m */; }; E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78951FD94E5F001D07E0 /* KSCrashReportStore_Tests.m */; }; @@ -291,6 +292,7 @@ E790C46D24349CE2006FFB26 /* BugsnagAppWithState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppWithState.m; path = ../Source/BugsnagAppWithState.m; sourceTree = ""; }; E790C46E24349CE2006FFB26 /* BugsnagThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagThread.h; path = ../Source/BugsnagThread.h; sourceTree = ""; }; E790C46F24349CE2006FFB26 /* BugsnagThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThread.m; path = ../Source/BugsnagThread.m; sourceTree = ""; }; + E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagAppTest.m; sourceTree = ""; }; E791482B1FD82B0C003EFEBF /* BugsnagSessionTrackerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackerTest.m; sourceTree = ""; }; E791482C1FD82B0C003EFEBF /* BugsnagSessionTrackingPayloadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackingPayloadTest.m; sourceTree = ""; }; E791482D1FD82B0C003EFEBF /* BugsnagSessionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTest.m; sourceTree = ""; }; @@ -555,6 +557,7 @@ 8A2C8FAF1C6BC1F700846019 /* Tests */ = { isa = PBXGroup; children = ( + E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */, E790C41F2432314A006FFB26 /* BugsnagClientMirrorTest.m */, E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */, 00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */, @@ -1039,6 +1042,7 @@ buildActionMask = 2147483647; files = ( E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */, + E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */, 4B775FD422CBE02F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */, E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */, E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */, diff --git a/Source/BugsnagApp.h b/Source/BugsnagApp.h index c561b9f8d..1756f085b 100644 --- a/Source/BugsnagApp.h +++ b/Source/BugsnagApp.h @@ -8,14 +8,45 @@ #import -NS_ASSUME_NONNULL_BEGIN - /** * Stateless information set by the notifier about your app can be found on this class. These values * can be accessed and amended if necessary. */ @interface BugsnagApp : NSObject -@end +/** + * The bundle version used by the application + */ +@property(nonatomic) NSString *_Nullable bundleVersion; + +/** + * The revision ID from the manifest (React Native apps only) + */ +@property(nonatomic) NSString *_Nullable codeBundleId; + +/** + * Unique identifier for the debug symbols file corresponding to the application + */ +@property(nonatomic) NSString *_Nullable dsymUuid; -NS_ASSUME_NONNULL_END +/** + * The app identifier used by the application + */ +@property(nonatomic) NSString *_Nullable id; + +/** + * The release stage set in Configuration + */ +@property(nonatomic) NSString *_Nullable releaseStage; + +/** + * The application type set in Configuration + */ +@property(nonatomic) NSString *_Nullable type; + +/** + * The version of the application set in Configuration + */ +@property(nonatomic) NSString *_Nullable version; + +@end diff --git a/Source/BugsnagApp.m b/Source/BugsnagApp.m index 44f715b0b..3a3bc5219 100644 --- a/Source/BugsnagApp.m +++ b/Source/BugsnagApp.m @@ -7,7 +7,42 @@ // #import "BugsnagApp.h" +#import "BugsnagKeys.h" +#import "BugsnagConfiguration.h" +#import "BugsnagCollections.h" @implementation BugsnagApp ++ (BugsnagApp *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config { + BugsnagApp *app = [BugsnagApp new]; + [self populateFields:app dictionary:event config:config]; + return app; +} + ++ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config { + NSDictionary *system = event[BSGKeySystem]; + app.id = system[@"CFBundleIdentifier"]; + app.bundleVersion = system[@"CFBundleVersion"]; + app.dsymUuid = system[@"app_uuid"]; + app.version = [event valueForKeyPath:@"user.config.appVersion"] ?: system[@"CFBundleShortVersionString"]; + app.releaseStage = [event valueForKeyPath:@"user.config.releaseStage"] ?: config.releaseStage; + app.codeBundleId = config.codeBundleId; + app.type = config.appType; +} + +- (NSDictionary *)toDict { + NSMutableDictionary *dict = [NSMutableDictionary new]; + BSGDictInsertIfNotNil(dict, self.bundleVersion, @"bundleVersion"); + BSGDictInsertIfNotNil(dict, self.codeBundleId, @"codeBundleId"); + BSGDictInsertIfNotNil(dict, self.id, @"id"); + BSGDictInsertIfNotNil(dict, self.releaseStage, @"releaseStage"); + BSGDictInsertIfNotNil(dict, self.type, @"type"); + BSGDictInsertIfNotNil(dict, self.version, @"version"); + + if (self.dsymUuid != nil) { + BSGDictInsertIfNotNil(dict, @[self.dsymUuid], @"dsymUUIDs"); + } + return dict; +} + @end diff --git a/Source/BugsnagAppWithState.h b/Source/BugsnagAppWithState.h index b393724ff..abdd4b8e7 100644 --- a/Source/BugsnagAppWithState.h +++ b/Source/BugsnagAppWithState.h @@ -10,14 +10,25 @@ #import "BugsnagApp.h" -NS_ASSUME_NONNULL_BEGIN - /** * Stateful information set by the notifier about your app can be found on this class. These values * can be accessed and amended if necessary. */ @interface BugsnagAppWithState : BugsnagApp -@end +/** + * The number of milliseconds the application was running before the event occurred + */ +@property(nonatomic) NSUInteger duration; + +/** + * The number of milliseconds the application was running in the foreground before the + * event occurred + */ +@property(nonatomic) NSUInteger durationInForeground; -NS_ASSUME_NONNULL_END +/** + * Whether the application was in the foreground when the event occurred + */ +@property(nonatomic) BOOL inForeground; +@end diff --git a/Source/BugsnagAppWithState.m b/Source/BugsnagAppWithState.m index 782a89280..36c985246 100644 --- a/Source/BugsnagAppWithState.m +++ b/Source/BugsnagAppWithState.m @@ -7,7 +7,52 @@ // #import "BugsnagAppWithState.h" +#import "BugsnagKeys.h" +#import "BugsnagConfiguration.h" +#import "BugsnagCollections.h" + +@interface BugsnagApp () ++ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config; + +- (NSDictionary *)toDict; +@end @implementation BugsnagAppWithState ++ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event { + BugsnagAppWithState *app = [BugsnagAppWithState new]; + app.id = event[@"id"]; + app.releaseStage = event[@"releaseStage"]; + app.version = event[@"version"]; + app.bundleVersion = event[@"bundleVersion"]; + app.codeBundleId = event[@"codeBundleId"]; + app.inForeground = [event[@"inForeground"] boolValue]; + app.type = event[@"type"]; + return app; +} + ++ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config { + BugsnagAppWithState *app = [BugsnagAppWithState new]; + NSDictionary *system = event[BSGKeySystem]; + NSDictionary *stats = system[@"application_stats"]; + + // convert from seconds to milliseconds + NSUInteger activeTimeSinceLaunch = (NSUInteger) ([stats[@"active_time_since_launch"] longValue] * 1000); + NSUInteger backgroundTimeSinceLaunch = (NSUInteger) ([stats[@"background_time_since_launch"] longValue] * 1000); + + app.durationInForeground = activeTimeSinceLaunch; + app.duration = activeTimeSinceLaunch + backgroundTimeSinceLaunch; + app.inForeground = [stats[@"application_in_foreground"] boolValue]; + [BugsnagApp populateFields:app dictionary:event config:config]; + return app; +} + +- (NSDictionary *)toDict { + NSMutableDictionary *dict = (NSMutableDictionary *) [super toDict]; + BSGDictInsertIfNotNil(dict, @(self.duration), @"duration"); + BSGDictInsertIfNotNil(dict, @(self.durationInForeground), @"durationInForeground"); + BSGDictInsertIfNotNil(dict, @(self.inForeground), @"inForeground"); + return dict; +} + @end diff --git a/Source/BugsnagConfiguration.m b/Source/BugsnagConfiguration.m index 5a50ae9d5..fa9be2f4e 100644 --- a/Source/BugsnagConfiguration.m +++ b/Source/BugsnagConfiguration.m @@ -171,6 +171,14 @@ - (instancetype _Nonnull)initWithApiKey:(NSString *_Nonnull)apiKey #else _releaseStage = BSGKeyProduction; #endif + +#if TARGET_OS_TV + _appType = @"tvOS"; +#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE + _appType = @"iOS"; +#elif TARGET_OS_MAC + _appType = @"macOS"; +#endif return self; } diff --git a/Source/BugsnagEvent.h b/Source/BugsnagEvent.h index 3c255b12a..a03452141 100644 --- a/Source/BugsnagEvent.h +++ b/Source/BugsnagEvent.h @@ -13,6 +13,7 @@ @class BugsnagHandledState; @class BugsnagSession; @class BugsnagBreadcrumb; +@class BugsnagAppWithState; @class BugsnagMetadata; typedef NS_ENUM(NSUInteger, BSGSeverity) { @@ -90,10 +91,6 @@ initWithErrorName:(NSString *_Nonnull)name * The severity of the error generating the report */ @property(readwrite) BSGSeverity severity; -/** - * The release stage of the application - */ -@property(readwrite, copy, nullable) NSString *releaseStage; /** * The class of the error generating the report */ @@ -120,19 +117,16 @@ initWithErrorName:(NSString *_Nonnull)name /** * Device information such as OS name and version */ -@property(readwrite, copy, nullable) NSDictionary *device; +@property(readonly, nonnull) NSDictionary *device; /** * Device state such as memory allocation at crash time */ @property(readwrite, copy, nullable) NSDictionary *deviceState; + /** * App information such as the name, version, and bundle ID */ -@property(readwrite, copy, nullable) NSDictionary *app; -/** - * Device state such as oreground status and run duration - */ -@property(readwrite, copy, nullable) NSDictionary *appState; +@property(readonly, nonnull) BugsnagAppWithState *app; /** * Whether the event was a crash (i.e. unhandled) or handled error in which the system diff --git a/Source/BugsnagEvent.m b/Source/BugsnagEvent.m index e9355a9ae..27e637c42 100644 --- a/Source/BugsnagEvent.m +++ b/Source/BugsnagEvent.m @@ -26,6 +26,12 @@ #import "BugsnagKeys.h" #import "BugsnagClient.h" +@interface BugsnagAppWithState () ++ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config; +- (NSDictionary *)toDict; ++ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event; +@end + @interface BugsnagBreadcrumb () + (instancetype _Nullable)breadcrumbWithBlock: (BSGBreadcrumbConfiguration _Nonnull)block; @@ -236,10 +242,6 @@ @interface BugsnagEvent () * The type of the error, such as `mach` or `user` */ @property(nonatomic, readwrite, copy, nullable) NSString *errorType; -/** - * The UUID of the dSYM file - */ -@property(nonatomic, readonly, copy, nullable) NSString *dsymUUID; /** * A unique hash identifying this device for the application or vendor */ @@ -295,6 +297,11 @@ - (BOOL)shouldBeSent; * Raw error data */ @property(readwrite, copy, nullable) NSDictionary *error; + +/** + * The release stage of the application + */ +@property(readwrite, copy, nullable) NSString *releaseStage; @end @implementation BugsnagEvent @@ -310,19 +317,21 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { } if (self = [super init]) { + BugsnagConfiguration *config = [Bugsnag configuration]; + _error = [report valueForKeyPath:@"crash.error"]; _errorType = _error[BSGKeyType]; if ([[report valueForKeyPath:@"user.state.didOOM"] boolValue]) { _errorClass = BSGParseErrorClass(_error, _errorType); _errorMessage = BSGParseErrorMessage(report, _error, _errorType); _breadcrumbs = BSGParseBreadcrumbs(report); - _app = [report valueForKeyPath:@"user.state.oom.app"]; + _app = [BugsnagAppWithState appWithOomData:[report valueForKeyPath:@"user.state.oom.app"]]; _device = [report valueForKeyPath:@"user.state.oom.device"]; _releaseStage = [report valueForKeyPath:@"user.state.oom.app.releaseStage"]; _handledState = [BugsnagHandledState handledStateWithSeverityReason:LikelyOutOfMemory]; _deviceAppHash = [report valueForKeyPath:@"user.state.oom.device.id"]; self.metadata = [BugsnagMetadata new]; - + NSDictionary *sessionData = [report valueForKeyPath:@"user.state.oom.session"]; if (sessionData) { _session = [[BugsnagSession alloc] initWithDictionary:sessionData]; @@ -345,7 +354,6 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { } _binaryImages = report[@"binary_images"]; _breadcrumbs = BSGParseBreadcrumbs(report); - _dsymUUID = [report valueForKeyPath:@"system.app_uuid"]; _deviceAppHash = [report valueForKeyPath:@"system.device_app_hash"]; id userMetadata = [report valueForKeyPath:@"user.metaData"]; @@ -355,15 +363,11 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { else { self.metadata = [BugsnagMetadata new]; } - + _context = BSGParseContext(report); _deviceState = BSGParseDeviceState(report); _device = BSGParseDevice(report); - _app = BSGParseApp(report); - _appState = BSGParseAppState(report[BSGKeySystem], - BSGLoadConfigValue(report, @"appVersion"), - _releaseStage, // Already loaded from config - BSGLoadConfigValue(report, @"codeBundleId")); + _app = [BugsnagAppWithState appWithDictionary:report config:config]; _groupingHash = BSGParseGroupingHash(report); _overrides = [report valueForKeyPath:@"user.overrides"]; _customException = BSGParseCustomException(report, [_errorClass copy], [_errorMessage copy]); @@ -378,7 +382,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report { _depth = [[report valueForKeyPath:@"user.depth"] unsignedIntegerValue]; } - + // the event was unhandled. else { BOOL isSignal = [BSGKeySignal isEqualToString:_errorType]; @@ -411,7 +415,7 @@ - (instancetype _Nonnull)initWithErrorName:(NSString *_Nonnull)name _errorMessage = message; _overrides = [NSDictionary new]; self.metadata = [metadata deepCopy] ?: [BugsnagMetadata new]; - _releaseStage = config.releaseStage; + self.releaseStage = config.releaseStage; _enabledReleaseStages = config.enabledReleaseStages; // Set context based on current values. May be nil. _context = [[Bugsnag configuration] context]; @@ -579,19 +583,7 @@ - (NSDictionary *)toJson { NSDictionary *device = BSGDictMerge(self.device, self.deviceState); BSGDictSetSafeObject(event, device, BSGKeyDevice); - - NSMutableDictionary *appObj = [NSMutableDictionary new]; - [appObj addEntriesFromDictionary:self.app]; - - for (NSString *key in self.appState) { - BSGDictInsertIfNotNil(appObj, self.appState[key], key); - } - - if (self.dsymUUID) { - BSGDictInsertIfNotNil(appObj, @[self.dsymUUID], @"dsymUUIDs"); - } - - BSGDictSetSafeObject(event, appObj, BSGKeyApp); + BSGDictSetSafeObject(event, [self.app toDict], BSGKeyApp); BSGDictSetSafeObject(event, [self context], BSGKeyContext); BSGDictInsertIfNotNil(event, self.groupingHash, BSGKeyGroupingHash); diff --git a/Source/BugsnagKSCrashSysInfoParser.h b/Source/BugsnagKSCrashSysInfoParser.h index fed7e6047..d2c7179f0 100644 --- a/Source/BugsnagKSCrashSysInfoParser.h +++ b/Source/BugsnagKSCrashSysInfoParser.h @@ -11,9 +11,4 @@ #define PLATFORM_WORD_SIZE sizeof(void*)*8 NSDictionary *_Nonnull BSGParseDevice(NSDictionary *_Nonnull report); -NSDictionary *_Nonnull BSGParseApp(NSDictionary *_Nonnull report); -NSDictionary *_Nonnull BSGParseAppState(NSDictionary *_Nonnull report, - NSString *_Nullable preferredVersion, - NSString *_Nullable releaseStage, - NSString *_Nullable codeBundleId); NSDictionary *_Nonnull BSGParseDeviceState(NSDictionary *_Nonnull report); diff --git a/Source/BugsnagKSCrashSysInfoParser.m b/Source/BugsnagKSCrashSysInfoParser.m index 983828e73..420baafea 100644 --- a/Source/BugsnagKSCrashSysInfoParser.m +++ b/Source/BugsnagKSCrashSysInfoParser.m @@ -66,59 +66,6 @@ return device; } -NSDictionary *BSGParseApp(NSDictionary *report) { - NSDictionary *system = report[BSGKeySystem]; - - NSMutableDictionary *appState = [NSMutableDictionary dictionary]; - - NSDictionary *stats = system[@"application_stats"]; - - NSInteger activeTimeSinceLaunch = - [stats[@"active_time_since_launch"] doubleValue] * 1000.0; - NSInteger backgroundTimeSinceLaunch = - [stats[@"background_time_since_launch"] doubleValue] * 1000.0; - - BSGDictSetSafeObject(appState, @(activeTimeSinceLaunch), - @"durationInForeground"); - - BSGDictSetSafeObject(appState, system[BSGKeyExecutableName], BSGKeyName); - BSGDictSetSafeObject(appState, - @(activeTimeSinceLaunch + backgroundTimeSinceLaunch), - @"duration"); - BSGDictSetSafeObject(appState, stats[@"application_in_foreground"], - @"inForeground"); - BSGDictSetSafeObject(appState, system[@"CFBundleIdentifier"], BSGKeyId); - return appState; -} - -NSDictionary *BSGParseAppState(NSDictionary *report, NSString *preferredVersion, NSString *releaseStage, NSString *codeBundleId) { - NSMutableDictionary *app = [NSMutableDictionary dictionary]; - - NSString *version = preferredVersion ?: report[@"CFBundleShortVersionString"]; - - BSGDictSetSafeObject(app, report[@"CFBundleVersion"], @"bundleVersion"); - BSGDictSetSafeObject(app, releaseStage, - BSGKeyReleaseStage); - BSGDictSetSafeObject(app, version, BSGKeyVersion); - - BSGDictSetSafeObject(app, codeBundleId, @"codeBundleId"); - - NSString *appType; -#if TARGET_OS_TV - appType = @"tvOS"; -#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - appType = @"iOS"; -#elif TARGET_OS_MAC - appType = @"macOS"; -#endif - - if ([Bugsnag configuration].appType) { - appType = [Bugsnag configuration].appType; - } - BSGDictSetSafeObject(app, appType, @"type"); - return app; -} - NSDictionary *BSGParseDeviceState(NSDictionary *report) { NSMutableDictionary *deviceState = [NSMutableDictionary new]; BSGDictSetSafeObject(deviceState, report[@"model"], @"modelNumber"); diff --git a/Source/BugsnagSessionTrackingApiClient.m b/Source/BugsnagSessionTrackingApiClient.m index 6855c4439..65cde9232 100644 --- a/Source/BugsnagSessionTrackingApiClient.m +++ b/Source/BugsnagSessionTrackingApiClient.m @@ -10,6 +10,7 @@ #import "BugsnagLogger.h" #import "BugsnagSession.h" #import "BSG_RFC3339DateTool.h" +#import "Private.h" @interface BugsnagConfiguration () @property(nonatomic, readwrite, strong) NSMutableArray *onSessionBlocks; @@ -44,7 +45,7 @@ - (void)deliverSessionsInStore:(BugsnagSessionFileStore *)store { for (NSDictionary *dict in [store allFiles]) { [sessions addObject:[[BugsnagSession alloc] initWithDictionary:dict]]; } - BugsnagSessionTrackingPayload *payload = [[BugsnagSessionTrackingPayload alloc] initWithSessions:sessions]; + BugsnagSessionTrackingPayload *payload = [[BugsnagSessionTrackingPayload alloc] initWithSessions:sessions config:[Bugsnag configuration]]; NSUInteger sessionCount = payload.sessions.count; NSMutableDictionary *data = [payload toJson]; diff --git a/Source/BugsnagSessionTrackingPayload.h b/Source/BugsnagSessionTrackingPayload.h index b234d9cac..7c77be486 100644 --- a/Source/BugsnagSessionTrackingPayload.h +++ b/Source/BugsnagSessionTrackingPayload.h @@ -10,9 +10,11 @@ #import "BugsnagSession.h" +@class BugsnagConfiguration; + @interface BugsnagSessionTrackingPayload : NSObject -- (instancetype)initWithSessions:(NSArray *)sessions; +- (instancetype)initWithSessions:(NSArray *)sessions config:(BugsnagConfiguration *)config; - (NSMutableDictionary *)toJson; diff --git a/Source/BugsnagSessionTrackingPayload.m b/Source/BugsnagSessionTrackingPayload.m index a76d8fa77..db9c01c8b 100644 --- a/Source/BugsnagSessionTrackingPayload.m +++ b/Source/BugsnagSessionTrackingPayload.m @@ -13,8 +13,10 @@ #import "Bugsnag.h" #import "BugsnagKeys.h" #import "BSG_KSSystemInfo.h" +#import "BugsnagConfiguration.h" #import "BugsnagKSCrashSysInfoParser.h" #import "Private.h" +#import "BugsnagApp.h" @interface BugsnagSession () - (NSDictionary *)toJson; @@ -24,33 +26,57 @@ @interface Bugsnag () + (BugsnagClient *)client; @end +@interface BugsnagApp () ++ (BugsnagApp *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config; + +- (NSDictionary *)toDict; +@end + +@interface BugsnagSessionTrackingPayload () +@property (nonatomic) BugsnagConfiguration *config; +@end + @implementation BugsnagSessionTrackingPayload -- (instancetype)initWithSessions:(NSArray *)sessions { +- (instancetype)initWithSessions:(NSArray *)sessions config:(BugsnagConfiguration *)config { if (self = [super init]) { _sessions = sessions; + _config = config; } return self; } - - (NSMutableDictionary *)toJson { NSMutableDictionary *dict = [NSMutableDictionary new]; NSMutableArray *sessionData = [NSMutableArray new]; - + for (BugsnagSession *session in self.sessions) { [sessionData addObject:[session toJson]]; } BSGDictInsertIfNotNil(dict, sessionData, @"sessions"); BSGDictSetSafeObject(dict, [Bugsnag client].details, BSGKeyNotifier); - + + // app/device data collection currently relies on KSCrash reports, + // need to mimic the JSON structure here NSDictionary *systemInfo = [BSG_KSSystemInfo systemInfo]; - BSGDictSetSafeObject(dict, BSGParseAppState(systemInfo, - [Bugsnag configuration].appVersion, - [Bugsnag configuration].releaseStage, - [Bugsnag configuration].codeBundleId), @"app"); + NSDictionary *event = [self appInfo:systemInfo config:self.config]; + BugsnagApp *app = [BugsnagApp appWithDictionary:event config:self.config]; + BSGDictSetSafeObject(dict, [app toDict], @"app"); BSGDictSetSafeObject(dict, BSGParseDeviceState(systemInfo), @"device"); return dict; } +- (NSDictionary *)appInfo:(NSDictionary *)systemInfo config:(BugsnagConfiguration *)config { + NSMutableDictionary *data = [NSMutableDictionary new]; + BSGDictInsertIfNotNil(data, config.releaseStage, @"releaseStage"); + BSGDictInsertIfNotNil(data, config.appVersion, @"appVersion"); + + return @{ + @"system": systemInfo, + @"user": @{ + @"config": data + } + }; +} + @end diff --git a/Tests/BugsnagAppTest.m b/Tests/BugsnagAppTest.m new file mode 100644 index 000000000..e98c92c4c --- /dev/null +++ b/Tests/BugsnagAppTest.m @@ -0,0 +1,153 @@ +// +// BugsnagDeviceTest.m +// Tests +// +// Created by Jamie Lynch on 01/04/2020. +// Copyright © 2020 Bugsnag. All rights reserved. +// + +#import + +#import "BugsnagAppWithState.h" +#import "BugsnagConfiguration.h" +#import "BugsnagTestConstants.h" + +@interface BugsnagApp () ++ (BugsnagApp *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config; + +- (NSDictionary *)toDict; +@end + +@interface BugsnagAppWithState () ++ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config; ++ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event; +- (NSDictionary *)toDict; +@end + +@interface BugsnagAppTest : XCTestCase +@property NSDictionary *data; +@property BugsnagConfiguration *config; +@end + +@implementation BugsnagAppTest + +- (void)setUp { + [super setUp]; + self.data = @{ + @"system": @{ + @"application_stats": @{ + @"active_time_since_launch": @2, + @"background_time_since_launch": @5, + @"application_in_foreground": @YES, + }, + @"CFBundleExecutable": @"MyIosApp", + @"CFBundleIdentifier": @"com.example.foo.MyIosApp", + @"CFBundleShortVersionString": @"5.6.3", + @"CFBundleVersion": @"1", + @"app_uuid": @"dsym-uuid-123" + }, + @"user": @{ + @"config": @{ + @"releaseStage": @"beta" + } + } + }; + + self.config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + self.config.appType = @"iOS"; + self.config.codeBundleId = @"bundle-123"; +} + +- (void)testApp { + BugsnagApp *app = [BugsnagApp appWithDictionary:self.data config:self.config]; + + // verify stateless fields + XCTAssertEqualObjects(@"1", app.bundleVersion); + XCTAssertEqualObjects(@"bundle-123", app.codeBundleId); + XCTAssertEqualObjects(@"dsym-uuid-123", app.dsymUuid); + XCTAssertEqualObjects(@"com.example.foo.MyIosApp", app.id); + XCTAssertEqualObjects(@"beta", app.releaseStage); + XCTAssertEqualObjects(@"iOS", app.type); + XCTAssertEqualObjects(@"5.6.3", app.version); +} + +- (void)testAppWithState { + BugsnagAppWithState *app = [BugsnagAppWithState appWithDictionary:self.data config:self.config]; + + // verify stateful fields + XCTAssertEqual(7000, app.duration); + XCTAssertEqual(2000, app.durationInForeground); + XCTAssertTrue(app.inForeground); + + // verify stateless fields + XCTAssertEqualObjects(@"1", app.bundleVersion); + XCTAssertEqualObjects(@"bundle-123", app.codeBundleId); + XCTAssertEqualObjects(@"dsym-uuid-123", app.dsymUuid); + XCTAssertEqualObjects(@"com.example.foo.MyIosApp", app.id); + XCTAssertEqualObjects(@"beta", app.releaseStage); + XCTAssertEqualObjects(@"iOS", app.type); + XCTAssertEqualObjects(@"5.6.3", app.version); +} + +- (void)testAppToDict { + BugsnagApp *app = [BugsnagApp appWithDictionary:self.data config:self.config]; + NSDictionary *dict = [app toDict]; + + // verify stateless fields + XCTAssertEqualObjects(@"1", dict[@"bundleVersion"]); + XCTAssertEqualObjects(@"bundle-123", dict[@"codeBundleId"]); + XCTAssertEqualObjects(@[@"dsym-uuid-123"], dict[@"dsymUUIDs"]); + XCTAssertEqualObjects(@"com.example.foo.MyIosApp", dict[@"id"]); + XCTAssertEqualObjects(@"beta", dict[@"releaseStage"]); + XCTAssertEqualObjects(@"iOS", dict[@"type"]); + XCTAssertEqualObjects(@"5.6.3", dict[@"version"]); +} + +- (void)testAppWithStateToDict { + BugsnagAppWithState *app = [BugsnagAppWithState appWithDictionary:self.data config:self.config]; + NSDictionary *dict = [app toDict]; + + // verify stateful fields + XCTAssertEqual(7000, [dict[@"duration"] longValue]); + XCTAssertEqual(2000, [dict[@"durationInForeground"] longValue]); + XCTAssertTrue([dict[@"inForeground"] boolValue]); + + // verify stateless fields + XCTAssertEqualObjects(@"1", dict[@"bundleVersion"]); + XCTAssertEqualObjects(@"bundle-123", dict[@"codeBundleId"]); + XCTAssertEqualObjects(@[@"dsym-uuid-123"], dict[@"dsymUUIDs"]); + XCTAssertEqualObjects(@"com.example.foo.MyIosApp", dict[@"id"]); + XCTAssertEqualObjects(@"beta", dict[@"releaseStage"]); + XCTAssertEqualObjects(@"iOS", dict[@"type"]); + XCTAssertEqualObjects(@"5.6.3", dict[@"version"]); +} + +- (void)testAppFromOOM { + NSDictionary *oomData = @{ + @"id": @"com.example.foo.MyIosApp", + @"releaseStage": @"beta", + @"version": @"5.6.3", + @"bundleVersion": @"1", + @"codeBundleId": @"bundle-123", + @"inForeground": @YES, + @"type": @"iOS" + }; + + BugsnagAppWithState *app = [BugsnagAppWithState appWithOomData:oomData]; + + // verify stateful fields + XCTAssertEqual(0, app.duration); + XCTAssertEqual(0, app.durationInForeground); + XCTAssertTrue(app.inForeground); + + // verify stateless fields + XCTAssertEqualObjects(@"1", app.bundleVersion); + XCTAssertEqualObjects(@"bundle-123", app.codeBundleId); + XCTAssertNil(app.dsymUuid); + XCTAssertEqualObjects(@"com.example.foo.MyIosApp", app.id); + XCTAssertEqualObjects(@"beta", app.releaseStage); + XCTAssertEqualObjects(@"iOS", app.type); + XCTAssertEqualObjects(@"5.6.3", app.version); +} + +@end diff --git a/Tests/BugsnagSessionTrackingPayloadTest.m b/Tests/BugsnagSessionTrackingPayloadTest.m index f7197ee60..a94522956 100644 --- a/Tests/BugsnagSessionTrackingPayloadTest.m +++ b/Tests/BugsnagSessionTrackingPayloadTest.m @@ -9,6 +9,8 @@ #import #import "BugsnagSessionTrackingPayload.h" +#import "BugsnagConfiguration.h" +#import "BugsnagTestConstants.h" @interface BugsnagSessionTrackingPayloadTest : XCTestCase @property NSDictionary *payload; @@ -18,7 +20,9 @@ @implementation BugsnagSessionTrackingPayloadTest - (void)setUp { [super setUp]; - BugsnagSessionTrackingPayload *data = [BugsnagSessionTrackingPayload new]; + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + config.releaseStage = @"beta"; + BugsnagSessionTrackingPayload *data = [[BugsnagSessionTrackingPayload alloc] initWithSessions:@[] config:config]; BugsnagSession *session = [[BugsnagSession alloc] initWithId:@"test" startDate:[NSDate date] user:nil @@ -59,7 +63,7 @@ - (void)testDeviceSerialisation { - (void)testAppSerialisation { NSDictionary *app = self.payload[@"app"]; XCTAssertNotNil(app); - XCTAssertEqual(5, app.count); + XCTAssertEqual(6, app.count); XCTAssertNotNil(app[@"type"]); XCTAssertNotNil(app[@"version"]); diff --git a/Tests/BugsnagSinkTests.m b/Tests/BugsnagSinkTests.m index 58dc9b7d2..86d267bac 100644 --- a/Tests/BugsnagSinkTests.m +++ b/Tests/BugsnagSinkTests.m @@ -299,12 +299,11 @@ - (void)testEventApp { NSDictionary *event = [self.processedData[@"events"] firstObject]; NSDictionary *app = event[@"app"]; XCTAssertNotNil(app); - XCTAssertEqual(11, app.count); + XCTAssertEqual(9, app.count); XCTAssertEqualObjects(app[@"id"], @"net.hockeyapp.CrashProbeiOS"); XCTAssertNotNil(app[@"type"]); XCTAssertEqualObjects(app[@"version"], @"1.0"); - XCTAssertEqualObjects(app[@"name"], @"CrashProbeiOS"); XCTAssertEqualObjects(app[@"bundleVersion"], @"1"); XCTAssertEqualObjects(app[@"releaseStage"], @"production"); XCTAssertEqualObjects(app[@"dsymUUIDs"], @[@"D0A41830-4FD2-3B02-A23B-0741AD4C7F52"]); diff --git a/UPGRADING.md b/UPGRADING.md index 823ad732e..72c25e9cd 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -223,6 +223,8 @@ This is now BugsnagEvent. + event.unhandled ``` +`event.app` is now a structured class with properties for each value, rather than an `NSDictionary`. + #### Renames To add metadata to an individual report in a callback, use `addMetadata` instead diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index e3041505c..d28364abd 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -331,6 +331,7 @@ E790C45F24349A70006FFB26 /* BugsnagError.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E790C44C24349898006FFB26 /* BugsnagError.h */; }; E790C46024349A70006FFB26 /* BugsnagStackframe.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E790C451243498BB006FFB26 /* BugsnagStackframe.h */; }; E790C46124349A70006FFB26 /* BugsnagThread.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E790C45624349A28006FFB26 /* BugsnagThread.h */; }; + E790C49B2434C8C8006FFB26 /* BugsnagAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C49A2434C8C8006FFB26 /* BugsnagAppTest.m */; }; E79148251FD828E6003EFEBF /* BugsnagKeys.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E794E8021F9F743D00A67EE7 /* BugsnagKeys.h */; }; E79148261FD828E6003EFEBF /* BugsnagSessionTracker.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E72BF7731FC867E4004BE82F /* BugsnagSessionTracker.h */; }; E79148271FD828E6003EFEBF /* BugsnagSession.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E72BF7781FC869F7004BE82F /* BugsnagSession.h */; }; @@ -357,7 +358,6 @@ F42954D2337A7CAE1FE9B308 /* BugsnagFileStore.m in Sources */ = {isa = PBXBuildFile; fileRef = F42958B2E67C338E3086EAC2 /* BugsnagFileStore.m */; }; F429561943952286B690CFB1 /* BugsnagSessionTrackingApiClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F429517A5571A61A897E963D /* BugsnagSessionTrackingApiClient.h */; }; F4295932E7EC58D1CB806EC9 /* BugsnagFileStore.h in Headers */ = {isa = PBXBuildFile; fileRef = F42953E7E61199381E0405CC /* BugsnagFileStore.h */; }; - F4295995C3259BF7D9730BC4 /* BugsnagKSCrashSysInfoParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */; }; F4295B2AC95281CBA3A42DCA /* BugsnagSessionFileStore.m in Sources */ = {isa = PBXBuildFile; fileRef = F42954ACC6FFDDE3C8471495 /* BugsnagSessionFileStore.m */; }; F4295BB0741ED030D0CD002C /* BugsnagApiClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F4295E2778677786239F2B28 /* BugsnagApiClient.m */; }; F4295C14DCDDF541188CDE66 /* BugsnagSessionFileStore.h in Headers */ = {isa = PBXBuildFile; fileRef = F42955025DBE1DCEFD928CAA /* BugsnagSessionFileStore.h */; }; @@ -674,6 +674,7 @@ E790C452243498BB006FFB26 /* BugsnagStackframe.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagStackframe.m; path = ../Source/BugsnagStackframe.m; sourceTree = ""; }; E790C45624349A28006FFB26 /* BugsnagThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BugsnagThread.h; path = ../Source/BugsnagThread.h; sourceTree = ""; }; E790C45724349A28006FFB26 /* BugsnagThread.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagThread.m; path = ../Source/BugsnagThread.m; sourceTree = ""; }; + E790C49A2434C8C8006FFB26 /* BugsnagAppTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppTest.m; path = ../../Tests/BugsnagAppTest.m; sourceTree = ""; }; E794E8021F9F743D00A67EE7 /* BugsnagKeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BugsnagKeys.h; path = ../Source/BugsnagKeys.h; sourceTree = SOURCE_ROOT; }; E79FEBE61F4CB1320048FAD6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E7AB4B9B2423E16C004F015A /* BugsnagOnBreadcrumbTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagOnBreadcrumbTest.m; path = ../../Tests/BugsnagOnBreadcrumbTest.m; sourceTree = ""; }; @@ -693,7 +694,6 @@ F42954B7D892334E7551F0F3 /* RegisterErrorDataTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegisterErrorDataTest.m; sourceTree = ""; }; F42955025DBE1DCEFD928CAA /* BugsnagSessionFileStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagSessionFileStore.h; path = ../Source/BugsnagSessionFileStore.h; sourceTree = SOURCE_ROOT; }; F429551527EAE3AFE1F605FE /* BugsnagThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagThreadTest.m; sourceTree = ""; }; - F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagKSCrashSysInfoParserTest.m; sourceTree = ""; }; F42958B2E67C338E3086EAC2 /* BugsnagFileStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagFileStore.m; path = ../Source/BugsnagFileStore.m; sourceTree = SOURCE_ROOT; }; F4295E2778677786239F2B28 /* BugsnagApiClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagApiClient.m; path = ../Source/BugsnagApiClient.m; sourceTree = SOURCE_ROOT; }; F4295FBD23F478FC6216A006 /* BugsnagSessionTrackingApiClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagSessionTrackingApiClient.m; path = ../Source/BugsnagSessionTrackingApiClient.m; sourceTree = SOURCE_ROOT; }; @@ -875,7 +875,6 @@ 8A2C8F8C1C6BBFDD00846019 /* BugsnagEventTests.m */, 4B47970922A9AE1F00FF9C2E /* BugsnagEventFromKSCrashReportTest.m */, E77316E11F73B46600A14F06 /* BugsnagHandledStateTest.m */, - F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */, 009131BB23F46279000810D9 /* BugsnagMetadataTests.m */, E78C1EFB1FCC759B00B976D3 /* BugsnagSessionTest.m */, E78C1EF01FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m */, @@ -899,6 +898,7 @@ E72AE1FF241A57B100ED8972 /* BugsnagPluginTest.m */, E7AB4B9B2423E16C004F015A /* BugsnagOnBreadcrumbTest.m */, E790C41D24323102006FFB26 /* BugsnagClientMirrorTest.m */, + E790C49A2434C8C8006FFB26 /* BugsnagAppTest.m */, ); name = Tests; path = BugsnagTests; @@ -1410,6 +1410,7 @@ 4B775FCF22CBDEB4004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */, E72AE200241A57B100ED8972 /* BugsnagPluginTest.m in Sources */, E733A7681FD7091F003EAA29 /* KSCrashSentry_NSException_Tests.m in Sources */, + E790C49B2434C8C8006FFB26 /* BugsnagAppTest.m in Sources */, E7B970311FD702DA00590C27 /* KSLogger_Tests.m in Sources */, 8AA661AD23D8C1F50031ECC8 /* BSGConnectivityTest.m in Sources */, E70EE0921FD706C700FA745C /* KSDynamicLinker_Tests.m in Sources */, @@ -1436,7 +1437,6 @@ E70EE0871FD7047800FA745C /* KSSysCtl_Tests.m in Sources */, E78C1EF31FCC615400B976D3 /* BugsnagSessionTrackingPayloadTest.m in Sources */, E78C1EF11FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m in Sources */, - F4295995C3259BF7D9730BC4 /* BugsnagKSCrashSysInfoParserTest.m in Sources */, E70E52152216E41C00A590AB /* BugsnagSessionTrackerStopTest.m in Sources */, 00F9393923FC4F64008C7073 /* BugsnagTestsDummyClass.m in Sources */, F4295F017754324FD52CCE46 /* RegisterErrorDataTest.m in Sources */, diff --git a/iOS/Bugsnag.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iOS/Bugsnag.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 94b2795e2..919434a62 100644 --- a/iOS/Bugsnag.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/iOS/Bugsnag.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,4 +1,7 @@ + + diff --git a/iOS/BugsnagTests/BugsnagEventFromKSCrashReportTest.m b/iOS/BugsnagTests/BugsnagEventFromKSCrashReportTest.m index e9f89b898..e0ca6f158 100644 --- a/iOS/BugsnagTests/BugsnagEventFromKSCrashReportTest.m +++ b/iOS/BugsnagTests/BugsnagEventFromKSCrashReportTest.m @@ -46,7 +46,7 @@ - (void)testReportDepth { } - (void)testReadReleaseStage { - XCTAssertEqualObjects(self.report.releaseStage, @"production"); + XCTAssertEqualObjects(self.report.app.releaseStage, @"production"); } - (void)testReadEnabledReleaseStages { diff --git a/iOS/BugsnagTests/BugsnagKSCrashSysInfoParserTest.m b/iOS/BugsnagTests/BugsnagKSCrashSysInfoParserTest.m deleted file mode 100644 index bca0d3d54..000000000 --- a/iOS/BugsnagTests/BugsnagKSCrashSysInfoParserTest.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// BugsnagKSCrashSysInfoParserTestTest.m -// Tests -// -// Created by Jamie Lynch on 15/05/2018. -// Copyright © 2018 Bugsnag. All rights reserved. -// - -@import XCTest; - -#import "BugsnagKSCrashSysInfoParser.h" -NSNumber * _Nullable BSGDeviceFreeSpace(NSSearchPathDirectory directory); - -@interface BugsnagKSCrashSysInfoParserTest : XCTestCase -@end - -@implementation BugsnagKSCrashSysInfoParserTest - -- (void)testEmptyDictSerialisation { - // ensures that an empty dictionary parameter returns a fallback dictionary populated with at least some information - NSDictionary *device = BSGParseDevice(@{}); - [self validateDeviceDict:device]; -} - - -- (void)testNilDictSerialisation { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" - NSDictionary *device = BSGParseDevice(nil); -#pragma clang diagnostic pop - [self validateDeviceDict:device]; -} - -- (void)validateDeviceDict:(NSDictionary *)device { - XCTAssertNotNil(device); - XCTAssertNotNil(device[@"locale"]); - XCTAssertNotNil(device[@"freeDisk"]); - - #if TARGET_OS_SIMULATOR - XCTAssertNotNil(device[@"simulator"]); - #elif TARGET_OS_IPHONE || TARGET_OS_TV - XCTAssertNil(device[@"simulator"]); - #endif -} - -- (void)testDeviceFreeSpaceShouldBeLargeNumber { - NSNumber *freeBytes = BSGDeviceFreeSpace(NSCachesDirectory); - XCTAssertNotNil(freeBytes, @"expect a valid number for successful call to retrieve free space"); - XCTAssertGreaterThan([freeBytes integerValue], 1000, @"expect at least 1k of free space on test device"); -} - -- (void)testDeviceFreeSpaceShouldBeNilWhenFailsToRetrieveIt { - NSSearchPathDirectory notAccessibleDirectory = NSAdminApplicationDirectory; - NSNumber *freeBytes = BSGDeviceFreeSpace(notAccessibleDirectory); - XCTAssertNil(freeBytes, @"expect nil when fails to retrieve free space for the directory"); -} - -- (void)testParseAppInfo { - NSDictionary *rawInfo = @{ - @"CFBundleShortVersionString":@"4.1.1", - @"CFBundleVersion":@"4.1.1.2362", - @"extra":@"foo", - }; - NSDictionary *state = BSGParseAppState(rawInfo, nil, @"prod", nil); - XCTAssertEqual(state.count, 5); - XCTAssertEqualObjects(state[@"releaseStage"], @"prod"); - XCTAssertEqualObjects(state[@"version"], @"4.1.1"); - XCTAssertEqualObjects(state[@"bundleVersion"], @"4.1.1.2362"); - XCTAssertEqual(state[@"codeBundleId"], [NSNull null]); -#if TARGET_OS_TV - XCTAssertEqualObjects(state[@"type"], @"tvOS"); -#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - XCTAssertEqualObjects(state[@"type"], @"iOS"); -#elif TARGET_OS_MAC - XCTAssertEqualObjects(state[@"type"], @"macOS"); -#endif -} - -- (void)testParseAppInfoPreferredValues { - NSDictionary *rawInfo = @{ - @"CFBundleShortVersionString":@"4.1.1", - @"CFBundleVersion":@"4.1.1.2362", - @"extra":@"foo", - }; - NSDictionary *state = BSGParseAppState(rawInfo, @"2.0", @"prod", @"4.2.0-cbd"); - XCTAssertEqual(state.count, 5); - XCTAssertEqualObjects(state[@"releaseStage"], @"prod"); - XCTAssertEqualObjects(state[@"version"], @"2.0"); - XCTAssertEqualObjects(state[@"bundleVersion"], @"4.1.1.2362"); - XCTAssertEqualObjects(state[@"codeBundleId"], @"4.2.0-cbd"); -#if TARGET_OS_TV - XCTAssertEqualObjects(state[@"type"], @"tvOS"); -#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - XCTAssertEqualObjects(state[@"type"], @"iOS"); -#elif TARGET_OS_MAC - XCTAssertEqualObjects(state[@"type"], @"macOS"); -#endif -} - -@end diff --git a/tvOS/Bugsnag.xcodeproj/project.pbxproj b/tvOS/Bugsnag.xcodeproj/project.pbxproj index 79d503da3..ad582d9d4 100644 --- a/tvOS/Bugsnag.xcodeproj/project.pbxproj +++ b/tvOS/Bugsnag.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 00D7ACAB23E98A9A00FBE4A7 /* BugsnagEventFromKSCrashReportTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D7ACA923E98A9A00FBE4A7 /* BugsnagEventFromKSCrashReportTest.m */; }; 00F9393623FC16DA008C7073 /* BugsnagBaseUnitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F9393523FC16DA008C7073 /* BugsnagBaseUnitTest.m */; }; 00F9393F23FD2DDB008C7073 /* BugsnagTestsDummyClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F9393E23FD2DDB008C7073 /* BugsnagTestsDummyClass.m */; }; - 4B3CD2B622C5625800DBFF33 /* BugsnagKSCrashSysInfoParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CD2B522C5625800DBFF33 /* BugsnagKSCrashSysInfoParserTest.m */; }; 4B3CD2BB22C5676800DBFF33 /* BSGOutOfMemoryWatchdogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CD2B722C5676700DBFF33 /* BSGOutOfMemoryWatchdogTests.m */; }; 4B3CD2BD22C5676800DBFF33 /* BugsnagThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CD2B922C5676700DBFF33 /* BugsnagThreadTest.m */; }; 4B3CD2BE22C5676800DBFF33 /* RegisterErrorDataTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3CD2BA22C5676800DBFF33 /* RegisterErrorDataTest.m */; }; @@ -161,6 +160,7 @@ E790C49724349D35006FFB26 /* BugsnagDeviceWithState.h in Headers */ = {isa = PBXBuildFile; fileRef = E790C48924349D34006FFB26 /* BugsnagDeviceWithState.h */; settings = {ATTRIBUTES = (Public, ); }; }; E790C49824349D35006FFB26 /* BugsnagDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C48A24349D34006FFB26 /* BugsnagDevice.m */; }; E790C49924349D35006FFB26 /* BugsnagAppWithState.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C48B24349D35006FFB26 /* BugsnagAppWithState.m */; }; + E790C4A12434CB5B006FFB26 /* BugsnagAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C4A02434CB5B006FFB26 /* BugsnagAppTest.m */; }; E79148771FD82E6D003EFEBF /* BugsnagSession.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148641FD82E6A003EFEBF /* BugsnagSession.h */; }; E79148781FD82E6D003EFEBF /* BugsnagKSCrashSysInfoParser.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148651FD82E6A003EFEBF /* BugsnagKSCrashSysInfoParser.h */; }; E79148791FD82E6D003EFEBF /* BugsnagUser.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148661FD82E6A003EFEBF /* BugsnagUser.h */; }; @@ -233,7 +233,6 @@ 00F9393523FC16DA008C7073 /* BugsnagBaseUnitTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagBaseUnitTest.m; path = ../../Tests/BugsnagBaseUnitTest.m; sourceTree = ""; }; 00F9393D23FD2DDB008C7073 /* BugsnagTestsDummyClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagTestsDummyClass.h; path = ../../Tests/BugsnagTestsDummyClass.h; sourceTree = ""; }; 00F9393E23FD2DDB008C7073 /* BugsnagTestsDummyClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagTestsDummyClass.m; path = ../../Tests/BugsnagTestsDummyClass.m; sourceTree = ""; }; - 4B3CD2B522C5625800DBFF33 /* BugsnagKSCrashSysInfoParserTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagKSCrashSysInfoParserTest.m; path = ../../iOS/BugsnagTests/BugsnagKSCrashSysInfoParserTest.m; sourceTree = ""; }; 4B3CD2B722C5676700DBFF33 /* BSGOutOfMemoryWatchdogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BSGOutOfMemoryWatchdogTests.m; path = ../../iOS/BugsnagTests/BSGOutOfMemoryWatchdogTests.m; sourceTree = ""; }; 4B3CD2B922C5676700DBFF33 /* BugsnagThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThreadTest.m; path = ../../iOS/BugsnagTests/BugsnagThreadTest.m; sourceTree = ""; }; 4B3CD2BA22C5676800DBFF33 /* RegisterErrorDataTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegisterErrorDataTest.m; path = ../../iOS/BugsnagTests/RegisterErrorDataTest.m; sourceTree = ""; }; @@ -379,6 +378,7 @@ E790C48924349D34006FFB26 /* BugsnagDeviceWithState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagDeviceWithState.h; path = ../Source/BugsnagDeviceWithState.h; sourceTree = ""; }; E790C48A24349D34006FFB26 /* BugsnagDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagDevice.m; path = ../Source/BugsnagDevice.m; sourceTree = ""; }; E790C48B24349D35006FFB26 /* BugsnagAppWithState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppWithState.m; path = ../Source/BugsnagAppWithState.m; sourceTree = ""; }; + E790C4A02434CB5B006FFB26 /* BugsnagAppTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppTest.m; path = ../../Tests/BugsnagAppTest.m; sourceTree = ""; }; E79148641FD82E6A003EFEBF /* BugsnagSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagSession.h; path = ../Source/BugsnagSession.h; sourceTree = ""; }; E79148651FD82E6A003EFEBF /* BugsnagKSCrashSysInfoParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagKSCrashSysInfoParser.h; path = ../Source/BugsnagKSCrashSysInfoParser.h; sourceTree = ""; }; E79148661FD82E6A003EFEBF /* BugsnagUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagUser.h; path = ../Source/BugsnagUser.h; sourceTree = ""; }; @@ -563,6 +563,7 @@ 8AB151131D41356800C9B218 /* Tests */ = { isa = PBXGroup; children = ( + E790C4A02434CB5B006FFB26 /* BugsnagAppTest.m */, E790C421243231EA006FFB26 /* BugsnagClientMirrorTest.m */, 00F9393D23FD2DDB008C7073 /* BugsnagTestsDummyClass.h */, 00F9393E23FD2DDB008C7073 /* BugsnagTestsDummyClass.m */, @@ -586,7 +587,6 @@ 8AB1511F1D41361700C9B218 /* BugsnagSinkTests.m */, 8AB151201D41361700C9B218 /* report.json */, 8AB151161D41356800C9B218 /* TestsInfo.plist */, - 4B3CD2B522C5625800DBFF33 /* BugsnagKSCrashSysInfoParserTest.m */, 4B3CD2B722C5676700DBFF33 /* BSGOutOfMemoryWatchdogTests.m */, 4B3CD2B922C5676700DBFF33 /* BugsnagThreadTest.m */, 4B3CD2BA22C5676800DBFF33 /* RegisterErrorDataTest.m */, @@ -1054,6 +1054,7 @@ buildActionMask = 2147483647; files = ( E77526C7242E694C0077A42F /* BugsnagOnBreadcrumbTest.m in Sources */, + E790C4A12434CB5B006FFB26 /* BugsnagAppTest.m in Sources */, 8ACF0F74201812AD00173809 /* BugsnagSinkTests.m in Sources */, 4B3CD2BD22C5676800DBFF33 /* BugsnagThreadTest.m in Sources */, 00D7ACA723E98A5D00FBE4A7 /* BugsnagEventTests.m in Sources */, @@ -1073,7 +1074,6 @@ E7CE78F61FD94F1B001D07E0 /* FileBasedTestCase.m in Sources */, E7CE79031FD94F1B001D07E0 /* KSCrashReportConverter_Tests.m in Sources */, E79148911FD82E77003EFEBF /* BugsnagUserTest.m in Sources */, - 4B3CD2B622C5625800DBFF33 /* BugsnagKSCrashSysInfoParserTest.m in Sources */, E791488E1FD82E77003EFEBF /* BugsnagSessionTrackerTest.m in Sources */, E79148901FD82E77003EFEBF /* BugsnagSessionTest.m in Sources */, 8AB151211D41361700C9B218 /* BugsnagBreadcrumbsTest.m in Sources */,