From ce32e2ccef8831bbdb11e774c1ede9a744a3c57a Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 15 Dec 2022 17:17:43 +0100 Subject: [PATCH 1/5] Initial cocoa v8 support --- RNSentry.podspec | 4 +- ios/RNSentry.mm | 5 +- ios/SentryOptions+RNOptions.h | 13 ++ ios/SentryOptions+RNOptions.mm | 210 +++++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 ios/SentryOptions+RNOptions.h create mode 100644 ios/SentryOptions+RNOptions.mm diff --git a/RNSentry.podspec b/RNSentry.podspec index 81e1db2980..a63dd8eb46 100644 --- a/RNSentry.podspec +++ b/RNSentry.podspec @@ -19,9 +19,9 @@ Pod::Spec.new do |s| s.preserve_paths = '*.js' s.dependency 'React-Core' - s.dependency 'Sentry/HybridSDK', '7.31.3' + s.dependency 'Sentry/HybridSDK', '8.0.0-beta.4' - s.source_files = 'ios/RNSentry.{h,mm}' + s.source_files = 'ios/*.{h,mm}' s.public_header_files = 'ios/RNSentry.h' # This guard prevent to install the dependencies when we run `pod install` in the old architecture. diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index 9bb990049c..3a2955f517 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -9,6 +9,7 @@ #import #import #import +#import "SentryOptions+RNOptions.h" // Thanks to this guard, we won't import this header when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED @@ -73,7 +74,7 @@ + (BOOL)requiresMainQueueSetup { [mutableOptions removeObjectForKey:@"tracesSampleRate"]; [mutableOptions removeObjectForKey:@"tracesSampler"]; - SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:mutableOptions didFailWithError:&error]; + SentryOptions *sentryOptions = [[SentryOptions alloc] initWithRNOptions:options didFailWithError:&error]; if (error) { reject(@"SentryReactNative", error.localizedDescription, error); return; @@ -99,7 +100,7 @@ + (BOOL)requiresMainQueueSetup { #endif } - [SentrySDK startWithOptionsObject:sentryOptions]; + [SentrySDK startWithOptions:sentryOptions]; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; diff --git a/ios/SentryOptions+RNOptions.h b/ios/SentryOptions+RNOptions.h new file mode 100644 index 0000000000..61a2fc4e32 --- /dev/null +++ b/ios/SentryOptions+RNOptions.h @@ -0,0 +1,13 @@ +#import "SentryOptions.h" + +@interface SentryOptions (RNOptions) + +/** + * Init SentryOptions. + * @param options React Native Options dictionary + * @return SentryOptions + */ +- (_Nullable instancetype)initWithRNOptions:(NSDictionary *_Nonnull)options + didFailWithError:(NSError *_Nullable *_Nullable)error; + +@end diff --git a/ios/SentryOptions+RNOptions.mm b/ios/SentryOptions+RNOptions.mm new file mode 100644 index 0000000000..f1a43acbcf --- /dev/null +++ b/ios/SentryOptions+RNOptions.mm @@ -0,0 +1,210 @@ +#import "SentryOptions.h" +#import "SentryDsn.h" +#import "SentryLog.h" +#import "SentryMeta.h" +#import "SentrySdkInfo.h" +#import "SentryOptions+RNOptions.h" + +@implementation SentryOptions (RNOptions) + + +- (_Nullable instancetype)initWithRNOptions:(NSDictionary *)options + didFailWithError:(NSError *_Nullable *_Nullable)error +{ + if (self = [self init]) { + if (![self validateOptions:options didFailWithError:error]) { + [SentryLog + logWithMessage:[NSString stringWithFormat:@"Failed to initialize: %@", *error] + andLevel:kSentryLevelError]; + return nil; + } + } + return self; +} + +/** + * Populates all `SentryOptions` values from `options` dict using fallbacks/defaults if needed. + */ +- (BOOL)validateOptions:(NSDictionary *)options + didFailWithError:(NSError *_Nullable *_Nullable)error +{ + __weak __block SentryOptions* _self = self; + + NSPredicate *isNSString = [NSPredicate predicateWithBlock:^BOOL( + id object, NSDictionary *bindings) { return [object isKindOfClass:[NSString class]]; }]; + + [self setBool:options[@"debug"] block:^(BOOL value) { _self.debug = value; }]; + + NSString *dsn = @""; + if (nil != options[@"dsn"] && [options[@"dsn"] isKindOfClass:[NSString class]]) { + dsn = options[@"dsn"]; + } + + self.parsedDsn = [[SentryDsn alloc] initWithString:dsn didFailWithError:error]; + + if ([options[@"release"] isKindOfClass:[NSString class]]) { + self.releaseName = options[@"release"]; + } + + if ([options[@"environment"] isKindOfClass:[NSString class]]) { + self.environment = options[@"environment"]; + } + + if ([options[@"dist"] isKindOfClass:[NSString class]]) { + self.dist = options[@"dist"]; + } + + [self setBool:options[@"enabled"] block:^(BOOL value) { _self.enabled = value; }]; + + [self setBool:options[@"enableCrashHandler"] + block:^(BOOL value) { _self.enableCrashHandler = value; }]; + + if ([options[@"maxBreadcrumbs"] isKindOfClass:[NSNumber class]]) { + self.maxBreadcrumbs = [options[@"maxBreadcrumbs"] unsignedIntValue]; + } + + [self setBool:options[@"enableNetworkBreadcrumbs"] + block:^(BOOL value) { _self.enableNetworkBreadcrumbs = value; }]; + + if ([options[@"maxCacheItems"] isKindOfClass:[NSNumber class]]) { + self.maxCacheItems = [options[@"maxCacheItems"] unsignedIntValue]; + } + + if ([options[@"integrations"] isKindOfClass:[NSArray class]]) { + self.integrations = [options[@"integrations"] filteredArrayUsingPredicate:isNSString]; + } + + if ([options[@"sampleRate"] isKindOfClass:[NSNumber class]]) { + self.sampleRate = options[@"sampleRate"]; + } + + [self setBool:options[@"enableAutoSessionTracking"] + block:^(BOOL value) { _self.enableAutoSessionTracking = value; }]; + + [self setBool:options[@"enableOutOfMemoryTracking"] + block:^(BOOL value) { _self.enableOutOfMemoryTracking = value; }]; + + if ([options[@"sessionTrackingIntervalMillis"] isKindOfClass:[NSNumber class]]) { + self.sessionTrackingIntervalMillis = + [options[@"sessionTrackingIntervalMillis"] unsignedIntValue]; + } + + [self setBool:options[@"attachStacktrace"] + block:^(BOOL value) { _self.attachStacktrace = value; }]; + + [self setBool:options[@"stitchAsyncCode"] + block:^(BOOL value) { _self.stitchAsyncCode = value; }]; + + if ([options[@"maxAttachmentSize"] isKindOfClass:[NSNumber class]]) { + self.maxAttachmentSize = [options[@"maxAttachmentSize"] unsignedIntValue]; + } + + [self setBool:options[@"sendDefaultPii"] + block:^(BOOL value) { _self.sendDefaultPii = value; }]; + + [self setBool:options[@"enableAutoPerformanceTracking"] + block:^(BOOL value) { _self.enableAutoPerformanceTracing = value; }]; + + [self setBool:options[@"enableCaptureFailedRequests"] + block:^(BOOL value) { _self.enableCaptureFailedRequests = value; }]; + +#if SENTRY_HAS_UIKIT + [self setBool:options[@"enableUIViewControllerTracking"] + block:^(BOOL value) { _self.enableUIViewControllerTracing = value; }]; + + [self setBool:options[@"attachScreenshot"] + block:^(BOOL value) { _self.attachScreenshot = value; }]; + + [self setBool:options[@"attachViewHierarchy"] + block:^(BOOL value) { _self.attachViewHierarchy = value; }]; + + [self setBool:options[@"enableUserInteractionTracing"] + block:^(BOOL value) { _self.enableUserInteractionTracing = value; }]; + + if ([options[@"idleTimeout"] isKindOfClass:[NSNumber class]]) { + self.idleTimeout = [options[@"idleTimeout"] doubleValue]; + } + + [self setBool:options[@"enablePreWarmedAppStartTracking"] + block:^(BOOL value) { _self.enablePreWarmedAppStartTracing = value; }]; +#endif + + [self setBool:options[@"enableAppHangTracking"] + block:^(BOOL value) { _self.enableAppHangTracking = value; }]; + + if ([options[@"appHangTimeoutInterval"] isKindOfClass:[NSNumber class]]) { + self.appHangTimeoutInterval = [options[@"appHangTimeoutInterval"] doubleValue]; + } + + [self setBool:options[@"enableNetworkTracking"] + block:^(BOOL value) { _self.enableNetworkTracking = value; }]; + + [self setBool:options[@"enableFileIOTracking"] + block:^(BOOL value) { _self.enableFileIOTracing = value; }]; + + if ([options[@"tracesSampleRate"] isKindOfClass:[NSNumber class]]) { + self.tracesSampleRate = options[@"tracesSampleRate"]; + } + + if ([options[@"urlSessionDelegate"] conformsToProtocol:@protocol(NSURLSessionDelegate)]) { + self.urlSessionDelegate = options[@"urlSessionDelegate"]; + } + + [self setBool:options[@"enableSwizzling"] + block:^(BOOL value) { _self.enableSwizzling = value; }]; + + [self setBool:options[@"enableCoreDataTracking"] + block:^(BOOL value) { _self.enableCoreDataTracing = value; }]; + +#if SENTRY_TARGET_PROFILING_SUPPORTED + if ([options[@"profilesSampleRate"] isKindOfClass:[NSNumber class]]) { + self.profilesSampleRate = options[@"profilesSampleRate"]; + } +#endif + + [self setBool:options[@"sendClientReports"] + block:^(BOOL value) { _self.sendClientReports = value; }]; + + [self setBool:options[@"enableAutoBreadcrumbTracking"] + block:^(BOOL value) { _self.enableAutoBreadcrumbTracking = value; }]; + + if ([options[@"tracePropagationTargets"] isKindOfClass:[NSArray class]]) { + self.tracePropagationTargets = options[@"tracePropagationTargets"]; + } + + if ([options[@"failedRequestStatusCodes"] isKindOfClass:[NSArray class]]) { + self.failedRequestStatusCodes = options[@"failedRequestStatusCodes"]; + } + + if ([options[@"failedRequestTargets"] isKindOfClass:[NSArray class]]) { + self.failedRequestTargets = options[@"failedRequestTargets"]; + } + + // SentrySdkInfo already expects a dictionary with {"sdk": {"name": ..., "value": ...}} + // so we're passing the whole options object. + // Note: we should remove this code once the hybrid SDKs move over to the new + // PrivateSentrySDKOnly setter functions. + if ([options[@"sdk"] isKindOfClass:[NSDictionary class]]) { + SentrySdkInfo *defaults = [[SentrySdkInfo alloc] initWithName:SentryMeta.sdkName + andVersion:SentryMeta.versionString]; + SentrySdkInfo *sdkInfo = [[SentrySdkInfo alloc] initWithDict:options orDefaults:defaults]; + SentryMeta.versionString = sdkInfo.version; + SentryMeta.sdkName = sdkInfo.name; + } + + if (nil != error && nil != *error) { + return NO; + } else { + return YES; + } +} + +- (void)setBool:(id)value block:(void (^)(BOOL))block +{ + // Entries in the dictionary can be NSNull. Especially, on React-Native, this can happen. + if (value != nil && ![value isEqual:[NSNull null]]) { + block([value boolValue]); + } +} + +@end From 3f7f0b92184af514d54e63ef920b678a75bec3b2 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 20 Dec 2022 09:38:19 +0100 Subject: [PATCH 2/5] Add SentryOptions+RNOptions tests --- RNSentry.podspec | 2 +- RNSentryPrivate.podspec | 44 ++ ios/{ => Private}/SentryOptions+RNOptions.h | 0 ios/{ => Private}/SentryOptions+RNOptions.mm | 2 +- sample/ios/Podfile | 1 + sample/ios/sampleTests/sampleTests.m | 702 +++++++++++++++++-- 6 files changed, 710 insertions(+), 41 deletions(-) create mode 100644 RNSentryPrivate.podspec rename ios/{ => Private}/SentryOptions+RNOptions.h (100%) rename ios/{ => Private}/SentryOptions+RNOptions.mm (99%) diff --git a/RNSentry.podspec b/RNSentry.podspec index a63dd8eb46..034df8b5c8 100644 --- a/RNSentry.podspec +++ b/RNSentry.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.dependency 'React-Core' s.dependency 'Sentry/HybridSDK', '8.0.0-beta.4' - s.source_files = 'ios/*.{h,mm}' + s.source_files = 'ios/**/*.{h,mm}' s.public_header_files = 'ios/RNSentry.h' # This guard prevent to install the dependencies when we run `pod install` in the old architecture. diff --git a/RNSentryPrivate.podspec b/RNSentryPrivate.podspec new file mode 100644 index 0000000000..e304012458 --- /dev/null +++ b/RNSentryPrivate.podspec @@ -0,0 +1,44 @@ +require 'json' +version = JSON.parse(File.read('package.json'))["version"] + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = 'RNSentryPrivate' + s.version = version + s.license = 'MIT' + s.summary = 'RNSentry Private Library' + s.author = 'Sentry' + s.homepage = "https://github.com/getsentry/sentry-react-native" + s.source = { :git => 'https://github.com/getsentry/sentry-react-native.git', :tag => "#{s.version}"} + + s.description = <<-DESC + Not for public use. + Common APIs for internal Sentry usage. + DESC + + s.preserve_paths = '*.js' + + s.dependency 'React-Core' + + s.ios.deployment_target = "12.4" + s.osx.deployment_target = "10.10" + s.tvos.deployment_target = "12.4" + + s.source_files = 'ios/Private/**/*.{h,mm}' + + # This guard prevent to install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end +end diff --git a/ios/SentryOptions+RNOptions.h b/ios/Private/SentryOptions+RNOptions.h similarity index 100% rename from ios/SentryOptions+RNOptions.h rename to ios/Private/SentryOptions+RNOptions.h diff --git a/ios/SentryOptions+RNOptions.mm b/ios/Private/SentryOptions+RNOptions.mm similarity index 99% rename from ios/SentryOptions+RNOptions.mm rename to ios/Private/SentryOptions+RNOptions.mm index f1a43acbcf..a2ec1042b6 100644 --- a/ios/SentryOptions+RNOptions.mm +++ b/ios/Private/SentryOptions+RNOptions.mm @@ -139,7 +139,7 @@ - (BOOL)validateOptions:(NSDictionary *)options [self setBool:options[@"enableNetworkTracking"] block:^(BOOL value) { _self.enableNetworkTracking = value; }]; - [self setBool:options[@"enableFileIOTracking"] + [self setBool:options[@"enableFileIOTracing"] block:^(BOOL value) { _self.enableFileIOTracing = value; }]; if ([options[@"tracesSampleRate"] isKindOfClass:[NSNumber class]]) { diff --git a/sample/ios/Podfile b/sample/ios/Podfile index 82af1d76b0..0bb5174b9a 100644 --- a/sample/ios/Podfile +++ b/sample/ios/Podfile @@ -25,6 +25,7 @@ target 'sample' do target 'sampleTests' do inherit! :complete # Pods for testing + pod 'RNSentryPrivate', :path => '../../RNSentryPrivate.podspec' end post_install do |installer| diff --git a/sample/ios/sampleTests/sampleTests.m b/sample/ios/sampleTests/sampleTests.m index b7900f51a5..6c5d1e4db9 100644 --- a/sample/ios/sampleTests/sampleTests.m +++ b/sample/ios/sampleTests/sampleTests.m @@ -1,11 +1,9 @@ #import #import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React" +#import "SentryOptions+RNOptions.h" +#import "SentryError.h" +#import "SentryHttpStatusCodeRange.h" +#import "SentrySamplingContext.h" @interface sampleTests : XCTestCase @@ -13,53 +11,679 @@ @interface sampleTests : XCTestCase @implementation sampleTests -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test + +- (void)testEmptyDsn +{ + NSError *error = nil; + SentryOptions *options = [[SentryOptions alloc] initWithRNOptions:@{} didFailWithError:&error]; + + [self assertDsnNil:options andError:error]; +} + +- (void)testInvalidDsnBoolean +{ + NSError *error = nil; + SentryOptions *options = [[SentryOptions alloc] initWithRNOptions:@{ @"dsn" : @YES } + didFailWithError:&error]; + + [self assertDsnNil:options andError:error]; +} + +- (void)assertDsnNil:(SentryOptions *)options andError:(NSError *)error +{ + XCTAssertNil(options.parsedDsn); + XCTAssertEqual(NO, options.debug); + XCTAssertEqual(kSentryErrorInvalidDsnError, error.code); +} + + +- (void)testInvalidDsn +{ + NSError *error = nil; + SentryOptions *options = [[SentryOptions alloc] initWithRNOptions:@{ @"dsn" : @"https://sentry.io" } + didFailWithError:&error]; + XCTAssertEqual(kSentryErrorInvalidDsnError, error.code); + XCTAssertNil(options); +} + +- (void)testRelease { - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; + SentryOptions *options = [self getValidOptions:@{ @"release" : @"abc" }]; + XCTAssertEqualObjects(options.releaseName, @"abc"); +} + +- (void)testSetEmptyRelease +{ + SentryOptions *options = [self getValidOptions:@{ @"release" : @"" }]; + XCTAssertEqualObjects(options.releaseName, @""); +} + +- (void)testSetReleaseToNonString +{ + SentryOptions *options = [self getValidOptions:@{ @"release" : @2 }]; + XCTAssertEqualObjects(options.releaseName, [self buildDefaultReleaseName]); +} + +- (void)testNoReleaseSetUsesDefault +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertEqualObjects(options.releaseName, [self buildDefaultReleaseName]); +} + +- (NSString *)buildDefaultReleaseName +{ + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + return [NSString stringWithFormat:@"%@@%@+%@", infoDict[@"CFBundleIdentifier"], + infoDict[@"CFBundleShortVersionString"], infoDict[@"CFBundleVersion"]]; +} + +- (void)testEnvironment +{ + SentryOptions *options = [self getValidOptions:@{}]; +// XCTAssertNil(options.environment); + + options = [self getValidOptions:@{ @"environment" : @"xxx" }]; + XCTAssertEqualObjects(options.environment, @"xxx"); +} + +- (void)testDist +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertNil(options.dist); + + options = [self getValidOptions:@{ @"dist" : @"hhh" }]; + XCTAssertEqualObjects(options.dist, @"hhh"); +} + +- (void)testValidDebug +{ + [self testDebugWith:@YES expected:YES]; + [self testDebugWith:@"YES" expected:YES]; + [self testDebugWith:@(YES) expected:YES]; +} + +- (void)testInvalidDebug +{ + [self testDebugWith:@"Invalid" expected:NO]; + [self testDebugWith:@NO expected:NO]; + [self testDebugWith:@(NO) expected:NO]; +} + +- (void)testDebugWith:(NSObject *)debugValue expected:(BOOL)expectedDebugValue +{ + NSError *error = nil; + SentryOptions *options = [[SentryOptions alloc] initWithRNOptions:@{ + @"dsn" : @"https://username:password@sentry.io/1", + @"debug" : debugValue } - } - return NO; + didFailWithError:&error]; + + XCTAssertNil(error); + XCTAssertEqual(expectedDebugValue, options.debug); +} + +//- (void)testValidDiagnosticLevel +//{ +// [self testDiagnosticlevelWith:@"none" expected:kSentryLevelNone]; +// [self testDiagnosticlevelWith:@"debug" expected:kSentryLevelDebug]; +// [self testDiagnosticlevelWith:@"info" expected:kSentryLevelInfo]; +// [self testDiagnosticlevelWith:@"warning" expected:kSentryLevelWarning]; +// [self testDiagnosticlevelWith:@"error" expected:kSentryLevelError]; +// [self testDiagnosticlevelWith:@"fatal" expected:kSentryLevelFatal]; +//} + +- (void)testInvalidDiagnosticLevel +{ + [self testDiagnosticlevelWith:@"fatala" expected:kSentryLevelDebug]; + [self testDiagnosticlevelWith:@(YES) expected:kSentryLevelDebug]; +} + +- (void)testDiagnosticlevelWith:(NSObject *)level expected:(SentryLevel)expected +{ + SentryOptions *options = [self getValidOptions:@{ @"diagnosticLevel" : level }]; + + XCTAssertEqual(expected, options.diagnosticLevel); +} + +- (void)testValidEnabled +{ + [self testEnabledWith:@YES expected:YES]; + [self testEnabledWith:@"YES" expected:YES]; + [self testEnabledWith:@(YES) expected:YES]; +} + +- (void)testInvalidEnabled +{ + [self testEnabledWith:@"Invalid" expected:NO]; + [self testEnabledWith:@NO expected:NO]; + [self testEnabledWith:@(NO) expected:NO]; +} + +- (void)testEnabledWith:(NSObject *)enabledValue expected:(BOOL)expectedValue +{ + SentryOptions *options = [self getValidOptions:@{ @"enabled" : enabledValue }]; + + XCTAssertEqual(expectedValue, options.enabled); +} + +- (void)testMaxBreadcrumbs +{ + NSNumber *maxBreadcrumbs = @20; + + SentryOptions *options = [self getValidOptions:@{ @"maxBreadcrumbs" : maxBreadcrumbs }]; + + XCTAssertEqual([maxBreadcrumbs unsignedIntValue], options.maxBreadcrumbs); +} + +- (void)testEnableNetworkBreadcrumbs +{ + [self testBooleanField:@"enableNetworkBreadcrumbs"]; +} + +- (void)testEnableAutoBreadcrumbTracking +{ + [self testBooleanField:@"enableAutoBreadcrumbTracking"]; +} + +- (void)testSendClientReports +{ + [self testBooleanField:@"sendClientReports" defaultValue:YES]; } -- (void)testRendersWelcomeScreen +- (void)testDefaultMaxBreadcrumbs { - UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; + SentryOptions *options = [self getValidOptions:@{}]; - __block NSString *redboxError = nil; -#ifdef DEBUG - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; + XCTAssertEqual([@100 unsignedIntValue], options.maxBreadcrumbs); +} + +- (void)testMaxBreadcrumbsGarbage +{ + SentryOptions *options = [self getValidOptions:@{ @"maxBreadcrumbs" : self }]; + + XCTAssertEqual(100, options.maxBreadcrumbs); +} + +- (void)testMaxCacheItems +{ + NSNumber *maxCacheItems = @20; + + SentryOptions *options = [self getValidOptions:@{ @"maxCacheItems" : maxCacheItems }]; + + XCTAssertEqual([maxCacheItems unsignedIntValue], options.maxCacheItems); +} + +- (void)testMaxCacheItemsGarbage +{ + SentryOptions *options = [self getValidOptions:@{ @"maxCacheItems" : self }]; + + XCTAssertEqual(30, options.maxCacheItems); +} + +- (void)testDefaultMaxCacheItems +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertEqual([@30 unsignedIntValue], options.maxCacheItems); +} + +- (void)testDefaultBeforeSend +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.beforeSend); +} + +- (void)testGarbageBeforeSend_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"beforeSend" : @"fault" }]; + + XCTAssertNil(options.beforeSend); +} + +- (void)testNSNullBeforeSend_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"beforeSend" : [NSNull null] }]; + + XCTAssertFalse([options.beforeSend isEqual:[NSNull null]]); +} + +- (void)testDefaultBeforeBreadcrumb +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.beforeBreadcrumb); +} + +- (void)testTracePropagationTargets +{ + SentryOptions *options = + [self getValidOptions:@{ @"tracePropagationTargets" : @[ @"localhost" ] }]; + + XCTAssertEqual(options.tracePropagationTargets.count, 1); + XCTAssertEqual(options.tracePropagationTargets[0], @"localhost"); +} + +- (void)testTracePropagationTargetsInvalidInstanceDoesntCrash +{ + SentryOptions *options = [self getValidOptions:@{ @"tracePropagationTargets" : @[ @YES ] }]; + + XCTAssertEqual(options.tracePropagationTargets.count, 1); + XCTAssertEqual(options.tracePropagationTargets[0], @YES); +} + +- (void)testFailedRequestTargets +{ + SentryOptions *options = + [self getValidOptions:@{ @"failedRequestTargets" : @[ @"localhost" ] }]; + + XCTAssertEqual(options.failedRequestTargets.count, 1); + XCTAssertEqual(options.failedRequestTargets[0], @"localhost"); +} + +- (void)testFailedRequestTargetsInvalidInstanceDoesntCrash +{ + SentryOptions *options = [self getValidOptions:@{ @"failedRequestTargets" : @[ @YES ] }]; + + XCTAssertEqual(options.failedRequestTargets.count, 1); + XCTAssertEqual(options.failedRequestTargets[0], @YES); +} + +- (void)testGarbageBeforeBreadcrumb_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"beforeBreadcrumb" : @"fault" }]; + + XCTAssertEqual(nil, options.beforeBreadcrumb); +} + +- (void)testDefaultOnCrashedLastRun +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.onCrashedLastRun); +} + +- (void)testGarbageOnCrashedLastRun_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"onCrashedLastRun" : @"fault" }]; + + XCTAssertNil(options.onCrashedLastRun); +} + +- (void)testDefaultIntegrations +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertTrue([[SentryOptions defaultIntegrations] isEqualToArray:options.integrations], + @"Default integrations are not set correctly"); +} + +- (void)testSampleRateWithDict +{ + NSNumber *sampleRate = @0.1; + SentryOptions *options = [self getValidOptions:@{ @"sampleRate" : sampleRate }]; + XCTAssertEqual(sampleRate, options.sampleRate); +} + +- (void)testSampleRateNotSet +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertEqual(@1, options.sampleRate); +} + +- (void)testEnableAutoSessionTracking +{ + [self testBooleanField:@"enableAutoSessionTracking"]; +} + +- (void)testEnableOutOfMemoryTracking +{ + [self testBooleanField:@"enableOutOfMemoryTracking"]; +} + +- (void)testSessionTrackingIntervalMillis +{ + NSNumber *sessionTracing = @2000; + SentryOptions *options = + [self getValidOptions:@{ @"sessionTrackingIntervalMillis" : sessionTracing }]; + + XCTAssertEqual([sessionTracing unsignedIntValue], options.sessionTrackingIntervalMillis); +} + +- (void)testDefaultSessionTracingIntervalMillis +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertEqual([@30000 unsignedIntValue], options.sessionTrackingIntervalMillis); +} + +- (void)testAttachStackTrace +{ + [self testBooleanField:@"attachStacktrace"]; +} + +- (void)testStitchAsyncCodeDisabledPerDefault +{ + [self testBooleanField:@"stitchAsyncCode" defaultValue:NO]; +} + +- (void)testEnableIOTracing +{ + [self testBooleanField:@"enableFileIOTracing" defaultValue:NO]; +} + +- (void)testNSNull_SetsDefaultValue +{ + SentryOptions *options = [[SentryOptions alloc] initWithRNOptions:@{ + @"dsn" : [NSNull null], + @"enabled" : [NSNull null], + @"debug" : [NSNull null], + @"diagnosticLevel" : [NSNull null], + @"release" : [NSNull null], + @"environment" : [NSNull null], + @"dist" : [NSNull null], + @"maxBreadcrumbs" : [NSNull null], + @"enableNetworkBreadcrumbs" : [NSNull null], + @"maxCacheItems" : [NSNull null], + @"beforeSend" : [NSNull null], + @"beforeBreadcrumb" : [NSNull null], + @"onCrashedLastRun" : [NSNull null], + @"integrations" : [NSNull null], + @"sampleRate" : [NSNull null], + @"enableAutoSessionTracing" : [NSNull null], + @"enableOutOfMemoryTracing" : [NSNull null], + @"sessionTracingIntervalMillis" : [NSNull null], + @"attachStacktrace" : [NSNull null], + @"stitchAsyncCode" : [NSNull null], + @"maxAttachmentSize" : [NSNull null], + @"sendDefaultPii" : [NSNull null], + @"enableAutoPerformanceTracing" : [NSNull null], +#if SENTRY_HAS_UIKIT + @"enableUIViewControllerTracing" : [NSNull null], + @"attachScreenshot" : [NSNull null], +#endif + @"enableAppHangTracing" : [NSNull null], + @"appHangTimeoutInterval" : [NSNull null], + @"enableNetworkTracing" : [NSNull null], + @"enableAutoBreadcrumbTracing" : [NSNull null], + @"tracesSampleRate" : [NSNull null], + @"tracesSampler" : [NSNull null], + @"inAppIncludes" : [NSNull null], + @"inAppExcludes" : [NSNull null], + @"urlSessionDelegate" : [NSNull null], + @"enableSwizzling" : [NSNull null], + @"enableIOTracing" : [NSNull null], + @"sdk" : [NSNull null], + @"enableCaptureFailedRequests" : [NSNull null], + @"failedRequestStatusCodes" : [NSNull null], } - }); + didFailWithError:nil]; + + XCTAssertNotNil(options.parsedDsn); + [self assertDefaultValues:options]; +} + +- (void)assertDefaultValues:(SentryOptions *)options +{ + XCTAssertEqual(YES, options.enabled); + XCTAssertEqual(NO, options.debug); + XCTAssertEqual(kSentryLevelDebug, options.diagnosticLevel); +// XCTAssertNil(options.environment); + XCTAssertNil(options.dist); + XCTAssertEqual(defaultMaxBreadcrumbs, options.maxBreadcrumbs); + XCTAssertTrue(options.enableNetworkBreadcrumbs); + XCTAssertEqual(30, options.maxCacheItems); + XCTAssertNil(options.beforeSend); + XCTAssertNil(options.beforeBreadcrumb); + XCTAssertNil(options.onCrashedLastRun); + XCTAssertTrue([[SentryOptions defaultIntegrations] isEqualToArray:options.integrations], + @"Default integrations are not set correctly"); + XCTAssertEqual(@1, options.sampleRate); + XCTAssertEqual(YES, options.enableAutoSessionTracking); + XCTAssertEqual(YES, options.enableOutOfMemoryTracking); + XCTAssertEqual([@30000 unsignedIntValue], options.sessionTrackingIntervalMillis); + XCTAssertEqual(YES, options.attachStacktrace); + XCTAssertEqual(NO, options.stitchAsyncCode); + XCTAssertEqual(20 * 1024 * 1024, options.maxAttachmentSize); + XCTAssertEqual(NO, options.sendDefaultPii); + XCTAssertTrue(options.enableAutoPerformanceTracing); +#if SENTRY_HAS_UIKIT + XCTAssertTrue(options.enableUIViewControllerTracing); + XCTAssertFalse(options.attachScreenshot); + XCTAssertEqual(3.0, options.idleTimeout); #endif + XCTAssertFalse(options.enableAppHangTracking); + XCTAssertEqual(options.appHangTimeoutInterval, 2); + XCTAssertEqual(YES, options.enableNetworkTracking); + XCTAssertNil(options.tracesSampleRate); + XCTAssertNil(options.tracesSampler); + XCTAssertEqual(@[], options.inAppExcludes); + XCTAssertNil(options.urlSessionDelegate); + XCTAssertEqual(YES, options.enableSwizzling); + XCTAssertEqual(NO, options.enableFileIOTracing); + XCTAssertEqual(YES, options.enableAutoBreadcrumbTracking); + + NSRegularExpression *regexTrace = options.tracePropagationTargets[0]; + XCTAssertTrue([regexTrace.pattern isEqualToString:@".*"]); - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + NSRegularExpression *regexRequests = options.failedRequestTargets[0]; + XCTAssertTrue([regexRequests.pattern isEqualToString:@".*"]); - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } + XCTAssertEqual(NO, options.enableCaptureFailedRequests); -#ifdef DEBUG - RCTSetLogFunction(RCTDefaultLogFunction); + SentryHttpStatusCodeRange *range = options.failedRequestStatusCodes[0]; + XCTAssertEqual(500, range.min); + XCTAssertEqual(599, range.max); + +#if SENTRY_TARGET_PROFILING_SUPPORTED +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" + XCTAssertEqual(NO, options.enableProfiling); +# pragma clang diagnostic pop + XCTAssertNil(options.profilesSampleRate); + XCTAssertNil(options.profilesSampler); #endif +} + + +- (void)testMaxAttachmentSize +{ + NSNumber *maxAttachmentSize = @21; + SentryOptions *options = [self getValidOptions:@{ @"maxAttachmentSize" : maxAttachmentSize }]; + + XCTAssertEqual([maxAttachmentSize unsignedIntValue], options.maxAttachmentSize); +} + +- (void)testDefaultMaxAttachmentSize +{ + SentryOptions *options = [self getValidOptions:@{}]; - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); + XCTAssertEqual(20 * 1024 * 1024, options.maxAttachmentSize); } +- (void)testSendDefaultPii +{ + [self testBooleanField:@"sendDefaultPii" defaultValue:NO]; +} + +//- (void)testEnableAutoPerformanceTracing +//{ +// [self testBooleanField:@"enableAutoPerformanceTracing"]; +//} + +#if SENTRY_HAS_UIKIT +//- (void)testEnableUIViewControllerTracking +//{ +// [self testBooleanField:@"enableUIViewControllerTracking"]; +//} + +- (void)testAttachScreenshot +{ + [self testBooleanField:@"attachScreenshot" defaultValue:NO]; +} + +//- (void)testEnableUserInteractionTracking +//{ +// [self testBooleanField:@"enableUserInteractionTracking" defaultValue:NO]; +//} + +- (void)testIdleTimeout +{ + NSNumber *idleTimeout = @2.1; + SentryOptions *options = [self getValidOptions:@{ @"idleTimeout" : idleTimeout }]; + + XCTAssertEqual([idleTimeout doubleValue], options.idleTimeout); +} + +//- (void)testEnablePreWarmedAppStartTracking +//{ +// [self testBooleanField:@"enablePreWarmedAppStartTracking" defaultValue:NO]; +//} + +#endif + +- (void)testEnableAppHangTracking +{ + [self testBooleanField:@"enableAppHangTracking" defaultValue:NO]; +} + +- (void)testDefaultAppHangsTimeout +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertEqual(2, options.appHangTimeoutInterval); +} + +- (void)testEnableNetworkTracking +{ + [self testBooleanField:@"enableNetworkTracking"]; +} + +- (void)testEnableSwizzling +{ + [self testBooleanField:@"enableSwizzling"]; +} + +- (void)testTracesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{ @"tracesSampleRate" : @0.1 }]; + + XCTAssertEqual(options.tracesSampleRate.doubleValue, 0.1); +} + +- (void)testDefaultTracesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.tracesSampleRate); +} + +- (double)tracesSamplerCallback:(NSDictionary *)context +{ + return 0.1; +} + +- (void)testDefaultTracesSampler +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertNil(options.tracesSampler); +} + +- (void)testGarbageTracesSampler_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"tracesSampler" : @"fault" }]; + XCTAssertNil(options.tracesSampler); +} + +#if SENTRY_TARGET_PROFILING_SUPPORTED +//- (void)testEnableProfiling +//{ +// [self testBooleanField:@"enableProfiling" defaultValue:NO]; +//} + +- (void)testProfilesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{ @"profilesSampleRate" : @0.1 }]; + + XCTAssertEqual(options.profilesSampleRate.doubleValue, 0.1); +} + +- (void)testDefaultProfilesSampleRate +{ + SentryOptions *options = [self getValidOptions:@{}]; + + XCTAssertNil(options.profilesSampleRate); +} + +- (double)profilesSamplerCallback:(NSDictionary *)context +{ + return 0.1; +} + +- (void)testDefaultProfilesSampler +{ + SentryOptions *options = [self getValidOptions:@{}]; + XCTAssertNil(options.profilesSampler); +} + +- (void)testGarbageProfilesSampler_ReturnsNil +{ + SentryOptions *options = [self getValidOptions:@{ @"profilesSampler" : @"fault" }]; + XCTAssertNil(options.profilesSampler); +} +#endif + +- (SentryOptions *)getValidOptions:(NSDictionary *)dict +{ + NSError *error = nil; + + NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; + options[@"dsn"] = @"https://username:password@sentry.io/1"; + + [options addEntriesFromDictionary:dict]; + + SentryOptions *sentryOptions = [[SentryOptions alloc] initWithRNOptions:options + didFailWithError:&error]; + XCTAssertNil(error); + return sentryOptions; +} + +- (void)testBooleanField:(NSString *)property +{ + [self testBooleanField:property defaultValue:YES]; +} + +- (void)testBooleanField:(NSString *)property defaultValue:(BOOL)defaultValue +{ + // Opposite of default + SentryOptions *options = [self getValidOptions:@{ property : @(!defaultValue) }]; + XCTAssertEqual(!defaultValue, [self getProperty:property of:options]); + + // Default + options = [self getValidOptions:@{}]; + XCTAssertEqual(defaultValue, [self getProperty:property of:options]); + + // Garbage + options = [self getValidOptions:@{ property : @"" }]; + XCTAssertEqual(NO, [self getProperty:property of:options]); +} + +- (BOOL)getProperty:(NSString *)property of:(SentryOptions *)options +{ + SEL selector = NSSelectorFromString(property); + NSAssert( + [options respondsToSelector:selector], @"Options doesn't have a property '%@'", property); + + NSInvocation *invocation = [NSInvocation + invocationWithMethodSignature:[[options class] + instanceMethodSignatureForSelector:selector]]; + [invocation setSelector:selector]; + [invocation setTarget:options]; + [invocation invoke]; + BOOL result; + [invocation getReturnValue:&result]; + + return result; +} @end From 1d8b642720b2a33449235c80eb512872c7a767a9 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 20 Dec 2022 14:00:34 +0100 Subject: [PATCH 3/5] Add xcode native test to ci --- .github/workflows/native-tests.yml | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/native-tests.yml diff --git a/.github/workflows/native-tests.yml b/.github/workflows/native-tests.yml new file mode 100644 index 0000000000..0fa02505cc --- /dev/null +++ b/.github/workflows/native-tests.yml @@ -0,0 +1,54 @@ +name: Native Tests + +on: + push: + branches: [main] + pull_request: + +env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + +jobs: + cancel-previous-workflow: + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 # pin@0.11.0 + with: + access_token: ${{ github.token }} + + test: + name: ios + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: NPM cache SDK + uses: actions/cache@v3 + id: deps-cache + with: + path: node_modules + key: ${{ github.workflow }}-${{ github.job }}-npm-${{ hashFiles('yarn.lock') }} + + - name: Install SDK JS Dependencies + if: steps.deps-cache.outputs['cache-hit'] != 'true' + run: yarn install + + - name: Build SDK + run: yarn build + + - name: Install App Pods + working-directory: sample/ios + run: PRODUCTION=1 pod install + + - name: Run iOS Tests + working-directory: sample/ios + env: + SCHEME: sample + CONFIGURATION: Release + DESTINATION: 'platform=iOS Simulator,OS=latest,name=iPhone 14' + run: | + env NSUnbufferedIO=YES xcodebuild -workspace *.xcworkspace \ + -scheme $SCHEME -configuration $CONFIGURATION \ + -destination "$DESTINATION" \ + test From 9fecc28c22ebcf23938df88d01bb0626119eba8c Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 20 Dec 2022 14:03:20 +0100 Subject: [PATCH 4/5] Add only active arch --- .github/workflows/native-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/native-tests.yml b/.github/workflows/native-tests.yml index 0fa02505cc..005103d6da 100644 --- a/.github/workflows/native-tests.yml +++ b/.github/workflows/native-tests.yml @@ -48,7 +48,9 @@ jobs: CONFIGURATION: Release DESTINATION: 'platform=iOS Simulator,OS=latest,name=iPhone 14' run: | - env NSUnbufferedIO=YES xcodebuild -workspace *.xcworkspace \ + env NSUnbufferedIO=YES \ + ONLY_ACTIVE_ARCH=yes \ + xcodebuild -workspace *.xcworkspace \ -scheme $SCHEME -configuration $CONFIGURATION \ -destination "$DESTINATION" \ test From a08fb3ae01d9bf40b09081356c1308683ea8f8d0 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 20 Dec 2022 14:13:22 +0100 Subject: [PATCH 5/5] Add app js deps install --- .github/workflows/native-tests.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/native-tests.yml b/.github/workflows/native-tests.yml index 005103d6da..76e551ef49 100644 --- a/.github/workflows/native-tests.yml +++ b/.github/workflows/native-tests.yml @@ -23,24 +23,31 @@ jobs: steps: - uses: actions/checkout@v3 - - name: NPM cache SDK + - name: NPM cache uses: actions/cache@v3 id: deps-cache with: - path: node_modules - key: ${{ github.workflow }}-${{ github.job }}-npm-${{ hashFiles('yarn.lock') }} + path: | + node_modules + sample/node_modules + key: ${{ github.workflow }}-${{ github.job }}-npm-${{ hashFiles('yarn.lock', 'sample/yarn.lock') }} - name: Install SDK JS Dependencies if: steps.deps-cache.outputs['cache-hit'] != 'true' run: yarn install - - name: Build SDK - run: yarn build + - name: Install App JS Dependencies + if: steps.deps-cache.outputs['cache-hit'] != 'true' + working-directory: sample + run: yarn install - name: Install App Pods working-directory: sample/ios run: PRODUCTION=1 pod install + - name: Build SDK + run: yarn build + - name: Run iOS Tests working-directory: sample/ios env: