diff --git a/.buildkite/pipeline.bb.yml b/.buildkite/pipeline.bb.yml new file mode 100644 index 000000000..9d8549636 --- /dev/null +++ b/.buildkite/pipeline.bb.yml @@ -0,0 +1,53 @@ +steps: + + - label: 'BitBar Poc - iOS 15 - batch 1' + depends_on: "cocoa_fixture" + timeout_in_minutes: 90 + agents: + queue: opensource + plugins: + artifacts#v1.9.0: + download: "features/fixtures/ios/output/iOSTestApp.ipa" + upload: "maze_output/failed/**/*" + docker-compose#v4.7.0: + pull: cocoa-maze-runner-bitbar + run: cocoa-maze-runner-bitbar + service-ports: true + command: +# TODO: Run in a single batch until PLAT-9603 is done +# - "--exclude=features/[e-z].*.feature$" + - "--app=/app/build/iOSTestApp.ipa" + - "--farm=bb" + - "--device=IOS_15" + - "--no-tunnel" + - "--aws-public-ip" + concurrency: 5 + concurrency_group: 'bitbar-ios-15' + concurrency_method: eager + + - label: 'BitBar Poc - iOS 15 - batch 2' + # TODO: Run in a single batch until PLAT-9603 is done + skip: Pending PLAT-9603 + depends_on: "cocoa_fixture" + timeout_in_minutes: 90 + agents: + queue: opensource + plugins: + artifacts#v1.9.0: + download: "features/fixtures/ios/output/iOSTestApp.ipa" + upload: "maze_output/failed/**/*" + docker-compose#v4.7.0: + pull: cocoa-maze-runner-bitbar + run: cocoa-maze-runner-bitbar + service-ports: true + command: + - "--exclude=features/[a-d].*.feature$" + - "--app=/app/build/iOSTestApp.ipa" + - "--farm=bb" + - "--device=IOS_15" + - "--no-tunnel" + - "--aws-public-ip" + concurrency: 5 + concurrency_group: 'bitbar-ios-15' + concurrency_method: eager + diff --git a/.buildkite/pipeline.bs.yml b/.buildkite/pipeline.bs.yml new file mode 100644 index 000000000..9eb3122b1 --- /dev/null +++ b/.buildkite/pipeline.bs.yml @@ -0,0 +1,123 @@ +steps: + + ############################################################################## + # + # Barebones E2E tests + # + + - label: 'iOS 16 E2E tests batch 1' + depends_on: + - cocoa_fixture + timeout_in_minutes: 60 + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/ios/output/ipa_url.txt" + upload: "maze_output/failed/**/*" + docker-compose#v3.7.0: + pull: cocoa-maze-runner + run: cocoa-maze-runner + command: + - "--app=@build/ipa_url.txt" + - "--farm=bs" + - "--device=IOS_16" + - "--appium-version=1.21.0" + - "--fail-fast" + - "--exclude=features/[e-z].*.feature$" + - "--order=random" + concurrency: 24 + concurrency_group: browserstack-app + concurrency_method: eager + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + + - label: 'iOS 16 E2E tests batch 2' + depends_on: + - cocoa_fixture + timeout_in_minutes: 60 + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/ios/output/ipa_url.txt" + upload: "maze_output/failed/**/*" + docker-compose#v3.7.0: + pull: cocoa-maze-runner + run: cocoa-maze-runner + command: + - "--app=@build/ipa_url.txt" + - "--farm=bs" + - "--device=IOS_16" + - "--appium-version=1.21.0" + - "--fail-fast" + - "--exclude=features/[a-d].*.feature$" + - "--order=random" + concurrency: 24 + concurrency_group: browserstack-app + concurrency_method: eager + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + + - label: 'iOS 10 E2E tests batch 1' + depends_on: + - cocoa_fixture + timeout_in_minutes: 60 + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/ios/output/ipa_url.txt" + upload: "maze_output/failed/**/*" + docker-compose#v3.7.0: + pull: cocoa-maze-runner-legacy + run: cocoa-maze-runner-legacy + command: + - "--app=@build/ipa_url.txt" + - "--farm=bs" + - "--device=IOS_10" + - "--appium-version=1.8.0" + - "--capabilities={\"browserstack.networkLogs\":\"true\"}" + - "--fail-fast" + - "--exclude=features/[e-z].*.feature$" + - "--order=random" + concurrency: 24 + concurrency_group: browserstack-app + concurrency_method: eager + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + + - label: 'iOS 10 E2E tests batch 2' + depends_on: + - cocoa_fixture + timeout_in_minutes: 60 + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/ios/output/ipa_url.txt" + upload: "maze_output/failed/**/*" + docker-compose#v3.7.0: + pull: cocoa-maze-runner-legacy + run: cocoa-maze-runner-legacy + command: + - "--app=@build/ipa_url.txt" + - "--farm=bs" + - "--device=IOS_10" + - "--appium-version=1.8.0" + - "--fail-fast" + - "--exclude=features/[a-d].*.feature$" + - "--order=random" + concurrency: 24 + concurrency_group: browserstack-app + concurrency_method: eager + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 65055ddef..c6294f68b 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -73,13 +73,13 @@ steps: queue: opensource plugins: artifacts#v1.5.0: - download: ["features/fixtures/ios/output/iOSTestApp.ipa"] + download: "features/fixtures/ios/output/ipa_url.txt" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: cocoa-maze-runner run: cocoa-maze-runner command: - - "--app=/app/build/iOSTestApp.ipa" + - "--app=@build/ipa_url.txt" - "--farm=bs" - "--device=IOS_14" - "--appium-version=1.21.0" @@ -102,13 +102,13 @@ steps: queue: opensource plugins: artifacts#v1.5.0: - download: ["features/fixtures/ios/output/iOSTestApp.ipa"] + download: "features/fixtures/ios/output/ipa_url.txt" upload: "maze_output/failed/**/*" docker-compose#v3.7.0: pull: cocoa-maze-runner run: cocoa-maze-runner command: - - "--app=/app/build/iOSTestApp.ipa" + - "--app=@build/ipa_url.txt" - "--farm=bs" - "--device=IOS_14" - "--appium-version=1.21.0" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 1130aa3ec..3d2d8fae0 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -201,128 +201,6 @@ steps: artifact_paths: - logs/* - ############################################################################## - # - # Barebones E2E tests - # - - - label: 'iOS 16 E2E tests batch 1' - depends_on: - - cocoa_fixture - timeout_in_minutes: 60 - agents: - queue: opensource - plugins: - artifacts#v1.5.0: - download: "features/fixtures/ios/output/ipa_url.txt" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: cocoa-maze-runner - run: cocoa-maze-runner - command: - - "--app=@build/ipa_url.txt" - - "--farm=bs" - - "--device=IOS_16" - - "--appium-version=1.21.0" - - "--fail-fast" - - "--exclude=features/[e-z].*.feature$" - - "--order=random" - concurrency: 24 - concurrency_group: browserstack-app - concurrency_method: eager - retry: - automatic: - - exit_status: -1 # Agent was lost - limit: 2 - - - label: 'iOS 16 E2E tests batch 2' - depends_on: - - cocoa_fixture - timeout_in_minutes: 60 - agents: - queue: opensource - plugins: - artifacts#v1.5.0: - download: "features/fixtures/ios/output/ipa_url.txt" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: cocoa-maze-runner - run: cocoa-maze-runner - command: - - "--app=@build/ipa_url.txt" - - "--farm=bs" - - "--device=IOS_16" - - "--appium-version=1.21.0" - - "--fail-fast" - - "--exclude=features/[a-d].*.feature$" - - "--order=random" - concurrency: 24 - concurrency_group: browserstack-app - concurrency_method: eager - retry: - automatic: - - exit_status: -1 # Agent was lost - limit: 2 - - - label: 'iOS 10 E2E tests batch 1' - depends_on: - - cocoa_fixture - timeout_in_minutes: 60 - agents: - queue: opensource - plugins: - artifacts#v1.5.0: - download: "features/fixtures/ios/output/ipa_url.txt" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: cocoa-maze-runner-legacy - run: cocoa-maze-runner-legacy - command: - - "--app=@build/ipa_url.txt" - - "--farm=bs" - - "--device=IOS_10" - - "--appium-version=1.8.0" - - "--capabilities={\"browserstack.networkLogs\":\"true\"}" - - "--fail-fast" - - "--exclude=features/[e-z].*.feature$" - - "--order=random" - concurrency: 24 - concurrency_group: browserstack-app - concurrency_method: eager - retry: - automatic: - - exit_status: -1 # Agent was lost - limit: 2 - - - label: 'iOS 10 E2E tests batch 2' - depends_on: - - cocoa_fixture - timeout_in_minutes: 60 - agents: - queue: opensource - plugins: - artifacts#v1.5.0: - download: "features/fixtures/ios/output/ipa_url.txt" - upload: "maze_output/failed/**/*" - docker-compose#v3.7.0: - pull: cocoa-maze-runner-legacy - run: cocoa-maze-runner-legacy - command: - - "--app=@build/ipa_url.txt" - - "--farm=bs" - - "--device=IOS_10" - - "--appium-version=1.8.0" - - "--fail-fast" - - "--exclude=features/[a-d].*.feature$" - - "--order=random" - concurrency: 24 - concurrency_group: browserstack-app - concurrency_method: eager - retry: - automatic: - - exit_status: -1 # Agent was lost - limit: 2 - - label: 'macOS 10.13 barebones E2E tests' depends_on: - cocoa_fixture diff --git a/.buildkite/pipeline_trigger.sh b/.buildkite/pipeline_trigger.sh index c3929d24f..f0e306d20 100755 --- a/.buildkite/pipeline_trigger.sh +++ b/.buildkite/pipeline_trigger.sh @@ -10,3 +10,15 @@ else echo "Running basic build" buildkite-agent pipeline upload .buildkite/block.full.yml fi + +# Run BrowserStack steps unless instructed not to +if [[ "$BUILDKITE_MESSAGE" != *"[nobs]"* && + "$DEVICE_FARM" != *"NO_BS"* ]]; then + buildkite-agent pipeline upload .buildkite/pipeline.bs.yml +fi + +# Run BitBar steps if instructed to +if [[ "$BUILDKITE_MESSAGE" == *"[bb]"* || + "$DEVICE_FARM" == *"BB"* ]]; then + buildkite-agent pipeline upload .buildkite/pipeline.bb.yml +fi diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1759f3e35..1af84fae0 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -14,11 +14,9 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Install dependencies - run: brew install infer oclint && gem install xcpretty + run: brew install oclint && gem install xcpretty - name: Build framework run: make compile_commands.json - - name: Infer - run: make infer - name: OCLint run: make oclint diff --git a/.jazzy.yaml b/.jazzy.yaml index 9f5fa22b2..a0f1294aa 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -2,11 +2,11 @@ author_url: "https://www.bugsnag.com" author: "Bugsnag Inc" clean: false # avoid deleting docs/.git framework_root: "Bugsnag" -github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.25.2/Bugsnag" +github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.26.0/Bugsnag" github_url: "https://github.com/bugsnag/bugsnag-cocoa" hide_documentation_coverage: true module: "Bugsnag" -module_version: "6.25.2" +module_version: "6.26.0" objc: true output: "docs" readme: "README.md" diff --git a/Bugsnag.podspec.json b/Bugsnag.podspec.json index b4acfe478..649de4f17 100644 --- a/Bugsnag.podspec.json +++ b/Bugsnag.podspec.json @@ -1,6 +1,6 @@ { "name": "Bugsnag", - "version": "6.25.2", + "version": "6.26.0", "summary": "The Bugsnag crash reporting framework for Apple platforms.", "homepage": "https://bugsnag.com", "license": "MIT", @@ -9,7 +9,7 @@ }, "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa.git", - "tag": "v6.25.2" + "tag": "v6.26.0" }, "ios": { "frameworks": [ diff --git a/Bugsnag/Bugsnag.m b/Bugsnag/Bugsnag.m index fbe714dc3..da9e7a344 100644 --- a/Bugsnag/Bugsnag.m +++ b/Bugsnag/Bugsnag.m @@ -62,6 +62,10 @@ + (BugsnagClient *_Nonnull)startWithConfiguration:(BugsnagConfiguration *_Nonnul } } ++ (BOOL)isStarted { + return bsg_g_bugsnag_client.isStarted; +} + /** * Purge the global client so that it will be regenerated on the next call to start. * This is only used by the unit tests. @@ -75,51 +79,51 @@ + (BugsnagClient *)client { } + (BOOL)appDidCrashLastLaunch { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [self.client appDidCrashLastLaunch]; } return NO; } + (BugsnagLastRunInfo *)lastRunInfo { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return self.client.lastRunInfo; } return nil; } + (void)markLaunchCompleted { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client markLaunchCompleted]; } } + (void)notify:(NSException *)exception { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client notify:exception]; } } + (void)notify:(NSException *)exception block:(BugsnagOnErrorBlock)block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client notify:exception block:block]; } } + (void)notifyError:(NSError *)error { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client notifyError:error]; } } + (void)notifyError:(NSError *)error block:(BugsnagOnErrorBlock)block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client notifyError:error block:block]; } } -+ (BOOL)bugsnagStarted { - if (!self.client.started) { ++ (BOOL)bugsnagReadyForInternalCalls { + if (!self.client.readyForInternalCalls) { bsg_log_err(@"Ensure you have started Bugsnag with startWithApiKey: " @"before calling any other Bugsnag functions."); @@ -129,14 +133,14 @@ + (BOOL)bugsnagStarted { } + (void)leaveBreadcrumbWithMessage:(NSString *)message { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client leaveBreadcrumbWithMessage:message]; } } + (void)leaveBreadcrumbForNotificationName: (NSString *_Nonnull)notificationName { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client leaveBreadcrumbForNotificationName:notificationName]; } } @@ -145,7 +149,7 @@ + (void)leaveBreadcrumbWithMessage:(NSString *_Nonnull)message metadata:(NSDictionary *_Nullable)metadata andType:(BSGBreadcrumbType)type { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client leaveBreadcrumbWithMessage:message metadata:metadata andType:type]; @@ -154,13 +158,13 @@ + (void)leaveBreadcrumbWithMessage:(NSString *_Nonnull)message + (void)leaveNetworkRequestBreadcrumbForTask:(NSURLSessionTask *)task metrics:(NSURLSessionTaskMetrics *)metrics { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client leaveNetworkRequestBreadcrumbForTask:task metrics:metrics]; } } + (NSArray *_Nonnull)breadcrumbs { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return self.client.breadcrumbs; } else { return @[]; @@ -168,19 +172,19 @@ + (void)leaveNetworkRequestBreadcrumbForTask:(NSURLSessionTask *)task } + (void)startSession { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client startSession]; } } + (void)pauseSession { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client pauseSession]; } } + (BOOL)resumeSession { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [self.client resumeSession]; } else { return false; @@ -192,31 +196,31 @@ + (BOOL)resumeSession { // ============================================================================= + (void)addFeatureFlagWithName:(NSString *)name variant:(nullable NSString *)variant { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client addFeatureFlagWithName:name variant:variant]; } } + (void)addFeatureFlagWithName:(NSString *)name { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client addFeatureFlagWithName:name]; } } + (void)addFeatureFlags:(NSArray *)featureFlags { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client addFeatureFlags:featureFlags]; } } + (void)clearFeatureFlagWithName:(NSString *)name { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client clearFeatureFlagWithName:name]; } } + (void)clearFeatureFlags { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client clearFeatureFlags]; } } @@ -237,7 +241,7 @@ + (void)addMetadata:(id _Nullable)metadata withKey:(NSString *_Nonnull)key toSection:(NSString *_Nonnull)section { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client addMetadata:metadata withKey:key toSection:section]; @@ -247,7 +251,7 @@ + (void)addMetadata:(id _Nullable)metadata + (void)addMetadata:(id _Nonnull)metadata toSection:(NSString *_Nonnull)section { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client addMetadata:metadata toSection:section]; } @@ -255,7 +259,7 @@ + (void)addMetadata:(id _Nonnull)metadata + (NSMutableDictionary *)getMetadataFromSection:(NSString *)section { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [[self.client getMetadataFromSection:section] mutableCopy]; } return nil; @@ -264,7 +268,7 @@ + (NSMutableDictionary *)getMetadataFromSection:(NSString *)section + (id _Nullable )getMetadataFromSection:(NSString *_Nonnull)section withKey:(NSString *_Nonnull)key { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [[self.client getMetadataFromSection:section withKey:key] mutableCopy]; } return nil; @@ -272,7 +276,7 @@ + (id _Nullable )getMetadataFromSection:(NSString *_Nonnull)section + (void)clearMetadataFromSection:(NSString *)section { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client clearMetadataFromSection:section]; } } @@ -280,7 +284,7 @@ + (void)clearMetadataFromSection:(NSString *)section + (void)clearMetadataFromSection:(NSString *_Nonnull)sectionName withKey:(NSString *_Nonnull)key { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client clearMetadataFromSection:sectionName withKey:key]; } @@ -289,13 +293,13 @@ + (void)clearMetadataFromSection:(NSString *_Nonnull)sectionName // MARK: - + (void)setContext:(NSString *_Nullable)context { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client setContext:context]; } } + (NSString *_Nullable)context { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return self.client.context; } return nil; @@ -308,13 +312,13 @@ + (BugsnagUser *)user { + (void)setUser:(NSString *_Nullable)userId withEmail:(NSString *_Nullable)email andName:(NSString *_Nullable)name { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client setUser:userId withEmail:email andName:name]; } } + (nonnull BugsnagOnSessionRef)addOnSessionBlock:(nonnull BugsnagOnSessionBlock)block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [self.client addOnSessionBlock:block]; } else { // We need to return something from this nonnull method; simulate what would have happened. @@ -323,14 +327,14 @@ + (nonnull BugsnagOnSessionRef)addOnSessionBlock:(nonnull BugsnagOnSessionBlock) } + (void)removeOnSession:(nonnull BugsnagOnSessionRef)callback { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client removeOnSession:callback]; } } + (void)removeOnSessionBlock:(BugsnagOnSessionBlock _Nonnull )block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client removeOnSessionBlock:block]; } } @@ -340,7 +344,7 @@ + (void)removeOnSessionBlock:(BugsnagOnSessionBlock _Nonnull )block // ============================================================================= + (nonnull BugsnagOnBreadcrumbRef)addOnBreadcrumbBlock:(nonnull BugsnagOnBreadcrumbBlock)block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { return [self.client addOnBreadcrumbBlock:block]; } else { // We need to return something from this nonnull method; simulate what would have happened. @@ -349,13 +353,13 @@ + (nonnull BugsnagOnBreadcrumbRef)addOnBreadcrumbBlock:(nonnull BugsnagOnBreadcr } + (void)removeOnBreadcrumb:(nonnull BugsnagOnBreadcrumbRef)callback { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client removeOnBreadcrumb:callback]; } } + (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { - if ([self bugsnagStarted]) { + if ([self bugsnagReadyForInternalCalls]) { [self.client removeOnBreadcrumbBlock:block]; } } diff --git a/Bugsnag/BugsnagInternals.h b/Bugsnag/BugsnagInternals.h index ebb6e5251..7a71c091e 100644 --- a/Bugsnag/BugsnagInternals.h +++ b/Bugsnag/BugsnagInternals.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @interface Bugsnag () -@property (class, readonly, nonatomic) BOOL bugsnagStarted; +@property (class, readonly, nonatomic) BOOL bugsnagReadyForInternalCalls; @property (class, readonly, nonatomic) BugsnagClient *client; diff --git a/Bugsnag/Client/BugsnagClient+Private.h b/Bugsnag/Client/BugsnagClient+Private.h index ef5e1827c..fd3c836fd 100644 --- a/Bugsnag/Client/BugsnagClient+Private.h +++ b/Bugsnag/Client/BugsnagClient+Private.h @@ -40,7 +40,11 @@ BSG_OBJC_DIRECT_MEMBERS @property (nonatomic) NSMutableDictionary *extraRuntimeInfo; -@property (nonatomic) BOOL started; +@property (atomic) BOOL isStarted; + +/// YES if BugsnagClient is ready to handle some internal method calls. +/// It does not mean that it is fully started and ready to receive method calls from outside of the library. +@property (atomic) BOOL readyForInternalCalls; /// State related metadata /// diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 9c3297dfc..4c6cd9464 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -284,7 +284,7 @@ - (void)start { #endif object:nil]; - self.started = YES; + self.readyForInternalCalls = YES; id reactNativePlugin = [NSClassFromString(@"BugsnagReactNativePlugin") new]; if (reactNativePlugin) { @@ -326,6 +326,7 @@ - (void)start { // Note: BSGAppHangDetector itself checks configuration.enabledErrorTypes.appHangs [self startAppHangDetector]; #endif + self.isStarted = YES; } - (void)appLaunchTimerFired:(__unused NSTimer *)timer { @@ -747,6 +748,12 @@ - (void)notifyErrorOrException:(id)errorOrException block:(BugsnagOnErrorBlock)b - (void)notifyInternal:(BugsnagEvent *_Nonnull)event block:(BugsnagOnErrorBlock)block { + // Checks whether releaseStage is in enabledReleaseStages, blocking onError callback from running if it is not. + if (!self.configuration.shouldSendReports || ![event shouldBeSent]) { + bsg_log_info("Discarding error because releaseStage '%@' not in enabledReleaseStages", self.configuration.releaseStage); + return; + } + NSString *errorClass = event.errors.firstObject.errorClass; if ([self.configuration shouldDiscardErrorClass:errorClass]) { bsg_log_info(@"Discarding event because errorClass \"%@\" matched configuration.discardClasses", errorClass); diff --git a/Bugsnag/Helpers/BSGHardware.h b/Bugsnag/Helpers/BSGHardware.h index 2bb1c0292..ce3c5b95a 100644 --- a/Bugsnag/Helpers/BSGHardware.h +++ b/Bugsnag/Helpers/BSGHardware.h @@ -18,11 +18,11 @@ #pragma mark Device #if TARGET_OS_IOS -static inline UIDevice *BSGGetDevice() { +static inline UIDevice *BSGGetDevice(void) { return [UIDEVICE currentDevice]; } #elif TARGET_OS_WATCH -static inline WKInterfaceDevice *BSGGetDevice() { +static inline WKInterfaceDevice *BSGGetDevice(void) { return [WKInterfaceDevice currentDevice]; } #endif diff --git a/Bugsnag/Helpers/BSGInternalErrorReporter.m b/Bugsnag/Helpers/BSGInternalErrorReporter.m index ec28e9d8e..fa00f2a4c 100644 --- a/Bugsnag/Helpers/BSGInternalErrorReporter.m +++ b/Bugsnag/Helpers/BSGInternalErrorReporter.m @@ -283,7 +283,7 @@ - (void)sendEvent:(nonnull BugsnagEvent *)event { // Intentionally differs from +[BSG_KSSystemInfo deviceAndAppHash] // See ROAD-1488 -static NSString * DeviceId() { +static NSString * DeviceId(void) { CC_SHA1_CTX ctx; CC_SHA1_Init(&ctx); diff --git a/Bugsnag/Helpers/BSGRunContext.h b/Bugsnag/Helpers/BSGRunContext.h index 496cdeb8c..0b6d2db55 100644 --- a/Bugsnag/Helpers/BSGRunContext.h +++ b/Bugsnag/Helpers/BSGRunContext.h @@ -77,7 +77,7 @@ void BSGRunContextUpdateTimestamp(void); #pragma mark - #ifdef FOUNDATION_EXTERN -static inline bool BSGRunContextWasCriticalThermalState() { +static inline bool BSGRunContextWasCriticalThermalState(void) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability-new" return bsg_lastRunContext && bsg_lastRunContext->thermalState == NSProcessInfoThermalStateCritical; @@ -89,16 +89,16 @@ static inline bool BSGRunContextWasCriticalThermalState() { bool BSGRunContextWasKilled(void); #endif -static inline bool BSGRunContextWasLaunching() { +static inline bool BSGRunContextWasLaunching(void) { return bsg_lastRunContext && bsg_lastRunContext->isLaunching; } #if BSG_HAVE_OOM_DETECTION -static inline bool BSGRunContextWasMemoryWarning() { +static inline bool BSGRunContextWasMemoryWarning(void) { return bsg_lastRunContext && bsg_lastRunContext->memoryPressure > DISPATCH_MEMORYPRESSURE_NORMAL; } #endif -static inline bool BSGRunContextWasTerminating() { +static inline bool BSGRunContextWasTerminating(void) { return bsg_lastRunContext && bsg_lastRunContext->isTerminating; } diff --git a/Bugsnag/Helpers/BSGRunContext.m b/Bugsnag/Helpers/BSGRunContext.m index 9dc6b81c8..9c2cf5a32 100644 --- a/Bugsnag/Helpers/BSGRunContext.m +++ b/Bugsnag/Helpers/BSGRunContext.m @@ -45,7 +45,7 @@ #pragma mark - Initial setup /// Populates `bsg_runContext` -static void InitRunContext() { +static void InitRunContext(void) { bsg_runContext->isDebuggerAttached = bsg_ksmachisBeingTraced(); bsg_runContext->isLaunching = YES; @@ -74,19 +74,19 @@ static void InitRunContext() { } BSGRunContextUpdateTimestamp(); - InstallTimer(); - BSGRunContextUpdateMemory(); if (!bsg_runContext->memoryLimit) { bsg_log_debug(@"Cannot query `memoryLimit` on this device"); } + + InstallTimer(); // Set `structVersion` last so that BSGRunContextLoadLast() will reject data // that is not fully initialised. bsg_runContext->structVersion = BSGRUNCONTEXT_VERSION; } -static uint64_t GetBootTime() { +static uint64_t GetBootTime(void) { struct timeval tv; size_t len = sizeof(tv); int ret = sysctl((int[]){CTL_KERN, KERN_BOOTTIME}, 2, &tv, &len, NULL, 0); @@ -94,7 +94,7 @@ static uint64_t GetBootTime() { return (uint64_t)tv.tv_sec * USEC_PER_SEC + (uint64_t)tv.tv_usec; } -static bool GetIsActive() { +static bool GetIsActive(void) { #if TARGET_OS_OSX return GetIsForeground(); #endif @@ -110,7 +110,7 @@ static bool GetIsActive() { #endif } -static bool GetIsForeground() { +static bool GetIsForeground(void) { #if TARGET_OS_OSX return [[NSAPPLICATION sharedApplication] isActive]; #endif @@ -182,7 +182,7 @@ static bool GetIsForeground() { #endif -static void InstallTimer() { +static void InstallTimer(void) { static dispatch_source_t timer; dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); @@ -206,27 +206,43 @@ static void InstallTimer() { #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_OSX -static void NoteAppActive() { +static void NoteAppActive(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->isActive = YES; bsg_runContext->isForeground = YES; BSGRunContextUpdateTimestamp(); } -static void NoteAppBackground() { +static void NoteAppBackground(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->isActive = NO; bsg_runContext->isForeground = NO; BSGRunContextUpdateTimestamp(); } #if !TARGET_OS_OSX -static void NoteAppInactive() { +static void NoteAppInactive(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->isActive = NO; bsg_runContext->isForeground = YES; BSGRunContextUpdateTimestamp(); } #endif -static void NoteAppWillTerminate() { +static void NoteAppWillTerminate(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->isTerminating = YES; BSGRunContextUpdateTimestamp(); } @@ -235,15 +251,27 @@ static void NoteAppWillTerminate() { #if TARGET_OS_IOS -static void NoteBatteryLevel() { +static void NoteBatteryLevel(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->batteryLevel = BSGGetDevice().batteryLevel; } -static void NoteBatteryState() { +static void NoteBatteryState(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { bsg_runContext->batteryState = BSGGetDevice().batteryState; } -static void NoteOrientation() { +static void NoteOrientation(__unused CFNotificationCenterRef center, + __unused void *observer, + __unused CFNotificationName name, + __unused const void *object, + __unused CFDictionaryRef userInfo) { UIDeviceOrientation orientation = [UIDEVICE currentDevice].orientation; if (orientation != UIDeviceOrientationUnknown) { bsg_runContext->lastKnownOrientation = orientation; @@ -272,7 +300,7 @@ static void NoteThermalState(__unused CFNotificationCenterRef center, #if BSG_HAVE_OOM_DETECTION -static void ObserveMemoryPressure() { +static void ObserveMemoryPressure(void) { // DISPATCH_SOURCE_TYPE_MEMORYPRESSURE arrives slightly sooner than // UIApplicationDidReceiveMemoryWarningNotification dispatch_source_t source = @@ -293,7 +321,7 @@ static void ObserveMemoryPressure() { #endif -static void AddObservers() { +static void AddObservers(void) { CFNotificationCenterRef center = CFNotificationCenterGetLocalCenter(); #define OBSERVE(name, function) CFNotificationCenterAddObserver(\ @@ -341,11 +369,11 @@ static void AddObservers() { #pragma mark - Misc -void BSGRunContextUpdateTimestamp() { +void BSGRunContextUpdateTimestamp(void) { ATOMIC_SET(bsg_runContext->timestamp, CFAbsoluteTimeGetCurrent()); } -static void UpdateHostMemory() { +static void UpdateHostMemory(void) { static _Atomic mach_port_t host_atomic = 0; mach_port_t host = atomic_load(&host_atomic); if (!host) { @@ -363,10 +391,10 @@ static void UpdateHostMemory() { } size_t hostMemoryFree = host_vm.free_count * vm_kernel_page_size; - bsg_runContext->hostMemoryFree = hostMemoryFree; + ATOMIC_SET(bsg_runContext->hostMemoryFree, hostMemoryFree); } -static void UpdateTaskMemory() { +static void UpdateTaskMemory(void) { task_vm_info_data_t task_vm = {0}; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kr = task_info(current_task(), TASK_VM_INFO, @@ -391,7 +419,7 @@ static void UpdateTaskMemory() { #endif } -void BSGRunContextUpdateMemory() { +void BSGRunContextUpdateMemory(void) { UpdateTaskMemory(); UpdateHostMemory(); } @@ -401,7 +429,7 @@ void BSGRunContextUpdateMemory() { #if !TARGET_OS_WATCH -bool BSGRunContextWasKilled() { +bool BSGRunContextWasKilled(void) { // App extensions have a different lifecycle and the heuristic used for // finding app terminations rooted in fixable code does not apply if ([BSG_KSSystemInfo isRunningInAppExtension]) { diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c index f7f1d40a7..3d7483eeb 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashC.c @@ -109,6 +109,8 @@ BSG_KSCrashType bsg_kscrash_install(const char *const crashReportFilePath, return context->config.handlingCrashTypes; } bsg_g_installed = 1; + + bsg_mach_headers_initialize(); bsg_kscrash_reinstall(crashReportFilePath, recrashReportFilePath, stateFilePath, crashID); diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c index 275811563..c36109511 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c @@ -844,7 +844,7 @@ void bsg_kscrw_i_writeBinaryImages(const BSG_KSCrashReportWriter *const writer, { writer->beginArray(writer, key); { - for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = img->next) { + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = atomic_load(&img->next)) { if (img->inCrashReport) { bsg_kscrw_i_writeBinaryImage(writer, NULL, img); } diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m index 6b1b6908c..bb849bc46 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m @@ -208,7 +208,7 @@ void bsg_kscrashstate_notifyAppCrash(void) { bsg_kscrashstate_i_saveState(bsg_g_state, bsg_g_stateFilePath); } -void bsg_kscrashstate_updateDurationStats() { +void bsg_kscrashstate_updateDurationStats(void) { uint64_t timeNow = mach_absolute_time(); const double duration = bsg_ksmachtimeDifferenceInSeconds( timeNow, bsg_g_state->lastUpdateDurationsTime ?: bsg_g_state->appLaunchTime); diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m index e389c78fb..1eeae0796 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m @@ -48,7 +48,7 @@ #import #import -static inline bool is_jailbroken() { +static inline bool is_jailbroken(void) { static bool initialized_jb; static bool is_jb; if(!initialized_jb) { @@ -72,7 +72,7 @@ static inline bool is_jailbroken() { * https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/system-version-compat.c.auto.html */ #if !TARGET_OS_SIMULATOR -static NSDictionary * bsg_systemversion() { +static NSDictionary * bsg_systemversion(void) { int fd = -1; char buffer[1024] = {0}; const char *file = "/System/Library/CoreServices/SystemVersion.plist"; diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c index c6e279f28..d593948b0 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c @@ -152,7 +152,7 @@ bool bsg_ksmachfillState(const thread_t thread, const thread_state_t state, } #endif -thread_t bsg_ksmachthread_self() { +thread_t bsg_ksmachthread_self(void) { thread_t thread_self = mach_thread_self(); mach_port_deallocate(mach_task_self(), thread_self); return thread_self; diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.c index 52f1b85ee..5a92664c9 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.c @@ -34,33 +34,43 @@ struct crashreporter_annotations_t { uint64_t abort_cause; // unsigned int }; -static void bsg_mach_headers_register_dyld_images(void); -static void bsg_mach_headers_register_for_changes(void); -static intptr_t bsg_mach_headers_compute_slide(const struct mach_header *header); -static bool bsg_mach_headers_contains_address(BSG_Mach_Header_Info *image, vm_address_t address); -static const char * bsg_mach_headers_get_path(const struct mach_header *header); +static void add_image(const struct mach_header *header, intptr_t slide); +static void remove_image(const struct mach_header *header, intptr_t slide); +static void register_dyld_images(void); +static void register_for_changes(void); +static intptr_t compute_slide(const struct mach_header *header); +static bool contains_address(BSG_Mach_Header_Info *image, vm_address_t address); +static const char * get_path(const struct mach_header *header); static const struct dyld_all_image_infos *g_all_image_infos; // MARK: - Mach Header Linked List -static BSG_Mach_Header_Info *bsg_g_mach_headers_images_head; -static BSG_Mach_Header_Info *bsg_g_mach_headers_images_tail; -static dispatch_queue_t bsg_g_serial_queue; - +// The list head is implemented as a dummy entry to simplify the algorithm. +// We fetch g_head_dummy.next to get the real head of the list. +static BSG_Mach_Header_Info g_head_dummy; +static _Atomic(BSG_Mach_Header_Info *) g_images_tail = &g_head_dummy; static BSG_Mach_Header_Info *g_self_image; -BSG_Mach_Header_Info *bsg_mach_headers_get_images() { - if (!bsg_g_mach_headers_images_head) { - bsg_mach_headers_initialize(); - bsg_mach_headers_register_dyld_images(); - bsg_mach_headers_register_for_changes(); +static _Atomic(bool) is_mach_headers_initialized; + +void bsg_mach_headers_initialize(void) { + bool expected = false; + if (!atomic_compare_exchange_strong(&is_mach_headers_initialized, &expected, true)) { + // Already called + return; } - return bsg_g_mach_headers_images_head; + + register_dyld_images(); + register_for_changes(); +} + +BSG_Mach_Header_Info *bsg_mach_headers_get_images(void) { + return atomic_load(&g_head_dummy.next); } -BSG_Mach_Header_Info *bsg_mach_headers_get_main_image() { - for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = img->next) { +BSG_Mach_Header_Info *bsg_mach_headers_get_main_image(void) { + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = atomic_load(&img->next)) { if (img->header->filetype == MH_EXECUTE) { return img; } @@ -69,26 +79,10 @@ BSG_Mach_Header_Info *bsg_mach_headers_get_main_image() { } BSG_Mach_Header_Info *bsg_mach_headers_get_self_image(void) { - (void)bsg_mach_headers_get_images(); return g_self_image; } -void bsg_mach_headers_initialize() { - - // Clear any existing headers to reset the head/tail pointers - for (BSG_Mach_Header_Info *img = bsg_g_mach_headers_images_head; img != NULL; ) { - BSG_Mach_Header_Info *imgToDelete = img; - img = img->next; - free(imgToDelete); - } - - bsg_g_mach_headers_images_head = NULL; - bsg_g_mach_headers_images_tail = NULL; - bsg_g_serial_queue = dispatch_queue_create("com.bugsnag.mach-headers", DISPATCH_QUEUE_SERIAL); - g_self_image = NULL; -} - -static void bsg_mach_headers_register_dyld_images() { +static void register_dyld_images(void) { // /usr/lib/dyld's mach header is is not exposed via the _dyld APIs, so to be able to include information // about stack frames in dyld`start (for example) we need to acess "_dyld_all_image_infos" task_dyld_info_data_t dyld_info = {0}; @@ -97,8 +91,8 @@ static void bsg_mach_headers_register_dyld_images() { if (kr == KERN_SUCCESS && dyld_info.all_image_info_addr) { g_all_image_infos = (const void *)dyld_info.all_image_info_addr; - intptr_t dyldImageSlide = bsg_mach_headers_compute_slide(g_all_image_infos->dyldImageLoadAddress); - bsg_mach_headers_add_image(g_all_image_infos->dyldImageLoadAddress, dyldImageSlide); + intptr_t dyldImageSlide = compute_slide(g_all_image_infos->dyldImageLoadAddress); + add_image(g_all_image_infos->dyldImageLoadAddress, dyldImageSlide); #if TARGET_OS_SIMULATOR // Get the mach header for `dyld_sim` which is not exposed via the _dyld APIs @@ -106,7 +100,7 @@ static void bsg_mach_headers_register_dyld_images() { if (g_all_image_infos->infoArray && strstr(g_all_image_infos->infoArray->imageFilePath, "/usr/lib/dyld_sim")) { const struct mach_header *header = g_all_image_infos->infoArray->imageLoadAddress; - bsg_mach_headers_add_image(header, bsg_mach_headers_compute_slide(header)); + add_image(header, compute_slide(header)); } #endif } else { @@ -114,12 +108,12 @@ static void bsg_mach_headers_register_dyld_images() { } } -static void bsg_mach_headers_register_for_changes() { +static void register_for_changes(void) { // Register for binary images being loaded and unloaded. dyld calls the add function once // for each library that has already been loaded and then keeps this cache up-to-date // with future changes - _dyld_register_func_for_add_image(&bsg_mach_headers_add_image); - _dyld_register_func_for_remove_image(&bsg_mach_headers_remove_image); + _dyld_register_func_for_add_image(&add_image); + _dyld_register_func_for_remove_image(&remove_image); } /** @@ -142,7 +136,7 @@ bool bsg_mach_headers_populate_info(const struct mach_header *header, intptr_t s } // 2. The image doesn't have a name. Note: running with a debugger attached causes this condition to match. - const char *imageName = bsg_mach_headers_get_path(header); + const char *imageName = get_path(header); if (!imageName) { BSG_KSLOG_ERROR("Could not find name for mach header @ %p", header); return false; @@ -195,43 +189,41 @@ bool bsg_mach_headers_populate_info(const struct mach_header *header, intptr_t s info->name = imageName; info->slide = slide; info->unloaded = FALSE; - info->next = NULL; + atomic_store(&info->next, NULL); return true; } -void bsg_mach_headers_add_image(const struct mach_header *header, intptr_t slide) { +static void add_image(const struct mach_header *header, intptr_t slide) { BSG_Mach_Header_Info *newImage = calloc(1, sizeof(BSG_Mach_Header_Info)); - if (newImage != NULL) { - if (bsg_mach_headers_populate_info(header, slide, newImage)) { - dispatch_sync(bsg_g_serial_queue, ^{ - if (bsg_g_mach_headers_images_head == NULL) { - bsg_g_mach_headers_images_head = newImage; - } else { - bsg_g_mach_headers_images_tail->next = newImage; - } - bsg_g_mach_headers_images_tail = newImage; - if (header == &__dso_handle) { - g_self_image = newImage; - } - }); - } else { - free(newImage); - } + if (newImage == NULL) { + return; + } + + if (!bsg_mach_headers_populate_info(header, slide, newImage)) { + free(newImage); + return; + } + + BSG_Mach_Header_Info *oldTail = atomic_exchange(&g_images_tail, newImage); + atomic_store(&oldTail->next, newImage); + + if (header == &__dso_handle) { + g_self_image = newImage; } } -/** - * To avoid a destructive operation that could lead thread safety problems, we maintain the - * image record, but mark it as unloaded - */ -void bsg_mach_headers_remove_image(const struct mach_header *header, intptr_t slide) { +static void remove_image(const struct mach_header *header, intptr_t slide) { BSG_Mach_Header_Info existingImage = { 0 }; - if (bsg_mach_headers_populate_info(header, slide, &existingImage)) { - for (BSG_Mach_Header_Info *img = bsg_g_mach_headers_images_head; img != NULL; img = img->next) { - if (img->imageVmAddr == existingImage.imageVmAddr) { - img->unloaded = true; - } + if (!bsg_mach_headers_populate_info(header, slide, &existingImage)) { + return; + } + + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = atomic_load(&img->next)) { + if (img->imageVmAddr == existingImage.imageVmAddr) { + // To avoid a destructive operation that could lead thread safety problems, + // we maintain the image record, but mark it as unloaded + img->unloaded = true; } } } @@ -240,7 +232,7 @@ BSG_Mach_Header_Info *bsg_mach_headers_image_named(const char *const imageName, if (imageName != NULL) { - for (BSG_Mach_Header_Info *img = bsg_g_mach_headers_images_head; img != NULL; img = img->next) { + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = atomic_load(&img->next)) { if (img->name == NULL) { continue; // name is null if the index is out of range per dyld(3) } else if (img->unloaded == true) { @@ -261,8 +253,8 @@ BSG_Mach_Header_Info *bsg_mach_headers_image_named(const char *const imageName, } BSG_Mach_Header_Info *bsg_mach_headers_image_at_address(const uintptr_t address) { - for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img; img = img->next) { - if (bsg_mach_headers_contains_address(img, address)) { + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img; img = atomic_load(&img->next)) { + if (contains_address(img, address)) { return img; } } @@ -350,7 +342,7 @@ const char *bsg_mach_headers_get_crash_info_message(const BSG_Mach_Header_Info * return NULL; } -static intptr_t bsg_mach_headers_compute_slide(const struct mach_header *header) { +static intptr_t compute_slide(const struct mach_header *header) { uintptr_t cmdPtr = bsg_mach_headers_first_cmd_after_header(header); if (!cmdPtr) { return 0; @@ -376,7 +368,7 @@ static intptr_t bsg_mach_headers_compute_slide(const struct mach_header *header) return 0; } -static bool bsg_mach_headers_contains_address(BSG_Mach_Header_Info *img, vm_address_t address) { +static bool contains_address(BSG_Mach_Header_Info *img, vm_address_t address) { if (img->unloaded) { return false; } @@ -384,7 +376,7 @@ static bool bsg_mach_headers_contains_address(BSG_Mach_Header_Info *img, vm_addr return address >= imageStart && address < (imageStart + img->imageSize); } -static const char * bsg_mach_headers_get_path(const struct mach_header *header) { +static const char * get_path(const struct mach_header *header) { Dl_info DlInfo = {0}; dladdr(header, &DlInfo); if (DlInfo.dli_fname) { @@ -403,3 +395,28 @@ static const char * bsg_mach_headers_get_path(const struct mach_header *header) #endif return NULL; } + +void bsg_test_support_mach_headers_reset(void) { + // Erase all current images + BSG_Mach_Header_Info *next = NULL; + for (BSG_Mach_Header_Info *img = bsg_mach_headers_get_images(); img != NULL; img = next) { + next = atomic_load(&img->next); + free(img); + } + + // Reset cached data + atomic_store(&g_head_dummy.next, NULL); + atomic_store(&g_images_tail, &g_head_dummy); + g_self_image = NULL; + + // Force bsg_mach_headers_initialize to run again when requested. + atomic_store(&is_mach_headers_initialized, false); +} + +void bsg_test_support_mach_headers_add_image(const struct mach_header *header, intptr_t slide) { + add_image(header, slide); +} + +void bsg_test_support_mach_headers_remove_image(const struct mach_header *header, intptr_t slide) { + remove_image(header, slide); +} diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.h b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.h index 05e2471d5..c69159c96 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.h +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMachHeaders.h @@ -11,6 +11,7 @@ #include #include +#include /* Maintaining our own list of framework Mach headers means that we avoid potential * deadlock situations where we try and suspend lock-holding threads prior to @@ -52,13 +53,14 @@ typedef struct bsg_mach_image { bool inCrashReport; /// The next image in the linked list - struct bsg_mach_image *next; + _Atomic(struct bsg_mach_image *) next; } BSG_Mach_Header_Info; // MARK: - Operations /** - * Resets mach header data + * Initialize the headers management system. + * This MUST be called before calling anything else. */ void bsg_mach_headers_initialize(void); @@ -77,16 +79,6 @@ BSG_Mach_Header_Info *bsg_mach_headers_get_main_image(void); */ BSG_Mach_Header_Info *bsg_mach_headers_get_self_image(void); -/** - * Called when a binary image is loaded. - */ -void bsg_mach_headers_add_image(const struct mach_header *mh, intptr_t slide); - -/** - * Called when a binary image is unloaded. - */ -void bsg_mach_headers_remove_image(const struct mach_header *mh, intptr_t slide); - /** * Find the loaded binary image that contains the specified instruction address. */ @@ -120,4 +112,19 @@ uintptr_t bsg_mach_headers_first_cmd_after_header(const struct mach_header *head */ const char *bsg_mach_headers_get_crash_info_message(const BSG_Mach_Header_Info *header); +/** + * Resets mach header data (for unit tests). + */ +void bsg_test_support_mach_headers_reset(void); + +/** + * Add a binary image (for unit tests). + */ +void bsg_test_support_mach_headers_add_image(const struct mach_header *mh, intptr_t slide); + +/** + * Remove a binary image (for unit tests). + */ +void bsg_test_support_mach_headers_remove_image(const struct mach_header *mh, intptr_t slide); + #endif /* BSG_KSMachHeaders_h */ diff --git a/Bugsnag/Payload/BugsnagNotifier.m b/Bugsnag/Payload/BugsnagNotifier.m index dfe101c8a..bcbf16c50 100644 --- a/Bugsnag/Payload/BugsnagNotifier.m +++ b/Bugsnag/Payload/BugsnagNotifier.m @@ -23,7 +23,7 @@ - (instancetype)init { #else _name = @"Bugsnag Objective-C"; #endif - _version = @"6.25.2"; + _version = @"6.26.0"; _url = @"https://github.com/bugsnag/bugsnag-cocoa"; _dependencies = @[]; } diff --git a/Bugsnag/Payload/BugsnagThread.m b/Bugsnag/Payload/BugsnagThread.m index 1ed5d6307..6bd77c08b 100644 --- a/Bugsnag/Payload/BugsnagThread.m +++ b/Bugsnag/Payload/BugsnagThread.m @@ -26,12 +26,12 @@ // Protect access to thread-unsafe bsg_kscrashsentry_suspendThreads() static pthread_mutex_t bsg_suspend_threads_mutex = PTHREAD_MUTEX_INITIALIZER; -static void suspend_threads() { +static void suspend_threads(void) { pthread_mutex_lock(&bsg_suspend_threads_mutex); bsg_kscrashsentry_suspendThreads(); } -static void resume_threads() { +static void resume_threads(void) { bsg_kscrashsentry_resumeThreads(); pthread_mutex_unlock(&bsg_suspend_threads_mutex); } diff --git a/Bugsnag/include/Bugsnag/BSG_KSCrashReportWriter.h b/Bugsnag/include/Bugsnag/BSG_KSCrashReportWriter.h index f4f563c82..610eb20f1 100644 --- a/Bugsnag/include/Bugsnag/BSG_KSCrashReportWriter.h +++ b/Bugsnag/include/Bugsnag/BSG_KSCrashReportWriter.h @@ -190,7 +190,7 @@ typedef struct BSG_KSCrashReportWriter { * * @param name The name to give this element. * - * @param value A pointer to the JSON data. + * @param jsonElement A pointer to the JSON data. */ void (*addJSONElement)(const struct BSG_KSCrashReportWriter *writer, const char *name, const char *jsonElement); diff --git a/Bugsnag/include/Bugsnag/Bugsnag.h b/Bugsnag/include/Bugsnag/Bugsnag.h index 26d9b8e47..caab62b46 100644 --- a/Bugsnag/include/Bugsnag/Bugsnag.h +++ b/Bugsnag/include/Bugsnag/Bugsnag.h @@ -107,6 +107,12 @@ BUGSNAG_EXTERN */ + (BOOL)appDidCrashLastLaunch BUGSNAG_DEPRECATED_WITH_REPLACEMENT("lastRunInfo.crashed"); +/** + * @return YES if and only if a Bugsnag.start() has been called + * and Bugsnag has initialized such that any calls to the Bugsnag methods can succeed + */ ++ (BOOL)isStarted; + /** * Information about the last run of the app, and whether it crashed. */ diff --git a/BugsnagNetworkRequestPlugin.podspec.json b/BugsnagNetworkRequestPlugin.podspec.json index 62323d3bd..d0e51f071 100644 --- a/BugsnagNetworkRequestPlugin.podspec.json +++ b/BugsnagNetworkRequestPlugin.podspec.json @@ -1,16 +1,16 @@ { "name": "BugsnagNetworkRequestPlugin", - "version": "6.25.2", + "version": "6.26.0", "summary": "Network request monitoring support for Bugsnag.", "homepage": "https://bugsnag.com", "license": "MIT", "authors": { "Bugsnag": "notifiers@bugsnag.com" }, - "readme": "https://raw.githubusercontent.com/bugsnag/bugsnag-cocoa/v6.25.2/BugsnagNetworkRequestPlugin/README.md", + "readme": "https://raw.githubusercontent.com/bugsnag/bugsnag-cocoa/v6.26.0/BugsnagNetworkRequestPlugin/README.md", "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa.git", - "tag": "v6.25.2" + "tag": "v6.26.0" }, "dependencies": { "Bugsnag": "~> 6.13" diff --git a/CHANGELOG.md b/CHANGELOG.md index 481685654..2d1005c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ Changelog ========= +## 6.26.0 (2023-03-08) + +### Enhancements + +* Added isStarted to Bugsnag and BugsnagClient + [1528](https://github.com/bugsnag/bugsnag-cocoa/pull/1528) + +### Bug fixes + +* Fixed some race conditions that could cause issues in rare cases. + [1529](https://github.com/bugsnag/bugsnag-cocoa/pull/1529) + +* onError blocked from running if releaseStage not in enabledReleaseStages. + [1518](https://github.com/bugsnag/bugsnag-cocoa/pull/1518) + + ## 6.25.2 (2023-01-18) ### Bug fixes diff --git a/Framework/Info.plist b/Framework/Info.plist index 4ba85157b..37a72f1b7 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.25.2 + 6.26.0 CFBundleVersion 1 diff --git a/Gemfile b/Gemfile index dc2da16a5..7b7d53bc3 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem 'cocoapods' # A reference to Maze Runner is only needed for running tests locally and if committed it must be # portable for CI, e.g. a specific release. However, leaving it commented out would mean quicker CI. -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.10.1' +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.17.0' # Use a specific branch #gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'master' diff --git a/Gemfile.lock b/Gemfile.lock index f6d21057c..865c301b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: https://github.com/bugsnag/maze-runner - revision: c361875c7cbd9c4b37d56ccaa1493d4c42a0f0b6 - tag: v7.10.1 + revision: 01596b90f65a11759e4bd307242e46b1f4c240b2 + tag: v7.17.0 specs: - bugsnag-maze-runner (7.10.1) + bugsnag-maze-runner (7.17.0) appium_lib (~> 12.0.0) appium_lib_core (~> 5.4.0) bugsnag (~> 6.24) @@ -13,6 +13,7 @@ GIT json_schemer (~> 0.2.24) optimist (~> 3.0.1) os (~> 1.0.0) + rack (~> 2.2) rake (~> 12.3.3) rubyzip (~> 2.3.2) selenium-webdriver (~> 4.0) @@ -43,7 +44,7 @@ GEM faye-websocket (~> 0.11.0) selenium-webdriver (~> 4.2, < 4.6) atomos (0.1.3) - bugsnag (6.25.0) + bugsnag (6.25.1) concurrent-ruby (~> 1.0) builder (3.2.4) childprocess (4.1.0) @@ -146,23 +147,24 @@ GEM mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) - mini_portile2 (2.8.0) + mini_portile2 (2.8.1) minitest (5.16.1) molinillo (0.8.0) multi_test (0.1.2) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - nokogiri (1.13.10) + nokogiri (1.14.1) mini_portile2 (~> 2.8.0) racc (~> 1.4) optimist (3.0.1) os (1.0.1) - power_assert (2.0.2) + power_assert (2.0.3) public_suffix (4.0.7) - racc (1.6.1) + racc (1.6.2) + rack (2.2.6.2) rake (12.3.3) - regexp_parser (2.6.1) + regexp_parser (2.6.2) rexml (3.2.5) ruby-macho (2.5.1) rubyzip (2.3.2) diff --git a/Tests/BugsnagTests/BugsnagClientMirrorTest.m b/Tests/BugsnagTests/BugsnagClientMirrorTest.m index 25e31d6b4..e31ff1a27 100644 --- a/Tests/BugsnagTests/BugsnagClientMirrorTest.m +++ b/Tests/BugsnagTests/BugsnagClientMirrorTest.m @@ -76,6 +76,8 @@ - (void)setUp { @"orientationDidChange: v24@0:8@16", @"pluginClient @16@0:8", @"populateEventData: v24@0:8@16", + @"readyForInternalCalls B16@0:8", + @"readyForInternalCalls c16@0:8", @"sendBreadcrumbForControlNotification: v24@0:8@16", @"sendBreadcrumbForMenuItemNotification: v24@0:8@16", @"sendBreadcrumbForNotification: v24@0:8@16", @@ -107,9 +109,11 @@ - (void)setUp { @"setNotifier: v24@0:8@16", @"setObserver: v24@0:8@?16", @"setPluginClient: v24@0:8@16", + @"setReadyForInternalCalls: v20@0:8B16", + @"setReadyForInternalCalls: v20@0:8c16", @"setSessionTracker: v24@0:8@16", - @"setStarted: v20@0:8B16", - @"setStarted: v20@0:8c16", + @"setIsStarted: v20@0:8B16", + @"setIsStarted: v20@0:8c16", @"setState: v24@0:8@16", @"setStateEventBlocks: v24@0:8@16", @"setStateMetadataFromLastLaunch: v24@0:8@16", @@ -130,8 +134,8 @@ - (void)setUp { // the following methods are implemented on Bugsnag but do not need to // be mirrored on BugsnagClient self.bugsnagMethodsNotRequiredOnClient = [NSSet setWithArray:@[ - @"bugsnagStarted B16@0:8", - @"bugsnagStarted c16@0:8", + @"bugsnagReadyForInternalCalls B16@0:8", + @"bugsnagReadyForInternalCalls c16@0:8", @"client @16@0:8", @"getContext @16@0:8", @"purge v16@0:8", diff --git a/Tests/BugsnagTests/Info.plist b/Tests/BugsnagTests/Info.plist index 3900ec36e..2558e2828 100644 --- a/Tests/BugsnagTests/Info.plist +++ b/Tests/BugsnagTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.25.2 + 6.26.0 CFBundleVersion 1 diff --git a/Tests/KSCrashTests/BSG_KSMachHeadersTests.m b/Tests/KSCrashTests/BSG_KSMachHeadersTests.m index f1c972f90..3d5370417 100644 --- a/Tests/KSCrashTests/BSG_KSMachHeadersTests.m +++ b/Tests/KSCrashTests/BSG_KSMachHeadersTests.m @@ -13,8 +13,6 @@ #import #import -void bsg_mach_headers_add_image(const struct mach_header *mh, intptr_t slide); - const struct mach_header header1 = { .magic = MH_MAGIC, .cputype = 0, @@ -25,7 +23,11 @@ .flags = 0 }; const struct segment_command command1 = { - LC_SEGMENT,0,SEG_TEXT,111,10,0,0,0,0,0,0 + .cmd = LC_SEGMENT, + .cmdsize = 0, + .segname = SEG_TEXT, + .vmaddr = 111, + .vmsize = 10, }; const struct mach_header header2 = { @@ -38,7 +40,11 @@ .flags = 0 }; const struct segment_command command2 = { - LC_SEGMENT,0,SEG_TEXT,222,10,0,0,0,0,0,0 + .cmd = LC_SEGMENT, + .cmdsize = 0, + .segname = SEG_TEXT, + .vmaddr = 222, + .vmsize = 10, }; @interface BSG_KSMachHeadersTests : XCTestCase @@ -50,54 +56,56 @@ - (void)setUp { bsg_mach_headers_initialize(); } -- (void)testAddRemoveHeaders { - bsg_mach_headers_add_image(&header1, 0); - - BSG_Mach_Header_Info *listTail; +static BSG_Mach_Header_Info *get_tail(BSG_Mach_Header_Info *head) { + BSG_Mach_Header_Info *current = head; + for (; current->next != NULL; current = current->next) { + } + return current; +} + +- (void)testAddRemove { + bsg_test_support_mach_headers_reset(); + + bsg_test_support_mach_headers_add_image(&header1, 0); - listTail = bsg_mach_headers_get_images(); - XCTAssertEqual(listTail->imageVmAddr, 111); + BSG_Mach_Header_Info *listTail = get_tail(bsg_mach_headers_get_images()); + XCTAssertEqual(listTail->imageVmAddr, command1.vmaddr); XCTAssert(listTail->unloaded == FALSE); - bsg_mach_headers_add_image(&header2, 0); - - listTail = bsg_mach_headers_get_images(); - XCTAssertEqual(listTail->imageVmAddr, 111); + bsg_test_support_mach_headers_add_image(&header2, 0); + + XCTAssertEqual(listTail->imageVmAddr, command1.vmaddr); XCTAssert(listTail->unloaded == FALSE); - XCTAssertEqual(listTail->next->imageVmAddr, 222); + XCTAssertEqual(listTail->next->imageVmAddr, command2.vmaddr); XCTAssert(listTail->next->unloaded == FALSE); - - bsg_mach_headers_remove_image(&header1, 0); - listTail = bsg_mach_headers_get_images(); - XCTAssertEqual(listTail->imageVmAddr, 111); + bsg_test_support_mach_headers_remove_image(&header1, 0); + + XCTAssertEqual(listTail->imageVmAddr, command1.vmaddr); XCTAssert(listTail->unloaded == TRUE); - XCTAssertEqual(listTail->next->imageVmAddr, 222); + XCTAssertEqual(listTail->next->imageVmAddr, command2.vmaddr); XCTAssert(listTail->next->unloaded == FALSE); - - bsg_mach_headers_remove_image(&header2, 0); - listTail = bsg_mach_headers_get_images(); - XCTAssertEqual(listTail->imageVmAddr, 111); + bsg_test_support_mach_headers_remove_image(&header2, 0); + + XCTAssertEqual(listTail->imageVmAddr, command1.vmaddr); XCTAssert(listTail->unloaded == TRUE); - XCTAssertEqual(listTail->next->imageVmAddr, 222); + XCTAssertEqual(listTail->next->imageVmAddr, command2.vmaddr); XCTAssert(listTail->next->unloaded == TRUE); - - bsg_mach_headers_initialize(); } - (void)testFindImageAtAddress { - bsg_mach_headers_add_image(&header1, 0); - bsg_mach_headers_add_image(&header2, 0); + bsg_test_support_mach_headers_reset(); + + bsg_test_support_mach_headers_add_image(&header1, 0); + bsg_test_support_mach_headers_add_image(&header2, 0); BSG_Mach_Header_Info *item; item = bsg_mach_headers_image_at_address((uintptr_t)&header1); - XCTAssertEqual(item->imageVmAddr, 111); + XCTAssertEqual(item->imageVmAddr, command1.vmaddr); item = bsg_mach_headers_image_at_address((uintptr_t)&header2); - XCTAssertEqual(item->imageVmAddr, 222); - - bsg_mach_headers_initialize(); + XCTAssertEqual(item->imageVmAddr, command2.vmaddr); } - (void) testGetImageNameNULL diff --git a/Tests/TestHost-iOS/Info.plist b/Tests/TestHost-iOS/Info.plist index 422122c22..98e58351d 100644 --- a/Tests/TestHost-iOS/Info.plist +++ b/Tests/TestHost-iOS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.25.2 + 6.26.0 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/VERSION b/VERSION index aec9b1056..4c6a35fb6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.25.2 +6.26.0 diff --git a/docker-compose.yml b/docker-compose.yml index c38575871..7911bc1c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,34 @@ services: - ./features/:/app/features/ - ./maze_output:/app/maze_output + cocoa-maze-runner-bitbar: + image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner:tms-device-fixes-cli + environment: + DEBUG: + VERBOSE: + BUILDKITE: + BUILDKITE_BRANCH: + BUILDKITE_BUILD_CREATOR: + BUILDKITE_BUILD_NUMBER: + BUILDKITE_BUILD_URL: + BUILDKITE_LABEL: + BUILDKITE_MESSAGE: + BUILDKITE_PIPELINE_NAME: + BUILDKITE_REPO: + BUILDKITE_RETRY_COUNT: + BUILDKITE_STEP_KEY: + MAZE_BUGSNAG_API_KEY: + MAZE_REPEATER_API_KEY: + BITBAR_USERNAME: + BITBAR_ACCESS_KEY: + ports: + - "9000-9499:9339" + volumes: + - ./features/fixtures/ios/output:/app/build + - ./features/:/app/features/ + - ./maze_output:/app/maze_output + - /var/run/docker.sock:/var/run/docker.sock + cocoa-maze-runner-legacy: image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v7-cli-legacy environment: diff --git a/examples/objective-c-ios/objective-c-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/objective-c-ios/objective-c-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/examples/objective-c-ios/objective-c-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/features/app_and_device_attributes.feature b/features/app_and_device_attributes.feature index 8f8352667..1829bccd1 100644 --- a/features/app_and_device_attributes.feature +++ b/features/app_and_device_attributes.feature @@ -19,7 +19,8 @@ Feature: App and Device attributes present And the error payload field "events.0.device.manufacturer" equals "Apple" And the error payload field "events.0.device.locale" is not null And the error payload field "events.0.device.id" is not null - And the error payload field "events.0.device.model" matches the test device model + And the error payload field "events.0.device.model" matches the regex "[Macmini|iPhone]1?\d,\d" + And the error payload field "events.0.device.modelNumber" equals the platform-dependent string: | ios | @not_null | | macos | @null | @@ -90,14 +91,6 @@ Feature: App and Device attributes present And the error payload field "events.0.device.manufacturer" equals "Nokia" And the error payload field "events.0.device.modelNumber" equals "0898" - Scenario: Info.plist settings are used when calling startWithApiKey - When I run "AppAndDeviceAttributesStartWithApiKeyScenario" - And I wait to receive an error - Then the error is valid for the error reporting API - And the error "Bugsnag-API-Key" header equals "12312312312312312312312312312312" - - And the error payload field "events.0.app.releaseStage" equals "beta2" - Scenario: Duration value increments as expected When I run "AppDurationScenario" And I wait to receive 3 errors diff --git a/features/app_hangs.feature b/features/app_hangs.feature index dfa7f6044..709c5188f 100644 --- a/features/app_hangs.feature +++ b/features/app_hangs.feature @@ -7,6 +7,8 @@ Feature: App hangs When I run "AppHangDefaultConfigScenario" Then I should receive no errors + # PLAT-9616 + @skip_bitbar Scenario: App hangs above the threshold should be reported When I set the app to "2.1" mode And I run "AppHangScenario" @@ -46,7 +48,7 @@ Feature: App hangs And the error payload field "events.0.device.manufacturer" equals "Apple" And the error payload field "events.0.device.locale" is not null And the error payload field "events.0.device.id" is not null - And the error payload field "events.0.device.model" matches the test device model + And the error payload field "events.0.device.model" matches the regex "[Macmini|iPhone]1?\d,\d" And the error payload field "events.0.device.modelNumber" equals the platform-dependent string: | ios | @not_null | | macos | @null | diff --git a/features/barebone_tests.feature b/features/barebone_tests.feature index 8ed471a0f..7467fdccf 100644 --- a/features/barebone_tests.feature +++ b/features/barebone_tests.feature @@ -110,7 +110,7 @@ Feature: Barebone tests And the error payload field "events.0.app.durationInForeground" is a number And the error payload field "events.0.device.freeDisk" is an integer And the error payload field "events.0.device.freeMemory" is an integer - And the error payload field "events.0.device.model" matches the test device model + And the error payload field "events.0.device.model" matches the regex "[Macmini|iPhone]1?\d,\d" And the error payload field "events.0.device.totalMemory" is an integer And the error payload field "events.0.threads" is an array with 0 elements And the "isPC" of stack frame 0 is null @@ -234,7 +234,7 @@ Feature: Barebone tests And the error payload field "events.0.app.durationInForeground" is a number And the error payload field "events.0.device.freeDisk" is an integer And the error payload field "events.0.device.freeMemory" is an integer - And the error payload field "events.0.device.model" matches the test device model + And the error payload field "events.0.device.model" matches the regex "[Macmini|iPhone]1?\d,\d" And the error payload field "events.0.device.totalMemory" is an integer And on !watchOS, the error payload field "events.0.threads" is a non-empty array And on !watchOS, the error payload field "events.0.threads.1" is not null @@ -243,6 +243,7 @@ Feature: Barebone tests And the "isLR" of stack frame 0 is null @skip_macos + @skip_ios_16 # https://smartbear.atlassian.net/browse/PLAT-9724 Scenario: Barebone test: Out Of Memory When I run "OOMScenario" @@ -334,5 +335,5 @@ Feature: Barebone tests And the error payload field "events.0.app.duration" is null And the error payload field "events.0.app.durationInForeground" is null And the error payload field "events.0.device.freeDisk" is null - And the error payload field "events.0.device.model" matches the test device model + And the error payload field "events.0.device.model" matches the regex "[Macmini|iPhone]1?\d,\d" And the error payload field "events.0.threads" is an array with 0 elements diff --git a/features/breadcrumbs.feature b/features/breadcrumbs.feature index 2ecf471a5..7c09fab46 100644 --- a/features/breadcrumbs.feature +++ b/features/breadcrumbs.feature @@ -57,14 +57,13 @@ Feature: Attaching a series of notable events leading up to errors @watchos Scenario: Network breadcrumbs - When I start the document server - And I run "NetworkBreadcrumbsScenario" + When I run "NetworkBreadcrumbsScenario" Then I wait to receive an error And the event "breadcrumbs.0.timestamp" is a timestamp And the event "breadcrumbs.0.name" equals "NSURLSession request failed" And the event "breadcrumbs.0.type" equals "request" And the event "breadcrumbs.0.metaData.method" equals "GET" - And the event "breadcrumbs.0.metaData.url" matches "http://.*:9340/reflect/" + And the event "breadcrumbs.0.metaData.url" matches "http://.*:9\d{3}/reflect/" And the event "breadcrumbs.0.metaData.urlParams.status" equals "444" And the event "breadcrumbs.0.metaData.urlParams.password" equals "[REDACTED]" And the event "breadcrumbs.0.metaData.status" equals 444 @@ -75,7 +74,7 @@ Feature: Attaching a series of notable events leading up to errors And the event "breadcrumbs.1.name" equals "NSURLSession request succeeded" And the event "breadcrumbs.1.type" equals "request" And the event "breadcrumbs.1.metaData.method" equals "GET" - And the event "breadcrumbs.1.metaData.url" matches "http://.*:9340/reflect/" + And the event "breadcrumbs.1.metaData.url" matches "http://.*:9\d{3}/reflect/" And the event "breadcrumbs.1.metaData.urlParams.delay_ms" equals "3000" And the event "breadcrumbs.1.metaData.status" equals 200 And the event "breadcrumbs.1.metaData.duration" is greater than 0 diff --git a/features/config_from_plist.feature b/features/config_from_plist.feature index 17038da06..85aefc0ec 100644 --- a/features/config_from_plist.feature +++ b/features/config_from_plist.feature @@ -2,36 +2,44 @@ Feature: Loading Bugsnag configuration from Info.plist Configuration options can be specified in build at build time to avoid writing code for those options. - Background: - Given I clear all persistent data - - Scenario: Specifying config in Info.plist - When I run "LoadConfigFromFileScenario" - And I wait to receive a session - And I wait to receive an error - - Then the session "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" - And the session payload field "sessions" is not null - - And the error "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" - And the event "metaData.nserror.domain" equals the platform-dependent string: - | ios | iOSTestApp.LaunchError | - | macos | macOSTestApp.LaunchError | - And the event "app.releaseStage" equals "beta2" - - Scenario: Calling Bugsnag.start() with no configuration - When I run "LoadConfigFromFileAutoScenario" - And I wait to receive a session - And I wait to receive an error - - Then the session "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" - And the session payload field "sessions" is not null - - And the error "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" - And the error payload field "notifier.name" equals the platform-dependent string: - | ios | iOS Bugsnag Notifier | - | macos | OSX Bugsnag Notifier | - And the event "metaData.nserror.domain" equals the platform-dependent string: - | ios | iOSTestApp.LoadConfigFromFileAutoScenarioError | - | macos | macOSTestApp.LoadConfigFromFileAutoScenarioError | - And the event "app.releaseStage" equals "beta2" + Background: + Given I clear all persistent data + + Scenario: Specifying config in Info.plist + When I run "LoadConfigFromFileScenario" + And I wait to receive a session + And I wait to receive an error + + Then the session "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" + And the session payload field "sessions" is not null + + And the error "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" + And the event "metaData.nserror.domain" equals the platform-dependent string: + | ios | iOSTestApp.LaunchError | + | macos | macOSTestApp.LaunchError | + And the event "app.releaseStage" equals "beta2" + + Scenario: Calling Bugsnag.start() with no configuration + When I run "LoadConfigFromFileAutoScenario" + And I wait to receive a session + And I wait to receive an error + + Then the session "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" + And the session payload field "sessions" is not null + + And the error "Bugsnag-API-Key" header equals "0192837465afbecd0192837465afbecd" + And the error payload field "notifier.name" equals the platform-dependent string: + | ios | iOS Bugsnag Notifier | + | macos | OSX Bugsnag Notifier | + And the event "metaData.nserror.domain" equals the platform-dependent string: + | ios | iOSTestApp.LoadConfigFromFileAutoScenarioError | + | macos | macOSTestApp.LoadConfigFromFileAutoScenarioError | + And the event "app.releaseStage" equals "beta2" + + Scenario: Info.plist settings are used when calling startWithApiKey + When I run "AppAndDeviceAttributesStartWithApiKeyScenario" + And I wait to receive an error + Then the error is valid for the error reporting API + And the error "Bugsnag-API-Key" header equals "12312312312312312312312312312312" + + And the error payload field "events.0.app.releaseStage" equals "beta2" diff --git a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj index bb7ae576a..118ada868 100644 --- a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj @@ -115,10 +115,12 @@ 8AF6FD7A225E3FA00056EF9E /* ResumeSessionOOMScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF6FD79225E3FA00056EF9E /* ResumeSessionOOMScenario.m */; }; 8AF8FCAC22BD1E5400A967CA /* UnhandledInternalNotifyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF8FCAB22BD1E5400A967CA /* UnhandledInternalNotifyScenario.swift */; }; 8AF8FCAE22BD23BA00A967CA /* HandledInternalNotifyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF8FCAD22BD23BA00A967CA /* HandledInternalNotifyScenario.swift */; }; + 967F6F1229B2236A0054EED8 /* InternalWorkingsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F6F1129B2236A0054EED8 /* InternalWorkingsScenario.swift */; }; A1117E552535A59100014FDA /* OOMLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E542535A59100014FDA /* OOMLoadScenario.swift */; }; A1117E572535B22300014FDA /* OOMAutoDetectErrorsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E562535B22300014FDA /* OOMAutoDetectErrorsScenario.swift */; }; A1117E592535B29800014FDA /* OOMEnabledErrorTypesScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E582535B29800014FDA /* OOMEnabledErrorTypesScenario.swift */; }; A1117E5B2536036400014FDA /* OOMSessionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1117E5A2536036400014FDA /* OOMSessionScenario.swift */; }; + AA4C7F1529AEA0C4009B09A9 /* BugsnagWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4C7F1429AEA0C4009B09A9 /* BugsnagWrapper.swift */; }; AA6ACD1C2773E0B3006464C4 /* UserFromConfigScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6ACD1B2773E0B3006464C4 /* UserFromConfigScenario.swift */; }; AA6ACD202773E3F0006464C4 /* UserInfoScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6ACD1F2773E3F0006464C4 /* UserInfoScenario.swift */; }; AAFEF9EA26EB533800980A10 /* NetworkBreadcrumbsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFEF9E926EB533800980A10 /* NetworkBreadcrumbsScenario.swift */; }; @@ -310,10 +312,12 @@ 8AF6FD79225E3FA00056EF9E /* ResumeSessionOOMScenario.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ResumeSessionOOMScenario.m; sourceTree = ""; }; 8AF8FCAB22BD1E5400A967CA /* UnhandledInternalNotifyScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnhandledInternalNotifyScenario.swift; sourceTree = ""; }; 8AF8FCAD22BD23BA00A967CA /* HandledInternalNotifyScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandledInternalNotifyScenario.swift; sourceTree = ""; }; + 967F6F1129B2236A0054EED8 /* InternalWorkingsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalWorkingsScenario.swift; sourceTree = ""; }; A1117E542535A59100014FDA /* OOMLoadScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMLoadScenario.swift; sourceTree = ""; }; A1117E562535B22300014FDA /* OOMAutoDetectErrorsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMAutoDetectErrorsScenario.swift; sourceTree = ""; }; A1117E582535B29800014FDA /* OOMEnabledErrorTypesScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMEnabledErrorTypesScenario.swift; sourceTree = ""; }; A1117E5A2536036400014FDA /* OOMSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OOMSessionScenario.swift; sourceTree = ""; }; + AA4C7F1429AEA0C4009B09A9 /* BugsnagWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugsnagWrapper.swift; sourceTree = ""; }; AA6ACD1B2773E0B3006464C4 /* UserFromConfigScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFromConfigScenario.swift; sourceTree = ""; }; AA6ACD1F2773E3F0006464C4 /* UserInfoScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoScenario.swift; sourceTree = ""; }; AAFEF9E926EB533800980A10 /* NetworkBreadcrumbsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkBreadcrumbsScenario.swift; sourceTree = ""; }; @@ -414,6 +418,7 @@ 8AB8865720404DD30003E444 = { isa = PBXGroup; children = ( + AA4C7F1329AEA060009B09A9 /* utils */, 8AB8866220404DD30003E444 /* iOSTestApp */, F42953DE2BB41023C0B07F41 /* scenarios */, 8AB8866120404DD30003E444 /* Products */, @@ -442,6 +447,15 @@ path = iOSTestApp; sourceTree = ""; }; + AA4C7F1329AEA060009B09A9 /* utils */ = { + isa = PBXGroup; + children = ( + AA4C7F1429AEA0C4009B09A9 /* BugsnagWrapper.swift */, + ); + name = utils; + path = ../shared/utils; + sourceTree = ""; + }; D018DCE3210BCB4D0B8FA6D2 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -640,6 +654,7 @@ 010BAAF22833CE570003FF36 /* UserPersistencePersistUserClientScenario.m */, 010BAAF82833CE570003FF36 /* UserPersistencePersistUserScenario.m */, E700EE49247D1164008CFFB6 /* UserSessionOverrideScenario.swift */, + 967F6F1129B2236A0054EED8 /* InternalWorkingsScenario.swift */, ); name = scenarios; path = ../shared/scenarios; @@ -744,6 +759,7 @@ files = ( 01221E55282E5538008095C3 /* MaxPersistedSessionsScenario.m in Sources */, E700EE7B247D7A1F008CFFB6 /* SIGSEGVScenario.m in Sources */, + 967F6F1229B2236A0054EED8 /* InternalWorkingsScenario.swift in Sources */, 010BAB0D2833CE570003FF36 /* OOMWillTerminateScenario.m in Sources */, 010BAB252833D0070003FF36 /* AppHangDisabledScenario.swift in Sources */, E75040A02478019D005D33BD /* AutoDetectFalseHandledScenario.swift in Sources */, @@ -783,6 +799,7 @@ E700EE65247D6C08008CFFB6 /* OnSendCallbackRemovalScenario.m in Sources */, 8A38C5D124094D7B00BC4463 /* DiscardedBreadcrumbTypeScenario.swift in Sources */, 010BAB292833D08F0003FF36 /* BareboneTestUnhandledErrorScenario.swift in Sources */, + AA4C7F1529AEA0C4009B09A9 /* BugsnagWrapper.swift in Sources */, 01F6B75F2832757F00B75C5D /* OversizedHandledErrorScenario.swift in Sources */, 010BAB002833CE570003FF36 /* DisableNSExceptionScenario.m in Sources */, 8AEEBBD020FC9E1D00C60763 /* AutoSessionMixedEventsScenario.m in Sources */, diff --git a/features/fixtures/ios/iOSTestApp/Info.plist b/features/fixtures/ios/iOSTestApp/Info.plist index 272ced950..b24cc7ebe 100644 --- a/features/fixtures/ios/iOSTestApp/Info.plist +++ b/features/fixtures/ios/iOSTestApp/Info.plist @@ -57,9 +57,9 @@ endpoints notify - http://bs-local.com:9339/notify + http://example.com/notify sessions - http://bs-local.com:9339/sessions + http://example.com/sessions releaseStage beta2 diff --git a/features/fixtures/ios/iOSTestApp/ViewController.swift b/features/fixtures/ios/iOSTestApp/ViewController.swift index 694f12841..b2ed6df4e 100644 --- a/features/fixtures/ios/iOSTestApp/ViewController.swift +++ b/features/fixtures/ios/iOSTestApp/ViewController.swift @@ -9,11 +9,16 @@ import UIKit import os +class FixtureConfig: Codable { + var maze_address: String +} + class ViewController: UIViewController { @IBOutlet var scenarioNameField : UITextField! @IBOutlet var scenarioMetaDataField : UITextField! @IBOutlet var apiKeyField: UITextField! + var mazeRunnerAddress: String = ""; override func viewDidLoad() { super.viewDidLoad() @@ -30,7 +35,7 @@ class ViewController: UIViewController { log("Starting Bugsnag for scenario: \(Scenario.current!)") Scenario.current!.startBugsnag() } - + log("Running scenario: \(Scenario.current!)") Scenario.current!.run() } @@ -46,6 +51,43 @@ class ViewController: UIViewController { Scenario.clearPersistentData() } + func loadMazeRunnerAddress() -> String { + + let bsAddress = "http://bs-local.com:9339" + + // Only iOS 12 and above will run on BitBar for now + if #available(iOS 12.0, *) {} else { + return bsAddress; + } + + for _ in 1...60 { + let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + + log("Reading Maze Runner address from fixture_config.json") + do { + let fileUrl = URL(fileURLWithPath: "fixture_config", + relativeTo: documentsUrl).appendingPathExtension("json") + let savedData = try Data(contentsOf: fileUrl) + if let contents = String(data: savedData, encoding: .utf8) { + let decoder = JSONDecoder() + let jsonData = contents.data(using: .utf8) + let config = try decoder.decode(FixtureConfig.self, from: jsonData!) + let address = "http://" + config.maze_address + log("Using Maze Runner address: " + address) + return address + } + } + catch let error as NSError { + log("Failed to read fixture_config.json: \(error)") + } + log("Waiting for fixture_config.json to appear") + sleep(1) + } + + log("Unable to read from fixture_config.json, defaulting to BrowserStack environment") + return bsAddress; + } + internal func prepareScenario() { var config: BugsnagConfiguration? if (apiKeyField.text!.count > 0) { @@ -55,12 +97,14 @@ class ViewController: UIViewController { UserDefaults.standard.setValue(apiKey, forKey: "apiKey") config = BugsnagConfiguration(apiKeyField.text!) } - - Scenario.createScenarioNamed(scenarioNameField.text!, withConfig: config) + + Scenario.createScenarioNamed(scenarioNameField.text!, + withConfig: config) Scenario.current!.eventMode = scenarioMetaDataField.text } @IBAction func executeCommand(_ sender: Any) { + Scenario.baseMazeAddress = loadMazeRunnerAddress() Scenario.executeMazeRunnerCommand { _, scenarioName, eventMode in self.scenarioNameField.text = scenarioName self.scenarioMetaDataField.text = eventMode diff --git a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj index ee3c69a6b..dc8b700aa 100644 --- a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj @@ -184,6 +184,8 @@ 01FA9EC626D64FFF0059FF4A /* AppHangInTerminationScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA9EC526D64FFF0059FF4A /* AppHangInTerminationScenario.swift */; }; 8A096DF827C7E63A00DB6ECC /* CxxUnexpectedScenario.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8A096DF727C7E63A00DB6ECC /* CxxUnexpectedScenario.mm */; }; 8A096DFA27C7E6D800DB6ECC /* CxxBareThrowScenario.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8A096DF927C7E6D800DB6ECC /* CxxBareThrowScenario.mm */; }; + 967F6F1629B767CE0054EED8 /* InternalWorkingsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F6F1529B767CE0054EED8 /* InternalWorkingsScenario.swift */; }; + AA4C7F1829AEA31D009B09A9 /* BugsnagWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4C7F1729AEA31D009B09A9 /* BugsnagWrapper.swift */; }; AA6ACD1A2773E098006464C4 /* UserFromConfigScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6ACD192773E098006464C4 /* UserFromConfigScenario.swift */; }; AA6ACD1E2773E39C006464C4 /* UserInfoScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6ACD1D2773E39C006464C4 /* UserInfoScenario.swift */; }; CB0AE1F3287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AE1F2287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift */; }; @@ -390,6 +392,8 @@ 5C65BFC9838298CFA8A35072 /* Pods_macOSTestApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_macOSTestApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A096DF727C7E63A00DB6ECC /* CxxUnexpectedScenario.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CxxUnexpectedScenario.mm; sourceTree = ""; }; 8A096DF927C7E6D800DB6ECC /* CxxBareThrowScenario.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CxxBareThrowScenario.mm; sourceTree = ""; }; + 967F6F1529B767CE0054EED8 /* InternalWorkingsScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalWorkingsScenario.swift; sourceTree = ""; }; + AA4C7F1729AEA31D009B09A9 /* BugsnagWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugsnagWrapper.swift; sourceTree = ""; }; AA6ACD192773E098006464C4 /* UserFromConfigScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFromConfigScenario.swift; sourceTree = ""; }; AA6ACD1D2773E39C006464C4 /* UserInfoScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoScenario.swift; sourceTree = ""; }; CB0AE1F2287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnSendErrorCallbackFeatureFlagsScenario.swift; sourceTree = ""; }; @@ -485,6 +489,7 @@ 010BAB4B2833D34A0003FF36 /* HandledErrorValidReleaseStageScenario.swift */, 01F47C28254B1B2C00B184AD /* HandledExceptionScenario.swift */, 01F47C55254B1B2E00B184AD /* HandledInternalNotifyScenario.swift */, + 967F6F1529B767CE0054EED8 /* InternalWorkingsScenario.swift */, 01847DCC26443DF000ADA4C7 /* InvalidCrashReportScenario.m */, 01B6BB7125D56CBF00FC4DE6 /* LastRunInfoScenario.swift */, 01F47C23254B1B2C00B184AD /* LoadConfigFromFileAutoScenario.swift */, @@ -607,6 +612,7 @@ 0176C09F254AE81B0066E0F3 = { isa = PBXGroup; children = ( + AA4C7F1629AEA1F2009B09A9 /* utils */, 0176C0AA254AE81B0066E0F3 /* macOSTestApp */, 01452234254AFCD600D436AA /* scenarios */, 0176C0A9254AE81B0066E0F3 /* Products */, @@ -648,6 +654,15 @@ path = Pods; sourceTree = ""; }; + AA4C7F1629AEA1F2009B09A9 /* utils */ = { + isa = PBXGroup; + children = ( + AA4C7F1729AEA31D009B09A9 /* BugsnagWrapper.swift */, + ); + name = utils; + path = ../shared/utils; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -921,6 +936,7 @@ 01F47D20254B1B3100B184AD /* ObjCMsgSendScenario.m in Sources */, 01F47CD4254B1B3100B184AD /* AsyncSafeThreadScenario.m in Sources */, 01ECBCF525A7522000FC0678 /* OnErrorOverwriteUnhandledTrueScenario.swift in Sources */, + AA4C7F1829AEA31D009B09A9 /* BugsnagWrapper.swift in Sources */, 01DCB82D279868160048640A /* ConcurrentCrashesScenario.mm in Sources */, 0123189C275921590007EFD7 /* RecrashScenarios.mm in Sources */, CB0AE1F3287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift in Sources */, @@ -940,6 +956,7 @@ 017D9D002833C81100B0AA87 /* DisableSignalsExceptionScenario.m in Sources */, 01F47CF7254B1B3100B184AD /* OnSendErrorCallbackCrashScenario.swift in Sources */, 01F47CDF254B1B3100B184AD /* AbortScenario.m in Sources */, + 967F6F1629B767CE0054EED8 /* InternalWorkingsScenario.swift in Sources */, 010BAB672833D34A0003FF36 /* HandledErrorValidReleaseStageScenario.swift in Sources */, 01F47CD1254B1B3100B184AD /* ManualContextClientScenario.swift in Sources */, 010BAB732833D34A0003FF36 /* AppAndDeviceAttributesStartWithApiKeyScenario.swift in Sources */, diff --git a/features/fixtures/macos/macOSTestApp/Info.plist b/features/fixtures/macos/macOSTestApp/Info.plist index cfd84f3e1..0389cd5ea 100644 --- a/features/fixtures/macos/macOSTestApp/Info.plist +++ b/features/fixtures/macos/macOSTestApp/Info.plist @@ -9,9 +9,9 @@ endpoints notify - http://bs-local.com:9339/notify + http://example.com/notify sessions - http://bs-local.com:9339/sessions + http://example.com/sessions releaseStage beta2 diff --git a/features/fixtures/macos/macOSTestApp/MainWindowController.m b/features/fixtures/macos/macOSTestApp/MainWindowController.m index 3bc1254b9..5b88b35e8 100644 --- a/features/fixtures/macos/macOSTestApp/MainWindowController.m +++ b/features/fixtures/macos/macOSTestApp/MainWindowController.m @@ -92,6 +92,7 @@ - (IBAction)useDashboardEndpoints:(id)sender { } - (IBAction)executeMazeRunnerCommand:(id)sender { + Scenario.baseMazeAddress = @"http://localhost:9339"; [Scenario executeMazeRunnerCommand:^(NSString *action, NSString *scenarioName, NSString *eventMode){ self.scenarioName = scenarioName; self.scenarioMetadata = eventMode; diff --git a/features/fixtures/shared/scenarios/AppAndDeviceAttributesStartWithApiKeyScenario.swift b/features/fixtures/shared/scenarios/AppAndDeviceAttributesStartWithApiKeyScenario.swift index 5ba622ec5..b3f4a3363 100644 --- a/features/fixtures/shared/scenarios/AppAndDeviceAttributesStartWithApiKeyScenario.swift +++ b/features/fixtures/shared/scenarios/AppAndDeviceAttributesStartWithApiKeyScenario.swift @@ -4,7 +4,7 @@ class AppAndDeviceAttributesStartWithApiKeyScenario: Scenario { override func startBugsnag() { - Bugsnag.start(withApiKey: "12312312312312312312312312312312") + BugsnagWrapper.start(withApiKey: "12312312312312312312312312312312") super.startBugsnag() } diff --git a/features/fixtures/shared/scenarios/InternalWorkingsScenario.swift b/features/fixtures/shared/scenarios/InternalWorkingsScenario.swift new file mode 100644 index 000000000..e5d9e7847 --- /dev/null +++ b/features/fixtures/shared/scenarios/InternalWorkingsScenario.swift @@ -0,0 +1,36 @@ +// +// IsStartedScenario.swift +// iOSTestApp +// +// Created by Robert B on 03/03/2023. +// Copyright © 2023 Bugsnag. All rights reserved. +// + +import Foundation + +@objc class InternalWorkingsScenario: Scenario { + + override func startBugsnag() { + verifyBugsnagIsNotStarted() + super.startBugsnag() + } + + override func run() { + verifyBugsnagIsStarted() + reportStatusOk() + } + + private func verifyBugsnagIsStarted() { + assert(Bugsnag.isStarted(), "Bugsnag should be started") + } + + private func verifyBugsnagIsNotStarted() { + assert(!Bugsnag.isStarted(), "Bugsnag should not be started initially") + } + + private func reportStatusOk() { + Bugsnag.notify(NSException(name: NSExceptionName("InternalWorkingsScenario"), + reason: "All Clear!", + userInfo: nil)) + } +} diff --git a/features/fixtures/shared/scenarios/LoadConfigFromFileAutoScenario.swift b/features/fixtures/shared/scenarios/LoadConfigFromFileAutoScenario.swift index 027b8ad3b..98824c5c4 100644 --- a/features/fixtures/shared/scenarios/LoadConfigFromFileAutoScenario.swift +++ b/features/fixtures/shared/scenarios/LoadConfigFromFileAutoScenario.swift @@ -10,7 +10,7 @@ class LoadConfigFromFileAutoScenarioError : Error { @objc class LoadConfigFromFileAutoScenario: Scenario { override func startBugsnag() { - Bugsnag.start() + BugsnagWrapper.start() } override func run() { diff --git a/features/fixtures/shared/scenarios/LoadConfigFromFileScenario.swift b/features/fixtures/shared/scenarios/LoadConfigFromFileScenario.swift index 6f086548d..3edc16da4 100644 --- a/features/fixtures/shared/scenarios/LoadConfigFromFileScenario.swift +++ b/features/fixtures/shared/scenarios/LoadConfigFromFileScenario.swift @@ -11,7 +11,7 @@ class LaunchError : Error { override func startBugsnag() { config = BugsnagConfiguration.loadConfig() - Bugsnag.start(with: config) + _ = BugsnagWrapper.start(with: config) } override func run() { diff --git a/features/fixtures/shared/scenarios/NetworkBreadcrumbsScenario.swift b/features/fixtures/shared/scenarios/NetworkBreadcrumbsScenario.swift index 101e370e2..62c4b05a0 100644 --- a/features/fixtures/shared/scenarios/NetworkBreadcrumbsScenario.swift +++ b/features/fixtures/shared/scenarios/NetworkBreadcrumbsScenario.swift @@ -10,18 +10,12 @@ import Foundation @available(iOS 10.0, macOS 10.12, *) class NetworkBreadcrumbsScenario : Scenario { - - lazy var baseURL: URL = { - var components = URLComponents(string: Scenario.mazeRunnerURL.absoluteString)! - components.port = 9340 // `/reflect` listens on a different port :-(( - return components.url! - }() - + override func startBugsnag() { config.autoTrackSessions = false; config.add(BugsnagNetworkRequestPlugin()) config.addOnBreadcrumb { - ($0.metadata["url"] as? String ?? "").hasPrefix(self.baseURL.absoluteString) + ($0.metadata["url"] as? String ?? "").hasPrefix(Scenario.baseMazeAddress) } super.startBugsnag() @@ -38,7 +32,7 @@ class NetworkBreadcrumbsScenario : Scenario { } func query(string: String) { - let url = URL(string: string, relativeTo: baseURL)! + let url = URL(string: Scenario.baseMazeAddress + string)! let semaphore = DispatchSemaphore(value: 0) let task = URLSession.shared.dataTask(with: url) {(data, response, error) in diff --git a/features/fixtures/shared/scenarios/Scenario.h b/features/fixtures/shared/scenarios/Scenario.h index 31e5e02eb..387df60ef 100644 --- a/features/fixtures/shared/scenarios/Scenario.h +++ b/features/fixtures/shared/scenarios/Scenario.h @@ -15,12 +15,12 @@ void markErrorHandledCallback(const BSG_KSCrashReportWriter *writer); @interface Scenario : NSObject -@property (class, readonly) NSURL *mazeRunnerURL; - @property (strong, nonatomic, nonnull) BugsnagConfiguration *config; -+ (Scenario *)createScenarioNamed:(NSString *)className withConfig:(nullable BugsnagConfiguration *)config; - ++ (Scenario *)createScenarioNamed:(NSString *)className + withConfig:(nullable BugsnagConfiguration *)config; + +@property (class, readwrite) NSString *baseMazeAddress; @property (class, readonly, nullable) Scenario *currentScenario; - (instancetype)initWithConfig:(nullable BugsnagConfiguration *)config; diff --git a/features/fixtures/shared/scenarios/Scenario.m b/features/fixtures/shared/scenarios/Scenario.m index 6db35ec47..32af23ba5 100644 --- a/features/fixtures/shared/scenarios/Scenario.m +++ b/features/fixtures/shared/scenarios/Scenario.m @@ -7,14 +7,11 @@ #import #if TARGET_OS_IOS -#define MAZE_RUNNER_URL "http://bs-local.com:9339" #define SWIFT_MODULE "iOSTestApp" #elif TARGET_OS_OSX -#define MAZE_RUNNER_URL "http://localhost:9339" #define SWIFT_MODULE "macOSTestApp" #elif TARGET_OS_WATCH #import "watchos_maze_host.h" -#define MAZE_RUNNER_URL "http://" WATCHOS_MAZE_HOST ":9339" #define SWIFT_MODULE "watchOSTestApp_WatchKit_Extension" #else #error Unsupported TARGET_OS @@ -35,6 +32,7 @@ void markErrorHandledCallback(const BSG_KSCrashReportWriter *writer) { // MARK: - static Scenario *theScenario; +static NSString *theBaseMazeAddress; #if !TARGET_OS_WATCH static char ksLogPath[PATH_MAX]; @@ -71,10 +69,6 @@ + (void)load { }]; } -+ (NSURL *)mazeRunnerURL { - return [NSURL URLWithString:@MAZE_RUNNER_URL]; -} - + (Scenario *)createScenarioNamed:(NSString *)className withConfig:(BugsnagConfiguration *)config { Class class = NSClassFromString(className) ?: NSClassFromString([@SWIFT_MODULE "." stringByAppendingString:className]); @@ -90,14 +84,22 @@ + (Scenario *)currentScenario { return theScenario; } ++ (NSString *)baseMazeAddress { + return theBaseMazeAddress; +} + ++ (void)setBaseMazeAddress:(NSString *)baseMazeAddress { + theBaseMazeAddress = baseMazeAddress; +} + - (instancetype)initWithConfig:(BugsnagConfiguration *)config { if (self = [super init]) { if (config) { _config = config; } else { _config = [[BugsnagConfiguration alloc] initWithApiKey:@"12312312312312312312312312312312"]; - _config.endpoints.notify = @MAZE_RUNNER_URL "/notify"; - _config.endpoints.sessions = @MAZE_RUNNER_URL "/sessions"; + _config.endpoints.notify = [NSString stringWithFormat:@"%@/notify", theBaseMazeAddress]; + _config.endpoints.sessions = [NSString stringWithFormat:@"%@/sessions", theBaseMazeAddress]; } #if !TARGET_OS_WATCH _config.enabledErrorTypes.ooms = NO; @@ -218,8 +220,8 @@ + (void)clearPersistentData { + (void)executeMazeRunnerCommand:(void (^)(NSString *action, NSString *scenarioName, NSString *scenarioMode))preHandler { NSLog(@"%s", __PRETTY_FUNCTION__); NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - - NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@MAZE_RUNNER_URL "/command"]]; + NSString *commandEndpoint = [NSString stringWithFormat:@"%@/command", theBaseMazeAddress]; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:commandEndpoint]]; [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (![response isKindOfClass:[NSHTTPURLResponse class]] || [(NSHTTPURLResponse *)response statusCode] != 200) { NSLog(@"%s request failed with %@", __PRETTY_FUNCTION__, response ?: error); @@ -264,7 +266,6 @@ + (void)runScenario:(NSString *)scenarioName eventMode:(NSString *)eventMode { [self startBugsnagForScenario:scenarioName eventMode:eventMode]; - NSLog(@"Running scenario \"%@\"", NSStringFromClass([theScenario class])); [theScenario run]; } @@ -274,7 +275,7 @@ + (void)startBugsnagForScenario:(NSString *)scenarioName eventMode:(NSString *)e theScenario = [Scenario createScenarioNamed:scenarioName withConfig:nil]; theScenario.eventMode = eventMode; - NSLog(@"Starting scenario \"%@\"", NSStringFromClass([theScenario class])); + NSLog(@"Starting scenario \"%@\"", NSStringFromClass([self class])); [theScenario startBugsnag]; } diff --git a/features/fixtures/shared/utils/BugsnagWrapper.swift b/features/fixtures/shared/utils/BugsnagWrapper.swift new file mode 100644 index 000000000..cd51e3d49 --- /dev/null +++ b/features/fixtures/shared/utils/BugsnagWrapper.swift @@ -0,0 +1,37 @@ +// +// BugsnagWrapper.swift +// iOSTestApp +// +// Created by Steve Kirkland-Walton on 23/02/2023. +// Copyright © 2023 Bugsnag. All rights reserved. +// + +import Foundation + +class BugsnagWrapper : Bugsnag { + + static var plistNotifyEndpoint: String = ""; + static var plistSessionsEndpoint: String = ""; + + override class func start(with configuration: BugsnagConfiguration) -> BugsnagClient { + + // Store the endpoint values read from the plist + plistNotifyEndpoint = configuration.endpoints.notify; + plistSessionsEndpoint = configuration.endpoints.sessions; + + NSLog("Plist notify endpoint: %@", plistNotifyEndpoint); + NSLog("Plist sessions endpoint: %@", plistSessionsEndpoint); + + if (plistNotifyEndpoint != "http://example.com/notify" + || plistSessionsEndpoint != "http://example.com/sessions") { + + fatalError("Endpoint configuration read from plist is not as expected"); + } + + // Overwrite the configuration endpoints + configuration.endpoints.notify = String(format: "%@/notify", Scenario.baseMazeAddress); + configuration.endpoints.sessions = String(format: "%@/sessions", Scenario.baseMazeAddress); + + return Bugsnag.start(with: configuration); + } +} diff --git a/features/internal_workings.feature b/features/internal_workings.feature new file mode 100644 index 000000000..8d2ba683b --- /dev/null +++ b/features/internal_workings.feature @@ -0,0 +1,10 @@ +Feature: Bugsnag's internal workings + + Background: + Given I clear all persistent data + + Scenario: Bugsnag library works as it should internally + When I run "InternalWorkingsScenario" + And I wait to receive a session + And I wait to receive an error + And the exception "message" equals "All Clear!" diff --git a/features/session_tracking.feature b/features/session_tracking.feature index 8712c1205..941727950 100644 --- a/features/session_tracking.feature +++ b/features/session_tracking.feature @@ -17,7 +17,7 @@ Feature: Session Tracking And the session payload field "device.osName" equals the platform-dependent string: | ios | iOS | | macos | Mac OS | - And the session payload field "device.model" matches the test device model + And the session payload field "device.model" matches the regex "[Macmini|iPhone]1?\d,\d" And the session payload field "sessions.0.id" is a UUID And the session payload field "sessions.0.startedAt" is a parsable timestamp in seconds diff --git a/features/steps/cocoa_steps.rb b/features/steps/cocoa_steps.rb index d84417bac..e94c3f054 100644 --- a/features/steps/cocoa_steps.rb +++ b/features/steps/cocoa_steps.rb @@ -44,45 +44,6 @@ def request_fields_are_equal(key, index_a, index_b) ) end -def check_device_model(field, list) - internal_names = { - 'iPhone 6' => %w[iPhone7,2], - 'iPhone 6 Plus' => %w[iPhone7,1], - 'iPhone 6S' => %w[iPhone8,1], - 'iPhone 7' => %w[iPhone9,1 iPhone9,2 iPhone9,3 iPhone9,4], - 'iPhone 8' => %w[iPhone10,1 iPhone10,4], - 'iPhone 8 Plus' => %w[iPhone10,2 iPhone10,5], - 'iPhone 11' => %w[iPhone12,1], - 'iPhone 11 Pro' => %w[iPhone12,3], - 'iPhone 11 Pro Max' => %w[iPhone12,5], - 'iPhone 14' => %w[iPhone14,7], - 'iPhone 14 Plus' => %w[iPhone14,8], - 'iPhone 14 Pro' => %w[iPhone15,2], - 'iPhone 14 Pro Max' => %w[iPhone15,3], - 'iPhone X' => %w[iPhone10,3 iPhone10,6], - 'iPhone XR' => %w[iPhone11,8], - 'iPhone XS' => %w[iPhone11,2 iPhone11,4 iPhone11,8] - } - $logger.info Maze.config.capabilities - - expected_model = Maze.config.capabilities['deviceName'] - msg = "Model '#{expected_model}' found by Appium not present in internal_names hash" - Maze.check.true(internal_names.has_key?(expected_model), msg) - - valid_models = internal_names[expected_model] - device_model = Maze::Helper.read_key_path(list.current[:body], field) - Maze.check.true(valid_models.include?(device_model), - "'#{device_model}' did not match any of the list of valid models #{valid_models}") -end - -Then('the error payload field {string} matches the test device model') do |field| - check_device_model field, Maze::Server.errors if Maze::Helper.get_current_platform.eql?('ios') -end - -Then('the session payload field {string} matches the test device model') do |field| - check_device_model field, Maze::Server.sessions if Maze::Helper.get_current_platform.eql?('ios') -end - Then('the error is valid for the error reporting API') do platform = Maze::Helper.get_current_platform case platform diff --git a/features/support/env.rb b/features/support/env.rb index 46c4fcb43..e18ed88f9 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -61,10 +61,22 @@ def enable_malloc_scribble $app_env.merge! env end +Before('@skip_bitbar') do |scenario| + skip_this_scenario("Skipping scenario") if Maze.config.farm == :bb +end + def skip_below(os, version) skip_this_scenario("Skipping scenario") if Maze::Helper.get_current_platform == os and Maze.config.os_version < version end +def skip_between(os, version_lo, version_hi) + skip_this_scenario("Skipping scenario") if Maze::Helper.get_current_platform == os and Maze.config.os_version >= version_lo and Maze.config.os_version <= version_hi +end + +Before('@skip_ios_16') do |scenario| + skip_between('ios', 16, 16.99) +end + Before('@skip_below_ios_11') do |scenario| skip_below('ios', 11) end @@ -121,12 +133,6 @@ def skip_below(os, version) FileUtils.mv '/tmp/kscrash.log', path end when 'ios' - # get_log can be slow (1 or 2 seconds) on device farms - if scenario.failed? || Maze.config.farm == :local - File.open(File.join(path, 'syslog.log'), 'wb') do |file| - Maze.driver.get_log('syslog').each { |entry| file.puts entry.message } - end - end begin data = Maze.driver.pull_file '@com.bugsnag.iOSTestApp/Documents/kscrash.log' File.open(File.join(path, 'kscrash.log'), 'wb') { |file| file << data }