From 716a5b0ea1d64474b2285f5799a81ff0faac918f Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:32:24 +0100 Subject: [PATCH] fix: Use the same SdkInfo for envelope header and event (#4629) --- CHANGELOG.md | 1 + Sources/Sentry/SentryClient.m | 20 +-- Sources/Sentry/SentryEnvelope.m | 4 +- Sources/Sentry/SentrySdkInfo.m | 89 +++++++--- Sources/Sentry/SentrySerialization.m | 4 +- Sources/Sentry/include/SentrySdkInfo.h | 28 ++- .../Helper/SentryFileManagerTests.swift | 2 +- .../Helper/SentrySerializationTests.swift | 19 ++- .../SentryNSURLRequestBuilderTests.swift | 4 +- .../Protocol/SentryEnvelopeTests.swift | 6 +- .../Protocol/SentrySdkInfo+Equality.m | 12 ++ .../Protocol/SentrySdkInfoNilTests.m | 42 ++++- .../Protocol/SentrySdkInfoTests.swift | 161 +++++++++++------- 13 files changed, 271 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5fe232fb89..d3e7db0e576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - `SentrySdkInfo.packages` should be an array (#4626) +- Use the same SdkInfo for envelope header and event (#4629) ### Internal diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 3c2a4ecac26..5f0f146e41a 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -33,6 +33,7 @@ #import "SentryRandom.h" #import "SentrySDK+Private.h" #import "SentryScope+Private.h" +#import "SentrySdkInfo.h" #import "SentrySerialization.h" #import "SentrySession.h" #import "SentryStacktraceBuilder.h" @@ -880,24 +881,7 @@ - (void)setSdk:(SentryEvent *)event return; } - NSMutableArray *integrations = - [SentrySDK.currentHub trimmedInstalledIntegrationNames]; - -#if SENTRY_HAS_UIKIT - if (self.options.enablePreWarmedAppStartTracing) { - [integrations addObject:@"PreWarmedAppStartTracing"]; - } -#endif - - NSArray *features = - [SentryEnabledFeaturesBuilder getEnabledFeaturesWithOptions:self.options]; - - event.sdk = @{ - @"name" : SentryMeta.sdkName, - @"version" : SentryMeta.versionString, - @"integrations" : integrations, - @"features" : features - }; + event.sdk = [[[SentrySdkInfo alloc] initWithOptions:self.options] serialize]; } - (void)setUserInfo:(NSDictionary *)userInfo withEvent:(SentryEvent *)event diff --git a/Sources/Sentry/SentryEnvelope.m b/Sources/Sentry/SentryEnvelope.m index 7b63610d2ed..8cfefa0ffc2 100644 --- a/Sources/Sentry/SentryEnvelope.m +++ b/Sources/Sentry/SentryEnvelope.m @@ -8,7 +8,6 @@ #import "SentryEvent.h" #import "SentryLog.h" #import "SentryMessage.h" -#import "SentryMeta.h" #import "SentryMsgPackSerializer.h" #import "SentrySdkInfo.h" #import "SentrySerialization.h" @@ -30,8 +29,7 @@ - (instancetype)initWithId:(SentryId *_Nullable)eventId - (instancetype)initWithId:(nullable SentryId *)eventId traceContext:(nullable SentryTraceContext *)traceContext { - SentrySdkInfo *sdkInfo = [[SentrySdkInfo alloc] initWithName:SentryMeta.sdkName - andVersion:SentryMeta.versionString]; + SentrySdkInfo *sdkInfo = [SentrySdkInfo global]; self = [self initWithId:eventId sdkInfo:sdkInfo traceContext:traceContext]; return self; } diff --git a/Sources/Sentry/SentrySdkInfo.m b/Sources/Sentry/SentrySdkInfo.m index a153a53ee46..07b0d43e4e0 100644 --- a/Sources/Sentry/SentrySdkInfo.m +++ b/Sources/Sentry/SentrySdkInfo.m @@ -1,4 +1,10 @@ #import "SentrySdkInfo.h" +#import "SentryClient+Private.h" +#import "SentryHub+Private.h" +#import "SentryMeta.h" +#import "SentryOptions.h" +#import "SentrySDK+Private.h" +#import "SentrySwift.h" #import typedef NS_ENUM(NSUInteger, SentryPackageManagerOption) { @@ -33,48 +39,83 @@ @interface SentrySdkInfo () @implementation SentrySdkInfo -- (instancetype)initWithName:(NSString *)name andVersion:(NSString *)version ++ (instancetype)global +{ + return [[SentrySdkInfo alloc] initWithOptions:[SentrySDK.currentHub getClient].options]; +} + +- (instancetype)initWithOptions:(SentryOptions *)options +{ + + NSArray *features = + [SentryEnabledFeaturesBuilder getEnabledFeaturesWithOptions:options]; + + NSMutableArray *integrations = + [SentrySDK.currentHub trimmedInstalledIntegrationNames]; + +#if SENTRY_HAS_UIKIT + if (options.enablePreWarmedAppStartTracing) { + [integrations addObject:@"PreWarmedAppStartTracing"]; + } +#endif + + return [self initWithName:SentryMeta.sdkName + version:SentryMeta.versionString + integrations:integrations + features:features]; +} + +- (instancetype)initWithName:(NSString *)name + version:(NSString *)version + integrations:(NSArray *)integrations + features:(NSArray *)features { if (self = [super init]) { _name = name ?: @""; _version = version ?: @""; _packageManager = SENTRY_PACKAGE_INFO; + _integrations = integrations ?: @[]; + _features = features ?: @[]; } return self; } - (instancetype)initWithDict:(NSDictionary *)dict -{ - return [self initWithDictInternal:dict orDefaults:nil]; -} - -- (instancetype)initWithDict:(NSDictionary *)dict orDefaults:(SentrySdkInfo *)info; -{ - return [self initWithDictInternal:dict orDefaults:info]; -} - -- (instancetype)initWithDictInternal:(NSDictionary *)dict orDefaults:(SentrySdkInfo *_Nullable)info; { NSString *name = @""; NSString *version = @""; + NSMutableSet *integrations = [[NSMutableSet alloc] init]; + NSMutableSet *features = [[NSMutableSet alloc] init]; - if (nil != dict[@"sdk"] && [dict[@"sdk"] isKindOfClass:[NSDictionary class]]) { - NSDictionary *sdkInfoDict = dict[@"sdk"]; - if ([sdkInfoDict[@"name"] isKindOfClass:[NSString class]]) { - name = sdkInfoDict[@"name"]; - } else if (info && info.name) { - name = info.name; + if ([dict[@"name"] isKindOfClass:[NSString class]]) { + name = dict[@"name"]; + } + + if ([dict[@"version"] isKindOfClass:[NSString class]]) { + version = dict[@"version"]; + } + + if ([dict[@"integrations"] isKindOfClass:[NSArray class]]) { + for (id item in dict[@"integrations"]) { + if ([item isKindOfClass:[NSString class]]) { + [integrations addObject:item]; + } } + } - if ([sdkInfoDict[@"version"] isKindOfClass:[NSString class]]) { - version = sdkInfoDict[@"version"]; - } else if (info && info.version) { - version = info.version; + if ([dict[@"features"] isKindOfClass:[NSArray class]]) { + for (id item in dict[@"features"]) { + if ([item isKindOfClass:[NSString class]]) { + [features addObject:item]; + } } } - return [self initWithName:name andVersion:version]; + return [self initWithName:name + version:version + integrations:[integrations allObjects] + features:[features allObjects]]; } - (nullable NSString *)getPackageName:(SentryPackageManagerOption)packageManager @@ -96,6 +137,8 @@ - (nullable NSString *)getPackageName:(SentryPackageManagerOption)packageManager NSMutableDictionary *sdk = @{ @"name" : self.name, @"version" : self.version, + @"integrations" : self.integrations, + @"features" : self.features, } .mutableCopy; if (self.packageManager != SentryPackageManagerUnkown) { @@ -110,7 +153,7 @@ - (nullable NSString *)getPackageName:(SentryPackageManagerOption)packageManager } } - return @{ @"sdk" : sdk }; + return sdk; } @end diff --git a/Sources/Sentry/SentrySerialization.m b/Sources/Sentry/SentrySerialization.m index da006ef9e14..add4536a024 100644 --- a/Sources/Sentry/SentrySerialization.m +++ b/Sources/Sentry/SentrySerialization.m @@ -42,7 +42,7 @@ + (NSData *_Nullable)dataWithEnvelope:(SentryEnvelope *)envelope SentrySdkInfo *sdkInfo = envelope.header.sdkInfo; if (nil != sdkInfo) { - [serializedData addEntriesFromDictionary:[sdkInfo serialize]]; + [serializedData setValue:[sdkInfo serialize] forKey:@"sdk"]; } SentryTraceContext *traceContext = envelope.header.traceContext; @@ -111,7 +111,7 @@ + (SentryEnvelope *_Nullable)envelopeWithData:(NSData *)data SentrySdkInfo *sdkInfo = nil; if (nil != headerDictionary[@"sdk"]) { - sdkInfo = [[SentrySdkInfo alloc] initWithDict:headerDictionary]; + sdkInfo = [[SentrySdkInfo alloc] initWithDict:headerDictionary[@"sdk"]]; } SentryTraceContext *traceContext = nil; diff --git a/Sources/Sentry/include/SentrySdkInfo.h b/Sources/Sentry/include/SentrySdkInfo.h index 6060d9025d4..c25d3c0e74e 100644 --- a/Sources/Sentry/include/SentrySdkInfo.h +++ b/Sources/Sentry/include/SentrySdkInfo.h @@ -12,6 +12,8 @@ #import +@class SentryOptions; + NS_ASSUME_NONNULL_BEGIN /** @@ -22,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @interface SentrySdkInfo : NSObject SENTRY_NO_INIT ++ (instancetype)global; + /** * The name of the SDK. Examples: sentry.cocoa, sentry.cocoa.vapor, ... */ @@ -34,13 +38,31 @@ SENTRY_NO_INIT */ @property (nonatomic, readonly, copy) NSString *version; +/** + * A list of names identifying enabled integrations. The list should + * have all enabled integrations, including default integrations. Default + * integrations are included because different SDK releases may contain different + * default integrations. + */ +@property (nonatomic, readonly, copy) NSArray *integrations; + +/** + * A list of feature names identifying enabled SDK features. This list + * should contain all enabled SDK features. On some SDKs, enabling a feature in the + * options also adds an integration. We encourage tracking such features with either + * integrations or features but not both to reduce the payload size. + */ +@property (nonatomic, readonly, copy) NSArray *features; + +- (instancetype)initWithOptions:(SentryOptions *)options; + - (instancetype)initWithName:(NSString *)name - andVersion:(NSString *)version NS_DESIGNATED_INITIALIZER; + version:(NSString *)version + integrations:(NSArray *)integrations + features:(NSArray *)features NS_DESIGNATED_INITIALIZER; - (instancetype)initWithDict:(NSDictionary *)dict; -- (instancetype)initWithDict:(NSDictionary *)dict orDefaults:(SentrySdkInfo *)info; - @end NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Helper/SentryFileManagerTests.swift b/Tests/SentryTests/Helper/SentryFileManagerTests.swift index 1894368b57f..2bbdbe5ef1c 100644 --- a/Tests/SentryTests/Helper/SentryFileManagerTests.swift +++ b/Tests/SentryTests/Helper/SentryFileManagerTests.swift @@ -135,7 +135,7 @@ class SentryFileManagerTests: XCTestCase { } func testStoreInvalidEnvelope_ReturnsNil() { - let sdkInfoWithInvalidJSON = SentrySdkInfo(name: SentryInvalidJSONString() as String, andVersion: "8.0.0") + let sdkInfoWithInvalidJSON = SentrySdkInfo(name: SentryInvalidJSONString() as String, version: "8.0.0", integrations: [], features: []) let headerWithInvalidJSON = SentryEnvelopeHeader(id: nil, sdkInfo: sdkInfoWithInvalidJSON, traceContext: nil) let envelope = SentryEnvelope(header: headerWithInvalidJSON, items: []) diff --git a/Tests/SentryTests/Helper/SentrySerializationTests.swift b/Tests/SentryTests/Helper/SentrySerializationTests.swift index 5d5cb694466..586c8c0819b 100644 --- a/Tests/SentryTests/Helper/SentrySerializationTests.swift +++ b/Tests/SentryTests/Helper/SentrySerializationTests.swift @@ -1,4 +1,5 @@ @testable import Sentry +import SentryTestUtils import XCTest class SentrySerializationTests: XCTestCase { @@ -7,7 +8,17 @@ class SentrySerializationTests: XCTestCase { static var invalidData = "hi".data(using: .utf8)! static var traceContext = TraceContext(trace: SentryId(), publicKey: "PUBLIC_KEY", releaseName: "RELEASE_NAME", environment: "TEST", transaction: "transaction", userSegment: "some segment", sampleRate: "0.25", sampled: "true", replayId: nil) } - + + override func setUp() { + super.setUp() + clearTestState() + } + + override func tearDown() { + super.tearDown() + clearTestState() + } + func testSerializationFailsWithInvalidJSONObject() { let json: [String: Any] = [ "valid object": "hi, i'm a valid object", @@ -24,7 +35,7 @@ class SentrySerializationTests: XCTestCase { } func testEnvelopeWithData_InvalidEnvelopeHeaderJSON_ReturnsNil() { - let sdkInfoWithInvalidJSON = SentrySdkInfo(name: SentryInvalidJSONString() as String, andVersion: "8.0.0") + let sdkInfoWithInvalidJSON = SentrySdkInfo(name: SentryInvalidJSONString() as String, version: "8.0.0", integrations: [], features: []) let headerWithInvalidJSON = SentryEnvelopeHeader(id: nil, sdkInfo: sdkInfoWithInvalidJSON, traceContext: nil) let envelope = SentryEnvelope(header: headerWithInvalidJSON, items: []) @@ -125,7 +136,7 @@ class SentrySerializationTests: XCTestCase { } func testEnvelopeWithData_WithSdkInfo_ReturnsSDKInfo() throws { - let sdkInfo = SentrySdkInfo(name: "sentry.cocoa", andVersion: "5.0.1") + let sdkInfo = SentrySdkInfo(name: "sentry.cocoa", version: "5.0.1", integrations: [], features: []) let envelopeHeader = SentryEnvelopeHeader(id: nil, sdkInfo: sdkInfo, traceContext: nil) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) @@ -524,7 +535,7 @@ class SentrySerializationTests: XCTestCase { } private func assertDefaultSdkInfoSet(deserializedEnvelope: SentryEnvelope) { - let sdkInfo = SentrySdkInfo(name: SentryMeta.sdkName, andVersion: SentryMeta.versionString) + let sdkInfo = SentrySdkInfo(name: SentryMeta.sdkName, version: SentryMeta.versionString, integrations: [], features: []) XCTAssertEqual(sdkInfo, deserializedEnvelope.header.sdkInfo) } diff --git a/Tests/SentryTests/Networking/SentryNSURLRequestBuilderTests.swift b/Tests/SentryTests/Networking/SentryNSURLRequestBuilderTests.swift index 203229a1e53..88d9e1e08ae 100644 --- a/Tests/SentryTests/Networking/SentryNSURLRequestBuilderTests.swift +++ b/Tests/SentryTests/Networking/SentryNSURLRequestBuilderTests.swift @@ -66,7 +66,9 @@ class SentryNSURLRequestBuilderTests: XCTestCase { private func givenEnvelopeWithInvalidData() -> SentryEnvelope { let sdkInfoWithInvalidJSON = SentrySdkInfo( name: SentryInvalidJSONString() as String, - andVersion: "8.0.0" + version: "8.0.0", + integrations: [], + features: [] ) let headerWithInvalidJSON = SentryEnvelopeHeader( id: nil, diff --git a/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift b/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift index acabb00d528..62d7dc90f36 100644 --- a/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift +++ b/Tests/SentryTests/Protocol/SentryEnvelopeTests.swift @@ -66,8 +66,8 @@ class SentryEnvelopeTests: XCTestCase { clearTestState() } - private let defaultSdkInfo = SentrySdkInfo(name: SentryMeta.sdkName, andVersion: SentryMeta.versionString) - + private let defaultSdkInfo = SentrySdkInfo(name: SentryMeta.sdkName, version: SentryMeta.versionString, integrations: [], features: []) + func testSentryEnvelopeFromEvent() throws { let event = Event() @@ -147,7 +147,7 @@ class SentryEnvelopeTests: XCTestCase { func testInitSentryEnvelopeHeader_SetIdAndSdkInfo() { let eventId = SentryId() - let sdkInfo = SentrySdkInfo(name: "sdk", andVersion: "1.2.3-alpha.0") + let sdkInfo = SentrySdkInfo(name: "sdk", version: "1.2.3-alpha.0", integrations: [], features: []) let envelopeHeader = SentryEnvelopeHeader(id: eventId, sdkInfo: sdkInfo, traceContext: nil) XCTAssertEqual(eventId, envelopeHeader.eventId) diff --git a/Tests/SentryTests/Protocol/SentrySdkInfo+Equality.m b/Tests/SentryTests/Protocol/SentrySdkInfo+Equality.m index 42c13fb2c8e..bb38a1a958a 100644 --- a/Tests/SentryTests/Protocol/SentrySdkInfo+Equality.m +++ b/Tests/SentryTests/Protocol/SentrySdkInfo+Equality.m @@ -19,6 +19,16 @@ - (BOOL)isEqual:(id _Nullable)object return NO; } + if (![[NSSet setWithArray:self.integrations] + isEqualToSet:[NSSet setWithArray:otherSdkInfo.integrations]]) { + return NO; + } + + if (![[NSSet setWithArray:self.features] + isEqualToSet:[NSSet setWithArray:otherSdkInfo.features]]) { + return NO; + } + return YES; } @@ -28,6 +38,8 @@ - (NSUInteger)hash hash = hash * 23 + [self.name hash]; hash = hash * 23 + [self.version hash]; + hash = hash * 23 + [self.integrations hash]; + hash = hash * 23 + [self.features hash]; return hash; } diff --git a/Tests/SentryTests/Protocol/SentrySdkInfoNilTests.m b/Tests/SentryTests/Protocol/SentrySdkInfoNilTests.m index 6ba7d923164..d42140e2d52 100644 --- a/Tests/SentryTests/Protocol/SentrySdkInfoNilTests.m +++ b/Tests/SentryTests/Protocol/SentrySdkInfoNilTests.m @@ -15,17 +15,49 @@ - (void)testSdkNameIsNil { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:nil andVersion:@""]; + SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:nil + version:@"" + integrations:@[] + features:@[]]; #pragma clang diagnostic pop [self assertSdkInfoIsEmtpy:actual]; } -- (void)testVersinoStringIsNil +- (void)testVersinStringIsNil { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:@"" andVersion:nil]; + SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:@"" + version:nil + integrations:@[] + features:@[]]; +#pragma clang diagnostic pop + + [self assertSdkInfoIsEmtpy:actual]; +} + +- (void)testIntegrationsAreNil +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:@"" + version:@"" + integrations:nil + features:@[]]; +#pragma clang diagnostic pop + + [self assertSdkInfoIsEmtpy:actual]; +} + +- (void)testFeaturesAreNil +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + SentrySdkInfo *actual = [[SentrySdkInfo alloc] initWithName:@"" + version:@"" + integrations:@[] + features:nil]; #pragma clang diagnostic pop [self assertSdkInfoIsEmtpy:actual]; @@ -46,7 +78,7 @@ - (void)testInitWithDictWrongTypes #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" SentrySdkInfo *actual = - [[SentrySdkInfo alloc] initWithDict:@{ @"sdk" : @ { @"name" : @20, @"version" : @0 } }]; + [[SentrySdkInfo alloc] initWithDict:@{ @"name" : @20, @"version" : @0 }]; #pragma clang diagnostic pop [self assertSdkInfoIsEmtpy:actual]; @@ -56,6 +88,8 @@ - (void)assertSdkInfoIsEmtpy:(SentrySdkInfo *)sdkInfo { XCTAssertEqualObjects(@"", sdkInfo.name); XCTAssertEqualObjects(@"", sdkInfo.version); + XCTAssertEqualObjects(@[], sdkInfo.integrations); + XCTAssertEqualObjects(@[], sdkInfo.features); } @end diff --git a/Tests/SentryTests/Protocol/SentrySdkInfoTests.swift b/Tests/SentryTests/Protocol/SentrySdkInfoTests.swift index d04e47b1a4b..d5d87a1e33a 100644 --- a/Tests/SentryTests/Protocol/SentrySdkInfoTests.swift +++ b/Tests/SentryTests/Protocol/SentrySdkInfoTests.swift @@ -7,15 +7,15 @@ class SentrySdkInfoTests: XCTestCase { func testWithPatchLevelSuffix() { let version = "50.10.20-beta1" - let actual = SentrySdkInfo(name: sdkName, andVersion: version) - + let actual = SentrySdkInfo(name: sdkName, version: version, integrations: [], features: []) + XCTAssertEqual(sdkName, actual.name) XCTAssertEqual(version, actual.version) } func testWithAnyVersion() { let version = "anyVersion" - let actual = SentrySdkInfo(name: sdkName, andVersion: version) + let actual = SentrySdkInfo(name: sdkName, version: version, integrations: [], features: []) XCTAssertEqual(sdkName, actual.name) XCTAssertEqual(version, actual.version) @@ -23,104 +23,147 @@ class SentrySdkInfoTests: XCTestCase { func testSerialization() { let version = "5.2.0" - let actual = SentrySdkInfo(name: sdkName, andVersion: version).serialize() - - if let sdkInfo = actual["sdk"] as? [String: Any] { - XCTAssertEqual(2, sdkInfo.count) - XCTAssertEqual(sdkName, sdkInfo["name"] as? String) - XCTAssertEqual(version, sdkInfo["version"] as? String) - } else { - XCTFail("Serialization of SdkInfo doesn't contain sdk") - } + let sdkInfo = SentrySdkInfo(name: sdkName, version: version, integrations: ["a"], features: ["b"]).serialize() + + XCTAssertEqual(sdkName, sdkInfo["name"] as? String) + XCTAssertEqual(version, sdkInfo["version"] as? String) + XCTAssertEqual(["a"], sdkInfo["integrations"] as? [String]) + XCTAssertEqual(["b"], sdkInfo["features"] as? [String]) + } + + func testSerializationValidIntegrations() { + let sdkInfo = SentrySdkInfo(name: "", version: "", integrations: ["a", "b"], features: []).serialize() + + XCTAssertEqual(["a", "b"], sdkInfo["integrations"] as? [String]) + } + + func testSerializationValidFeatures() { + let sdkInfo = SentrySdkInfo(name: "", version: "", integrations: [], features: ["c", "d"]).serialize() + + XCTAssertEqual(["c", "d"], sdkInfo["features"] as? [String]) } func testSPM_packageInfo() throws { let version = "5.2.0" - let actual = SentrySdkInfo(name: sdkName, andVersion: version) + let actual = SentrySdkInfo(name: sdkName, version: version, integrations: [], features: []) Dynamic(actual).packageManager = 0 let serialization = actual.serialize() - if let sdkInfo = serialization["sdk"] as? [String: Any] { - XCTAssertEqual(3, sdkInfo.count) - - let packages = try XCTUnwrap(sdkInfo["packages"] as? [[String: Any]]) - XCTAssertEqual(1, packages.count) - XCTAssertEqual(packages[0]["name"] as? String, "spm:getsentry/\(sdkName)") - XCTAssertEqual(packages[0]["version"] as? String, version) - } else { - XCTFail("Serialization of SdkInfo doesn't contain sdk") - } + let packages = try XCTUnwrap(serialization["packages"] as? [[String: Any]]) + XCTAssertEqual(1, packages.count) + XCTAssertEqual(packages[0]["name"] as? String, "spm:getsentry/\(sdkName)") + XCTAssertEqual(packages[0]["version"] as? String, version) } func testCarthage_packageInfo() throws { let version = "5.2.0" - let actual = SentrySdkInfo(name: sdkName, andVersion: version) + let actual = SentrySdkInfo(name: sdkName, version: version, integrations: [], features: []) Dynamic(actual).packageManager = 2 let serialization = actual.serialize() - if let sdkInfo = serialization["sdk"] as? [String: Any] { - XCTAssertEqual(3, sdkInfo.count) - - let packages = try XCTUnwrap(sdkInfo["packages"] as? [[String: Any]]) - XCTAssertEqual(1, packages.count) - XCTAssertEqual(packages[0]["name"] as? String, "carthage:getsentry/\(sdkName)") - XCTAssertEqual(packages[0]["version"] as? String, version) - } else { - XCTFail("Serialization of SdkInfo doesn't contain sdk") - } + let packages = try XCTUnwrap(serialization["packages"] as? [[String: Any]]) + XCTAssertEqual(1, packages.count) + XCTAssertEqual(packages[0]["name"] as? String, "carthage:getsentry/\(sdkName)") + XCTAssertEqual(packages[0]["version"] as? String, version) } func testcocoapods_packageInfo() throws { let version = "5.2.0" - let actual = SentrySdkInfo(name: sdkName, andVersion: version) + let actual = SentrySdkInfo(name: sdkName, version: version, integrations: [], features: []) Dynamic(actual).packageManager = 1 let serialization = actual.serialize() - if let sdkInfo = serialization["sdk"] as? [String: Any] { - XCTAssertEqual(3, sdkInfo.count) - - let packages = try XCTUnwrap(sdkInfo["packages"] as? [[String: Any]]) - XCTAssertEqual(1, packages.count) - XCTAssertEqual(packages[0]["name"] as? String, "cocoapods:getsentry/\(sdkName)") - XCTAssertEqual(packages[0]["version"] as? String, version) - } else { - XCTFail("Serialization of SdkInfo doesn't contain sdk") - } + let packages = try XCTUnwrap(serialization["packages"] as? [[String: Any]]) + XCTAssertEqual(1, packages.count) + XCTAssertEqual(packages[0]["name"] as? String, "cocoapods:getsentry/\(sdkName)") + XCTAssertEqual(packages[0]["version"] as? String, version) } func testNoPackageNames () { - let actual = SentrySdkInfo(name: sdkName, andVersion: "") + let actual = SentrySdkInfo(name: sdkName, version: "", integrations: [], features: []) XCTAssertNil(Dynamic(actual).getPackageName(3).asString) } func testInitWithDict_SdkInfo() { let version = "10.3.1" - let expected = SentrySdkInfo(name: sdkName, andVersion: version) - - let dict = ["sdk": [ "name": sdkName, "version": version]] - + let expected = SentrySdkInfo(name: sdkName, version: version, integrations: ["a", "b"], features: ["c", "d"]) + + let dict = [ "name": sdkName, "version": version, "integrations": ["a", "b"], "features": ["c", "d"]] as [String: Any] + XCTAssertEqual(expected, SentrySdkInfo(dict: dict)) } - + + func testInitWithDict_SdkInfo_RemovesDuplicates() { + let version = "10.3.1" + let expected = SentrySdkInfo(name: sdkName, version: version, integrations: ["b"], features: ["c"]) + + let dict = [ "name": sdkName, "version": version, "integrations": ["b", "b"], "features": ["c", "c"]] as [String: Any] + + XCTAssertEqual(expected, SentrySdkInfo(dict: dict)) + } + + func testInitWithDict_SdkInfo_IgnoresOrder() { + let version = "10.3.1" + let expected = SentrySdkInfo(name: sdkName, version: version, integrations: ["a", "b"], features: ["c", "d"]) + + let dict = [ "name": sdkName, "version": version, "integrations": ["b", "a"], "features": ["d", "c"]] as [String: Any] + + XCTAssertEqual(expected, SentrySdkInfo(dict: dict)) + } + func testInitWithDict_AllNil() { - let dict = ["sdk": [ "name": nil, "version": nil] as [String: Any?]] - - assertEmptySdkInfo(actual: SentrySdkInfo(dict: dict)) + let dict = [ "name": nil, "version": nil, "integraions": nil, "features": nil] as [String: Any?] + + assertEmptySdkInfo(actual: SentrySdkInfo(dict: dict as [AnyHashable: Any])) } func testInitWithDict_WrongTypes() { - let dict = ["sdk": [ "name": 0, "version": 0]] - + let dict = [ "name": 0, "version": 0, "integrations": 0, "features": 0] + assertEmptySdkInfo(actual: SentrySdkInfo(dict: dict)) } - + + func testInitWithDict_WrongTypesInArrays() { + let version = "10.3.1" + let expected = SentrySdkInfo(name: sdkName, version: version, integrations: ["a"], features: ["b"]) + + let dict = [ + "name": sdkName, + "version": version, + "integrations": + [ + 0, + [] as [Any], + "a", + [:] as [String: Any] + ] as [Any], + "features": [ + 0, + [] as [Any], + "b", + [:] as [String: Any] + ] as [Any] + ] as [String: Any] + + XCTAssertEqual(expected, SentrySdkInfo(dict: dict)) + } + func testInitWithDict_SdkInfoIsString() { let dict = ["sdk": ""] assertEmptySdkInfo(actual: SentrySdkInfo(dict: dict)) } - + + func testglobal() throws { + SentrySDK.start(options: Options()) + let actual = SentrySdkInfo.global() + XCTAssertEqual(actual.name, SentryMeta.sdkName) + XCTAssertEqual(actual.version, SentryMeta.versionString) + XCTAssertTrue(actual.integrations.count > 0) + XCTAssertTrue(actual.features.count > 0) + } + private func assertEmptySdkInfo(actual: SentrySdkInfo) { - XCTAssertEqual(SentrySdkInfo(name: "", andVersion: ""), actual) + XCTAssertEqual(SentrySdkInfo(name: "", version: "", integrations: [], features: []), actual) } }