diff --git a/CHANGELOG.md b/CHANGELOG.md index a24c53abb..1b75c7ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Changelog ========= +## TBD + +### Bug fixes + +* Fix trimming the stacktraces of handled error/exceptions using the + [`depth`](https://docs.bugsnag.com/platforms/ios/reporting-handled-exceptions/#depth) + property. + [Paul Zabelin](https://github.com/paulz) + [#363](https://github.com/bugsnag/bugsnag-cocoa/pull/363) + + ## 5.22.1 (2019-05-21) * Report correct app version in out-of-memory reports. Previously the bundle diff --git a/Source/BugsnagCrashReport.m b/Source/BugsnagCrashReport.m index 97a316b66..6bb2ad604 100644 --- a/Source/BugsnagCrashReport.m +++ b/Source/BugsnagCrashReport.m @@ -290,7 +290,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report [[BugsnagHandledState alloc] initWithDictionary:recordedState]; // only makes sense to use serialised value for handled exceptions - _depth = [[report valueForKeyPath:@"user.state.crash.depth"] + _depth = [[report valueForKeyPath:@"user.depth"] unsignedIntegerValue]; } else if (_errorType != nil) { // the event was unhandled. BOOL isSignal = [BSGKeySignal isEqualToString:_errorType]; diff --git a/Tests/BugsnagConfigurationSpec.m b/Tests/BugsnagConfigurationSpec.m deleted file mode 100644 index 24044c7a9..000000000 --- a/Tests/BugsnagConfigurationSpec.m +++ /dev/null @@ -1,27 +0,0 @@ -// -// BugsnagConfigurationSpec.m -// Bugsnag -// -// Created by Delisa Mason on 11/30/16. -// Copyright 2016 Bugsnag. All rights reserved. -// -#import "Bugsnag.h" - -@interface SomeDelegate : NSObject -@property(nonatomic) BOOL didInvoke; -@end - -@implementation SomeDelegate - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didReceiveChallenge:(nonnull NSURLAuthenticationChallenge *)challenge - completionHandler: -(nonnull void (^)(NSURLSessionAuthChallengeDisposition, - NSURLCredential *_Nullable))completionHandler { - self.didInvoke = YES; - completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, - nil); -} - -@end diff --git a/Tests/BugsnagCrashReportTests.m b/Tests/BugsnagCrashReportTests.m index 7b5f8068e..bdba53561 100644 --- a/Tests/BugsnagCrashReportTests.m +++ b/Tests/BugsnagCrashReportTests.m @@ -16,88 +16,10 @@ @interface BugsnagCrashReportTests : XCTestCase -@property BugsnagCrashReport *report; @end @implementation BugsnagCrashReportTests -- (void)setUp { - [super setUp]; - NSBundle *bundle = [NSBundle bundleForClass:[self class]]; - NSString *path = [bundle pathForResource:@"report" ofType:@"json"]; - NSString *contents = [NSString stringWithContentsOfFile:path - encoding:NSUTF8StringEncoding - error:nil]; - NSDictionary *dictionary = [NSJSONSerialization - JSONObjectWithData:[contents dataUsingEncoding:NSUTF8StringEncoding] - options:0 - error:nil]; - self.report = [[BugsnagCrashReport alloc] initWithKSReport:dictionary]; -} - -- (void)tearDown { - [super tearDown]; - self.report = nil; -} - -- (void)testReadReleaseStage { - XCTAssertEqualObjects(self.report.releaseStage, @"production"); -} - -- (void)testReadNotifyReleaseStages { - XCTAssertEqualObjects(self.report.notifyReleaseStages, - (@[ @"production", @"development" ])); -} - -- (void)testReadNotifyReleaseStagesSends { - XCTAssertTrue([self.report shouldBeSent]); -} - -- (void)testAddMetadataAddsNewTab { - NSDictionary *metadata = @{@"color" : @"blue", @"beverage" : @"tea"}; - [self.report addMetadata:metadata toTabWithName:@"user prefs"]; - NSDictionary *prefs = self.report.metaData[@"user prefs"]; - XCTAssertEqual(@"blue", prefs[@"color"]); - XCTAssertEqual(@"tea", prefs[@"beverage"]); - XCTAssert([prefs count] == 2); -} - -- (void)testAddMetadataMergesExistingTab { - NSDictionary *oldMetadata = @{@"color" : @"red", @"food" : @"carrots"}; - [self.report addMetadata:oldMetadata toTabWithName:@"user prefs"]; - NSDictionary *metadata = @{@"color" : @"blue", @"beverage" : @"tea"}; - [self.report addMetadata:metadata toTabWithName:@"user prefs"]; - NSDictionary *prefs = self.report.metaData[@"user prefs"]; - XCTAssertEqual(@"blue", prefs[@"color"]); - XCTAssertEqual(@"tea", prefs[@"beverage"]); - XCTAssertEqual(@"carrots", prefs[@"food"]); - XCTAssert([prefs count] == 3); -} - -- (void)testAddAttributeAddsNewTab { - [self.report addAttribute:@"color" - withValue:@"blue" - toTabWithName:@"prefs"]; - NSDictionary *prefs = self.report.metaData[@"prefs"]; - XCTAssertEqual(@"blue", prefs[@"color"]); -} - -- (void)testAddAttributeOverridesExistingValue { - [self.report addAttribute:@"color" withValue:@"red" toTabWithName:@"prefs"]; - [self.report addAttribute:@"color" - withValue:@"blue" - toTabWithName:@"prefs"]; - NSDictionary *prefs = self.report.metaData[@"prefs"]; - XCTAssertEqual(@"blue", prefs[@"color"]); -} - -- (void)testAddAttributeRemovesValue { - [self.report addAttribute:@"color" withValue:@"red" toTabWithName:@"prefs"]; - [self.report addAttribute:@"color" withValue:nil toTabWithName:@"prefs"]; - NSDictionary *prefs = self.report.metaData[@"prefs"]; - XCTAssertNil(prefs[@"color"]); -} - - (void)testNotifyReleaseStagesSendsFromConfig { BugsnagConfiguration *config = [BugsnagConfiguration new]; config.notifyReleaseStages = @[ @"foo" ]; @@ -496,7 +418,7 @@ - (void)testEmptyReport { - (void)testUnhandledReportDepth { // unhandled reports should calculate their own depth - NSDictionary *dict = @{@"user.state.crash.depth": @2}; + NSDictionary *dict = @{@"user.depth": @2}; BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:dict]; XCTAssertEqual(report.depth, 0); } @@ -504,7 +426,7 @@ - (void)testUnhandledReportDepth { - (void)testHandledReportDepth { // handled reports should use the serialised depth BugsnagHandledState *state = [BugsnagHandledState handledStateWithSeverityReason:HandledException]; - NSDictionary *dict = @{@"user.state.crash.depth": @2, @"user.handledState": [state toJson]}; + NSDictionary *dict = @{@"user.depth": @2, @"user.handledState": [state toJson]}; BugsnagCrashReport *report = [[BugsnagCrashReport alloc] initWithKSReport:dict]; XCTAssertEqual(report.depth, 2); } @@ -553,12 +475,6 @@ - (void)testNoReportMetaData { XCTAssertEqual(report.metaData.count, 0); } -- (void)testAppVersion { - NSDictionary *dictionary = [self.report toJson]; - XCTAssertEqualObjects(@"1.0", dictionary[@"app"][@"version"]); - XCTAssertEqualObjects(@"1", dictionary[@"app"][@"bundleVersion"]); -} - - (void)testAppVersionOverride { BugsnagCrashReport *overrideReport = [[BugsnagCrashReport alloc] initWithKSReport:@{ @"system" : @{ diff --git a/Tests/BugsnagSinkTests.m b/Tests/BugsnagSinkTests.m index f046336c1..63478ac64 100644 --- a/Tests/BugsnagSinkTests.m +++ b/Tests/BugsnagSinkTests.m @@ -125,8 +125,8 @@ - (void)testEventSeverity { XCTAssertNotNil(event); NSString *severity = event[@"severity"]; - XCTAssertTrue([event[@"unhandled"] boolValue]); - XCTAssertEqualObjects(severity, @"error"); + XCTAssertFalse([event[@"unhandled"] boolValue]); + XCTAssertEqualObjects(severity, @"info"); } - (void)testEventBreadcrumbs { diff --git a/Tests/report.json b/Tests/report.json index 68178989b..f3b14ad66 100644 --- a/Tests/report.json +++ b/Tests/report.json @@ -2081,6 +2081,13 @@ "key": "value" } }, + "handledState":{ + "unhandled":false, + "currentSeverity":"info", + "severityReasonType":"handledError", + "originalSeverity":"warning" + }, + "depth":7, "config": { "releaseStage": "production", "notifyReleaseStages": ["production", "development"], diff --git a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift index 4faaa982e..2a90c899c 100644 --- a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift +++ b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift @@ -7,7 +7,9 @@ import Foundation import Bugsnag /** - * Sends a handled Error to Bugsnag and overrides the exception name + message + Sends a handled Error to Bugsnag and overrides the exception name + message + Demonstrates adjusting report depth to exclude common error handling code from grouping + See: https://docs.bugsnag.com/platforms/ios-objc/reporting-handled-exceptions/#depth */ class HandledErrorOverrideScenario: Scenario { @@ -16,12 +18,21 @@ class HandledErrorOverrideScenario: Scenario { super.startBugsnag() } - override func run() { - let error = NSError(domain: "HandledErrorOverrideScenario", code: 100, userInfo: nil) - Bugsnag.notifyError(error, block: { report in + fileprivate func logError(_ error: Error) { + Bugsnag.notifyError(error) { report in report.errorMessage = "Foo" report.errorClass = "Bar" - }) + report.depth += 2 + } + } + + private func handleError(_ error: NSError) { + logError(error) + } + + override func run() { + let error = NSError(domain: "HandledErrorOverrideScenario", code: 100, userInfo: nil) + handleError(error) } } diff --git a/features/handled_errors.feature b/features/handled_errors.feature index a67ae052a..b0cd26c15 100644 --- a/features/handled_errors.feature +++ b/features/handled_errors.feature @@ -1,13 +1,19 @@ Feature: Handled Errors and Exceptions -Scenario: Override errorClass and message from a notifyError() callback +Scenario: Override errorClass and message from a notifyError() callback and discard lines from stack + + Discard 2 lines from the stacktrace, as we have single place to report and log errors, see + https://docs.bugsnag.com/platforms/ios-objc/reporting-handled-exceptions/#depth + This way top of the stacktrace is not logError but run + When I run "HandledErrorOverrideScenario" Then I should receive a request And the request is a valid for the error reporting API And the exception "errorClass" equals "Bar" And the exception "message" equals "Foo" And the event "device.time" is within 30 seconds of the current timestamp - And the stack trace is an array with 22 stack frames + And the "method" of stack frame 0 demangles to "iOSTestApp.HandledErrorOverrideScenario.run() -> ()" + And the stack trace is an array with 15 stack frames Scenario: Reporting an NSError When I run "HandledErrorScenario" @@ -18,7 +24,7 @@ Scenario: Reporting an NSError And the payload field "events" is an array with 1 element And the exception "errorClass" equals "NSError" And the exception "message" equals "The operation couldn’t be completed. (HandledErrorScenario error 100.)" - And the stack trace is an array with 22 stack frames + And the stack trace is an array with 15 stack frames Scenario: Reporting a handled exception When I run "HandledExceptionScenario" @@ -29,7 +35,7 @@ Scenario: Reporting a handled exception And the payload field "events" is an array with 1 element And the exception "errorClass" equals "HandledExceptionScenario" And the exception "message" equals "Message: HandledExceptionScenario" - And the stack trace is an array with 22 stack frames + And the stack trace is an array with 15 stack frames Scenario: Reporting a handled exception's stacktrace When I run "NSExceptionShiftScenario" diff --git a/features/oom.feature b/features/out_of_memory.feature similarity index 100% rename from features/oom.feature rename to features/out_of_memory.feature diff --git a/features/steps/crash_assertion_steps.rb b/features/steps/crash_assertion_steps.rb index f588045c1..418442fb9 100644 --- a/features/steps/crash_assertion_steps.rb +++ b/features/steps/crash_assertion_steps.rb @@ -22,17 +22,17 @@ def assert_exception_matches_12_1(exception, stacktrace) case stacktrace.first["method"] when "__pthread_kill" - assert_equal(exception["errorClass"], "SIGABRT") - assert_equal(stacktrace[1]["method"], "abort") + assert_equal("SIGABRT", exception["errorClass"]) + assert_equal("abort", stacktrace[1]["method"]) when "nanov2_allocate_from_block" - assert_equal(exception["errorClass"], "EXC_BAD_INSTRUCTION") - assert_equal(stacktrace[1]["method"], "nanov2_allocate") - assert_equal(stacktrace[15]["method"], "NSLog") - assert_equal(stacktrace[16]["method"], "-[CorruptMallocScenario run]") + assert_equal("EXC_BAD_INSTRUCTION", exception["errorClass"]) + assert_equal("nanov2_allocate", stacktrace[1]["method"]) + assert_equal("NSLog", stacktrace[15]["method"]) + assert_equal("-[CorruptMallocScenario run]", stacktrace[16]["method"]) when "notify_dump_status" - assert_equal(exception["errorClass"], "EXC_BAD_ACCESS") - assert_equal(stacktrace[10]["method"], "NSLog") - assert_equal(stacktrace[11]["method"], "-[CorruptMallocScenario run]") + assert_equal("EXC_BAD_ACCESS", exception["errorClass"]) + assert_equal("NSLog", stacktrace[10]["method"]) + assert_equal("-[CorruptMallocScenario run]", stacktrace[11]["method"]) else fail("The exception does not reflect malloc corruption") end @@ -58,16 +58,16 @@ def assert_exception_matches_11_2(exception, stacktrace) frame = 2 end - assert_equal(stacktrace[frame]["method"], "notify_check") - assert_equal(stacktrace[frame + 1]["method"], "notify_check_tz") - assert_equal(stacktrace[frame + 2]["method"], "tzsetwall_basic") - assert_equal(stacktrace[frame + 3]["method"], "localtime_r") - assert_equal(stacktrace[frame + 4]["method"], "_populateBanner") - assert_equal(stacktrace[frame + 5]["method"], "_CFLogvEx2Predicate") - assert_equal(stacktrace[frame + 6]["method"], "_CFLogvEx3") - assert_equal(stacktrace[frame + 7]["method"], "_NSLogv") - assert_equal(stacktrace[frame + 8]["method"], "NSLog") - assert_equal(stacktrace[frame + 9]["method"], "-[CorruptMallocScenario run]") + assert_equal("notify_check", stacktrace[frame]["method"]) + assert_equal("notify_check_tz", stacktrace[frame + 1]["method"]) + assert_equal("tzsetwall_basic", stacktrace[frame + 2]["method"]) + assert_equal("localtime_r", stacktrace[frame + 3]["method"]) + assert_equal("_populateBanner", stacktrace[frame + 4]["method"]) + assert_equal("_CFLogvEx2Predicate", stacktrace[frame + 5]["method"]) + assert_equal("_CFLogvEx3", stacktrace[frame + 6]["method"]) + assert_equal("_NSLogv", stacktrace[frame + 7]["method"]) + assert_equal("NSLog", stacktrace[frame + 8]["method"]) + assert_equal("-[CorruptMallocScenario run]", stacktrace[frame + 9]["method"]) else fail("The exception does not reflect malloc corruption") end diff --git a/features/steps/ios_steps.rb b/features/steps/ios_steps.rb index b32b6415b..99e74afb1 100644 --- a/features/steps/ios_steps.rb +++ b/features/steps/ios_steps.rb @@ -120,9 +120,15 @@ assert_not_nil(match, "No crumb matches the provided message") end +Then("the {string} of stack frame {int} demangles to {string}") do |field, frame_index, expected_value| + value = read_key_path(find_request(0)[:body], "events.0.exceptions.0.stacktrace.#{frame_index}.#{field}") + demangled_value = `xcrun swift-demangle -compact '#{value}'`.chomp + assert_equal(expected_value, demangled_value) +end + Then("the stack trace is an array with {int} stack frames") do |expected_length| stack_trace = read_key_path(find_request(0)[:body], "events.0.exceptions.0.stacktrace") - assert_equal(stack_trace.length, expected_length) + assert_equal(expected_length, stack_trace.length) end Then("the payload field {string} equals the device version") do |field| value = read_key_path(find_request(0)[:body], field) diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index 1eb6f4542..5a67dd14e 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4B47970A22A9AE1F00FF9C2E /* BugsnagCrashReportFromKSCrashReportTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B47970922A9AE1F00FF9C2E /* BugsnagCrashReportFromKSCrashReportTest.m */; }; 8A12006A221C36420008C9C3 /* BSGFilepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A120069221C36420008C9C3 /* BSGFilepathTests.m */; }; 8A2C8F231C6BBD2300846019 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8F181C6BBD2300846019 /* Bugsnag.framework */; }; 8A2C8F4F1C6BBE3C00846019 /* Bugsnag.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A2C8F3D1C6BBE3C00846019 /* Bugsnag.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -38,7 +39,6 @@ 8A70D9CA22539C81006B696F /* BSGOutOfMemoryWatchdog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A70D9C822539C81006B696F /* BSGOutOfMemoryWatchdog.m */; }; 8A70D9CB22539C81006B696F /* BSGOutOfMemoryWatchdog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A70D9C822539C81006B696F /* BSGOutOfMemoryWatchdog.m */; }; 8A70D9CD2253C484006B696F /* BSGOutOfMemoryWatchdogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A70D9CC2253C484006B696F /* BSGOutOfMemoryWatchdogTests.m */; }; - 8AE1BC951DEFCE8B00D16CEF /* BugsnagConfigurationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AE1BC941DEFCE8B00D16CEF /* BugsnagConfigurationSpec.m */; }; E70E52152216E41C00A590AB /* BugsnagSessionTrackerStopTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E70E52142216E41C00A590AB /* BugsnagSessionTrackerStopTest.m */; }; E70EE0781FD7039E00FA745C /* RFC3339DateTool_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E70EE0771FD7039D00FA745C /* RFC3339DateTool_Tests.m */; }; E70EE07E1FD703D600FA745C /* NSError+SimpleConstructor_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E70EE07A1FD703D500FA745C /* NSError+SimpleConstructor_Tests.m */; }; @@ -416,6 +416,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 4B47970922A9AE1F00FF9C2E /* BugsnagCrashReportFromKSCrashReportTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagCrashReportFromKSCrashReportTest.m; sourceTree = ""; }; 8A120069221C36420008C9C3 /* BSGFilepathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BSGFilepathTests.m; path = ../../Tests/BSGFilepathTests.m; sourceTree = ""; }; 8A2C8F181C6BBD2300846019 /* Bugsnag.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bugsnag.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A2C8F1D1C6BBD2300846019 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; @@ -454,7 +455,6 @@ 8A70D9C722539C81006B696F /* BSGOutOfMemoryWatchdog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BSGOutOfMemoryWatchdog.h; path = ../Source/BSGOutOfMemoryWatchdog.h; sourceTree = ""; }; 8A70D9C822539C81006B696F /* BSGOutOfMemoryWatchdog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BSGOutOfMemoryWatchdog.m; path = ../Source/BSGOutOfMemoryWatchdog.m; sourceTree = ""; }; 8A70D9CC2253C484006B696F /* BSGOutOfMemoryWatchdogTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGOutOfMemoryWatchdogTests.m; sourceTree = ""; }; - 8AE1BC941DEFCE8B00D16CEF /* BugsnagConfigurationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagConfigurationSpec.m; path = ../Tests/BugsnagConfigurationSpec.m; sourceTree = SOURCE_ROOT; }; E70E52142216E41C00A590AB /* BugsnagSessionTrackerStopTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagSessionTrackerStopTest.m; path = ../../Tests/BugsnagSessionTrackerStopTest.m; sourceTree = ""; }; E70EE0771FD7039D00FA745C /* RFC3339DateTool_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RFC3339DateTool_Tests.m; path = ../Tests/KSCrash/RFC3339DateTool_Tests.m; sourceTree = SOURCE_ROOT; }; E70EE07A1FD703D500FA745C /* NSError+SimpleConstructor_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSError+SimpleConstructor_Tests.m"; path = "../Tests/KSCrash/NSError+SimpleConstructor_Tests.m"; sourceTree = SOURCE_ROOT; }; @@ -573,14 +573,13 @@ E733A76D1FD709B7003EAA29 /* KSCrashReportStore_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSCrashReportStore_Tests.m; path = ../Tests/KSCrash/KSCrashReportStore_Tests.m; sourceTree = SOURCE_ROOT; }; E737DEA01F73AD7400BC7C80 /* BugsnagHandledState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagHandledState.h; path = ../Source/BugsnagHandledState.h; sourceTree = SOURCE_ROOT; }; E737DEA11F73AD7400BC7C80 /* BugsnagHandledState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledState.m; path = ../Source/BugsnagHandledState.m; sourceTree = SOURCE_ROOT; }; - E7397DB21F83BA410034242A /* Bugsnag copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Bugsnag copy-Info.plist"; path = "/Users/jamielynch/repos/bugsnag-cocoa/iOS/Bugsnag copy-Info.plist"; sourceTree = ""; }; E7397DC41F83BAC50034242A /* libBugsnagStatic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBugsnagStatic.a; sourceTree = BUILT_PRODUCTS_DIR; }; E77316E11F73B46600A14F06 /* BugsnagHandledStateTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagHandledStateTest.m; path = ../Tests/BugsnagHandledStateTest.m; sourceTree = SOURCE_ROOT; }; E784D2511FD70AE6004B01E1 /* KSCrashState_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSCrashState_Tests.m; path = ../Tests/KSCrash/KSCrashState_Tests.m; sourceTree = SOURCE_ROOT; }; E784D2521FD70AE6004B01E1 /* KSMach_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSMach_Tests.m; path = ../Tests/KSCrash/KSMach_Tests.m; sourceTree = SOURCE_ROOT; }; E784D2571FD70BBD004B01E1 /* KSObjC_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSObjC_Tests.m; path = ../Tests/KSCrash/KSObjC_Tests.m; sourceTree = SOURCE_ROOT; }; E784D2591FD70C25004B01E1 /* KSJSONCodec_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSJSONCodec_Tests.m; path = ../Tests/KSCrash/KSJSONCodec_Tests.m; sourceTree = SOURCE_ROOT; }; - E784D25D1FD70E55004B01E1 /* KSString_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSString_Tests.m; path = ../../../Tests/KSCrash/KSString_Tests.m; sourceTree = ""; }; + E784D25D1FD70E55004B01E1 /* KSString_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSString_Tests.m; sourceTree = ""; }; E78C1EF01FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagSessionTrackerTest.m; path = ../Tests/BugsnagSessionTrackerTest.m; sourceTree = SOURCE_ROOT; }; E78C1EF21FCC615400B976D3 /* BugsnagSessionTrackingPayloadTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagSessionTrackingPayloadTest.m; path = ../Tests/BugsnagSessionTrackingPayloadTest.m; sourceTree = SOURCE_ROOT; }; E78C1EF41FCC61EA00B976D3 /* BugsnagSessionTrackingPayload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BugsnagSessionTrackingPayload.h; path = ../Source/BugsnagSessionTrackingPayload.h; sourceTree = SOURCE_ROOT; }; @@ -654,7 +653,6 @@ 8A2C8F261C6BBD2300846019 /* Tests */, 8A2C8F751C6BBEBB00846019 /* Frameworks */, 8A2C8F191C6BBD2300846019 /* Products */, - E7397DB21F83BA410034242A /* Bugsnag copy-Info.plist */, ); sourceTree = ""; }; @@ -733,6 +731,7 @@ E70EE0891FD7047D00FA745C /* KSCrash */, 8A2C8F8B1C6BBFDD00846019 /* BugsnagBreadcrumbsTest.m */, 8A2C8F8C1C6BBFDD00846019 /* BugsnagCrashReportTests.m */, + 4B47970922A9AE1F00FF9C2E /* BugsnagCrashReportFromKSCrashReportTest.m */, 8A4E733E1DC13281001F7CC8 /* BugsnagConfigurationTests.m */, 8A2C8F8D1C6BBFDD00846019 /* BugsnagSinkTests.m */, E77316E11F73B46600A14F06 /* BugsnagHandledStateTest.m */, @@ -800,7 +799,8 @@ E7B970331FD7031500590C27 /* XCTestCase+KSCrash.m */, E7B970301FD702DA00590C27 /* KSLogger_Tests.m */, ); - path = KSCrash; + name = KSCrash; + path = ../../Tests/KSCrash; sourceTree = ""; }; E7107BB31F4C97F100BB3F98 /* KSCrash */ = { @@ -1236,7 +1236,6 @@ E784D2561FD70B3E004B01E1 /* KSMach_Tests.m in Sources */, 8A2C8F8F1C6BBFDD00846019 /* BugsnagBreadcrumbsTest.m in Sources */, E733A76A1FD7091F003EAA29 /* KSCrashSentry_Tests.m in Sources */, - 8AE1BC951DEFCE8B00D16CEF /* BugsnagConfigurationSpec.m in Sources */, E784D25E1FD70E55004B01E1 /* KSString_Tests.m in Sources */, E733A7681FD7091F003EAA29 /* KSCrashSentry_NSException_Tests.m in Sources */, E70EE07F1FD703D600FA745C /* NSDictionary+Merge_Tests.m in Sources */, @@ -1266,6 +1265,7 @@ F4295F017754324FD52CCE46 /* RegisterErrorDataTest.m in Sources */, 8A12006A221C36420008C9C3 /* BSGFilepathTests.m in Sources */, F42952D83435C02F8D891C40 /* BugsnagThreadTest.m in Sources */, + 4B47970A22A9AE1F00FF9C2E /* BugsnagCrashReportFromKSCrashReportTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS/Bugsnag.xcodeproj/xcshareddata/xcschemes/Bugsnag.xcscheme b/iOS/Bugsnag.xcodeproj/xcshareddata/xcschemes/Bugsnag.xcscheme index 4af397f0b..f1aa1af96 100644 --- a/iOS/Bugsnag.xcodeproj/xcshareddata/xcschemes/Bugsnag.xcscheme +++ b/iOS/Bugsnag.xcodeproj/xcshareddata/xcschemes/Bugsnag.xcscheme @@ -24,10 +24,20 @@ + + + + diff --git a/iOS/BugsnagTests/BugsnagCrashReportFromKSCrashReportTest.m b/iOS/BugsnagTests/BugsnagCrashReportFromKSCrashReportTest.m new file mode 100644 index 000000000..9923b3051 --- /dev/null +++ b/iOS/BugsnagTests/BugsnagCrashReportFromKSCrashReportTest.m @@ -0,0 +1,105 @@ +// +// CreateCrashReportTests.m +// Tests +// +// Created by Paul Zabelin on 6/6/19. +// Copyright © 2019 Bugsnag. All rights reserved. +// + +@import XCTest; +@import Bugsnag; + +@interface BugsnagCrashReportFromKSCrashReportTest : XCTestCase +@property BugsnagCrashReport *report; +@end + +@implementation BugsnagCrashReportFromKSCrashReportTest + +- (void)setUp { + [super setUp]; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *path = [bundle pathForResource:@"report" ofType:@"json"]; + NSString *contents = [NSString stringWithContentsOfFile:path + encoding:NSUTF8StringEncoding + error:nil]; + NSDictionary *dictionary = [NSJSONSerialization + JSONObjectWithData:[contents dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:nil]; + self.report = [[BugsnagCrashReport alloc] initWithKSReport:dictionary]; +} + +- (void)tearDown { + [super tearDown]; + self.report = nil; +} + +- (void)testReportDepth { + XCTAssertEqual(7, self.report.depth); +} + +- (void)testReadReleaseStage { + XCTAssertEqualObjects(self.report.releaseStage, @"production"); +} + +- (void)testReadNotifyReleaseStages { + XCTAssertEqualObjects(self.report.notifyReleaseStages, + (@[ @"production", @"development" ])); +} + +- (void)testReadNotifyReleaseStagesSends { + XCTAssertTrue([self.report shouldBeSent]); +} + +- (void)testAddMetadataAddsNewTab { + NSDictionary *metadata = @{@"color" : @"blue", @"beverage" : @"tea"}; + [self.report addMetadata:metadata toTabWithName:@"user prefs"]; + NSDictionary *prefs = self.report.metaData[@"user prefs"]; + XCTAssertEqual(@"blue", prefs[@"color"]); + XCTAssertEqual(@"tea", prefs[@"beverage"]); + XCTAssert([prefs count] == 2); +} + +- (void)testAddMetadataMergesExistingTab { + NSDictionary *oldMetadata = @{@"color" : @"red", @"food" : @"carrots"}; + [self.report addMetadata:oldMetadata toTabWithName:@"user prefs"]; + NSDictionary *metadata = @{@"color" : @"blue", @"beverage" : @"tea"}; + [self.report addMetadata:metadata toTabWithName:@"user prefs"]; + NSDictionary *prefs = self.report.metaData[@"user prefs"]; + XCTAssertEqual(@"blue", prefs[@"color"]); + XCTAssertEqual(@"tea", prefs[@"beverage"]); + XCTAssertEqual(@"carrots", prefs[@"food"]); + XCTAssert([prefs count] == 3); +} + +- (void)testAddAttributeAddsNewTab { + [self.report addAttribute:@"color" + withValue:@"blue" + toTabWithName:@"prefs"]; + NSDictionary *prefs = self.report.metaData[@"prefs"]; + XCTAssertEqual(@"blue", prefs[@"color"]); +} + +- (void)testAddAttributeOverridesExistingValue { + [self.report addAttribute:@"color" withValue:@"red" toTabWithName:@"prefs"]; + [self.report addAttribute:@"color" + withValue:@"blue" + toTabWithName:@"prefs"]; + NSDictionary *prefs = self.report.metaData[@"prefs"]; + XCTAssertEqual(@"blue", prefs[@"color"]); +} + +- (void)testAddAttributeRemovesValue { + [self.report addAttribute:@"color" withValue:@"red" toTabWithName:@"prefs"]; + [self.report addAttribute:@"color" withValue:nil toTabWithName:@"prefs"]; + NSDictionary *prefs = self.report.metaData[@"prefs"]; + XCTAssertNil(prefs[@"color"]); +} + +- (void)testAppVersion { + NSDictionary *dictionary = [self.report toJson]; + XCTAssertEqualObjects(@"1.0", dictionary[@"app"][@"version"]); + XCTAssertEqualObjects(@"1", dictionary[@"app"][@"bundleVersion"]); +} + +@end