From 4eec7a14159a537e475b4a3d160d006717b9aca4 Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Wed, 19 Feb 2025 13:43:36 +0100 Subject: [PATCH 1/2] refactor: Add origin as parameter to file IO tracker (#4860) --- Sentry.xcodeproj/project.pbxproj | 8 + Sources/Sentry/SentryFileIOTracker.m | 53 +++-- Sources/Sentry/SentryNSDataSwizzling.m | 5 + Sources/Sentry/SentryNSFileManagerSwizzling.m | 1 + Sources/Sentry/include/SentryFileIOTracker.h | 6 + .../Transactions/SentrySpanDataKey.swift | 24 ++ .../Transactions/SentrySpanOperation.swift | 11 + .../Transactions/SentryTraceOrigin.swift | 1 + .../IO/SentryFileIOTrackerTests.swift | 207 ++++++++++-------- .../Transactions/SentrySpanKeyTests.swift | 12 + .../SentrySpanOperationTests.swift | 12 + .../Transactions/SentryTraceOriginTests.swift | 4 + 12 files changed, 241 insertions(+), 103 deletions(-) create mode 100644 Sources/Swift/Transactions/SentrySpanDataKey.swift create mode 100644 Tests/SentryTests/Transactions/SentrySpanKeyTests.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 9963785277..c805adb72d 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -811,6 +811,8 @@ A8F17B2E2901765900990B25 /* SentryRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B2D2901765900990B25 /* SentryRequest.m */; }; A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */; }; D42E48572D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48562D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift */; }; + D43647EF2D5CF9E3001468E0 /* SentrySpanDataKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43647EE2D5CF9DC001468E0 /* SentrySpanDataKey.swift */; }; + D43647F12D5CFB71001468E0 /* SentrySpanKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43647F02D5CFB71001468E0 /* SentrySpanKeyTests.swift */; }; D48724DB2D352597005DE483 /* SentryTraceOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */; }; D48724DD2D354939005DE483 /* SentrySpanOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DC2D354934005DE483 /* SentrySpanOperation.swift */; }; D48724E02D3549CA005DE483 /* SentrySpanOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */; }; @@ -1949,6 +1951,8 @@ D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryNSDictionarySanitize+Tests.m"; sourceTree = ""; }; D42E48562D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBuildAppStartSpansTests.swift; sourceTree = ""; }; D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSDictionarySanitizeTests.swift; sourceTree = ""; }; + D43647EE2D5CF9DC001468E0 /* SentrySpanDataKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanDataKey.swift; sourceTree = ""; }; + D43647F02D5CFB71001468E0 /* SentrySpanKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentrySpanKeyTests.swift; sourceTree = ""; }; D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOrigin.swift; sourceTree = ""; }; D48724DC2D354934005DE483 /* SentrySpanOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperation.swift; sourceTree = ""; }; D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperationTests.swift; sourceTree = ""; }; @@ -3785,6 +3789,7 @@ children = ( D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */, D48724DC2D354934005DE483 /* SentrySpanOperation.swift */, + D43647EE2D5CF9DC001468E0 /* SentrySpanDataKey.swift */, ); path = Transactions; sourceTree = ""; @@ -3794,6 +3799,7 @@ children = ( D48724E12D354D16005DE483 /* SentryTraceOriginTests.swift */, D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */, + D43647F02D5CFB71001468E0 /* SentrySpanKeyTests.swift */, ); path = Transactions; sourceTree = ""; @@ -4957,6 +4963,7 @@ D82859432C3E753C009A28AA /* SentrySessionReplaySyncC.c in Sources */, D833D57C2D10784800961E7A /* SentryRRWebOptionsEvent.swift in Sources */, 84A8891D28DBD28900C51DFD /* SentryDevice.mm in Sources */, + D43647EF2D5CF9E3001468E0 /* SentrySpanDataKey.swift in Sources */, 7B56D73324616D9500B842DA /* SentryConcurrentRateLimitsDictionary.m in Sources */, 8ECC674825C23A20000E2BF6 /* SentryTransaction.m in Sources */, 0A80E433291017C300095219 /* SentryWatchdogTerminationScopeObserver.m in Sources */, @@ -5392,6 +5399,7 @@ 7BF9EF7A2722B58900B5BBEF /* SentrySubClassFinderTests.swift in Sources */, D48724E02D3549CA005DE483 /* SentrySpanOperationTests.swift in Sources */, 7B59398224AB47650003AAD2 /* SentrySessionTrackerTests.swift in Sources */, + D43647F12D5CFB71001468E0 /* SentrySpanKeyTests.swift in Sources */, 7B05A61824A4D14A00EF211D /* SentrySessionGeneratorTests.swift in Sources */, D8CB742B294B1DD000A5F964 /* SentryUIApplicationTests.swift in Sources */, 63FE720920DA66EC00CDBAE8 /* XCTestCase+SentryCrash.m in Sources */, diff --git a/Sources/Sentry/SentryFileIOTracker.m b/Sources/Sentry/SentryFileIOTracker.m index 1c6d412e5e..51af175baa 100644 --- a/Sources/Sentry/SentryFileIOTracker.m +++ b/Sources/Sentry/SentryFileIOTracker.m @@ -59,9 +59,10 @@ - (void)disable - (BOOL)measureNSData:(NSData *)data writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile + origin:(NSString *)origin method:(BOOL (^)(NSString *, BOOL))method { - id span = [self startTrackingWritingNSData:data filePath:path]; + id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; BOOL result = method(path, useAuxiliaryFile); @@ -74,10 +75,11 @@ - (BOOL)measureNSData:(NSData *)data - (BOOL)measureNSData:(NSData *)data writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method { - id span = [self startTrackingWritingNSData:data filePath:path]; + id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; BOOL result = method(path, writeOptionsMask, error); @@ -88,9 +90,13 @@ - (BOOL)measureNSData:(NSData *)data return result; } -- (NSData *)measureNSDataFromFile:(NSString *)path method:(NSData * (^)(NSString *))method +- (NSData *)measureNSDataFromFile:(NSString *)path + origin:(NSString *)origin + method:(NSData * (^)(NSString *))method { - id span = [self startTrackingReadingFilePath:path]; + id span = [self startTrackingReadingFilePath:path + origin:origin + operation:SentrySpanOperation.fileRead]; NSData *result = method(path); @@ -104,10 +110,13 @@ - (NSData *)measureNSDataFromFile:(NSString *)path method:(NSData * (^)(NSString - (NSData *)measureNSDataFromFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(NSData * (^)(NSString *, NSDataReadingOptions, NSError **))method { - id span = [self startTrackingReadingFilePath:path]; + id span = [self startTrackingReadingFilePath:path + origin:origin + operation:SentrySpanOperation.fileRead]; NSData *result = method(path, readOptionsMask, error); @@ -121,6 +130,7 @@ - (NSData *)measureNSDataFromFile:(NSString *)path - (NSData *)measureNSDataFromURL:(NSURL *)url options:(NSDataReadingOptions)readOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(NSData * (^)(NSURL *, NSDataReadingOptions, NSError **))method { @@ -131,7 +141,9 @@ - (NSData *)measureNSDataFromURL:(NSURL *)url if (![url.scheme isEqualToString:NSURLFileScheme]) return method(url, readOptionsMask, error); - id span = [self startTrackingReadingFilePath:url.path]; + id span = [self startTrackingReadingFilePath:url.path + origin:origin + operation:SentrySpanOperation.fileRead]; NSData *result = method(url, readOptionsMask, error); @@ -146,11 +158,12 @@ - (NSData *)measureNSDataFromURL:(NSURL *)url - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path data:(NSData *)data attributes:(NSDictionary *)attributes + origin:(NSString *)origin method: (BOOL (^)(NSString *_Nonnull, NSData *_Nonnull, NSDictionary *_Nonnull))method { - id span = [self startTrackingWritingNSData:data filePath:path]; + id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; BOOL result = method(path, data, attributes); @@ -161,6 +174,7 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path } - (nullable id)spanForPath:(NSString *)path + origin:(NSString *)origin operation:(NSString *)operation size:(NSUInteger)size { @@ -179,7 +193,7 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path ioSpan = [span startChildWithOperation:operation description:[self transactionDescriptionForFile:path fileSize:size]]; - ioSpan.origin = SentryTraceOrigin.autoNSData; + ioSpan.origin = origin; }]; if (ioSpan == nil) { @@ -187,11 +201,10 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path return nil; } - SENTRY_LOG_DEBUG( - @"SentryNSDataTracker automatically started a new span with description: %@, operation: %@", + SENTRY_LOG_DEBUG(@"Automatically started a new span with description: %@, operation: %@", ioSpan.description, operation); - [ioSpan setDataValue:path forKey:@"file.path"]; + [ioSpan setDataValue:path forKey:SentrySpanDataKey.filePath]; [self mainThreadExtraInfo:ioSpan]; @@ -228,12 +241,19 @@ - (void)mainThreadExtraInfo:(id)span } } -- (nullable id)startTrackingWritingNSData:(NSData *)data filePath:(NSString *)path +- (nullable id)startTrackingWritingNSData:(NSData *)data + filePath:(NSString *)path + origin:(NSString *)origin { - return [self spanForPath:path operation:SentrySpanOperation.fileWrite size:data.length]; + return [self spanForPath:path + origin:origin + operation:SentrySpanOperation.fileWrite + size:data.length]; } - (nullable id)startTrackingReadingFilePath:(NSString *)path + origin:(NSString *)origin + operation:(NSString *)operation { // Some iOS versions nest constructors calls. This counter help us avoid create more than one // span for the same operation. @@ -245,7 +265,7 @@ - (void)mainThreadExtraInfo:(id)span if (count) return nil; - return [self spanForPath:path operation:SentrySpanOperation.fileRead size:0]; + return [self spanForPath:path origin:origin operation:operation size:0]; } - (void)endTrackingFile @@ -266,10 +286,11 @@ - (void)endTrackingFile - (void)finishTrackingNSData:(NSData *)data span:(id)span { - [span setDataValue:[NSNumber numberWithUnsignedInteger:data.length] forKey:@"file.size"]; + [span setDataValue:[NSNumber numberWithUnsignedInteger:data.length] + forKey:SentrySpanDataKey.fileSize]; [span finish]; - SENTRY_LOG_DEBUG(@"SentryNSDataTracker automatically finished span %@", span.description); + SENTRY_LOG_DEBUG(@"Automatically finished span %@", span.description); } - (BOOL)ignoreFile:(NSString *)path diff --git a/Sources/Sentry/SentryNSDataSwizzling.m b/Sources/Sentry/SentryNSDataSwizzling.m index c3b7a52ed8..3ec1b0a83b 100644 --- a/Sources/Sentry/SentryNSDataSwizzling.m +++ b/Sources/Sentry/SentryNSDataSwizzling.m @@ -45,6 +45,7 @@ + (void)swizzle measureNSData:self writeToFile:path atomically:useAuxiliaryFile + origin:SentryTraceOrigin.autoNSData method:^BOOL(NSString *_Nonnull filePath, BOOL isAtomically) { return SentrySWCallOriginal(filePath, isAtomically); }]; @@ -60,6 +61,7 @@ + (void)swizzle measureNSData:self writeToFile:path options:writeOptionsMask + origin:SentryTraceOrigin.autoNSData error:error method:^BOOL( NSString *filePath, NSDataWritingOptions options, NSError **outError) { @@ -77,6 +79,7 @@ + (void)swizzle return [SentryNSDataSwizzling.shared.tracker measureNSDataFromFile:path options:options + origin:SentryTraceOrigin.autoNSData error:error method:^NSData *(NSString *filePath, NSDataReadingOptions options, NSError **outError) { @@ -91,6 +94,7 @@ + (void)swizzle SentrySWReturnType(NSData *), SentrySWArguments(NSString * path), SentrySWReplacement({ return [SentryNSDataSwizzling.shared.tracker measureNSDataFromFile:path + origin:SentryTraceOrigin.autoNSData method:^NSData *( NSString *filePath) { return SentrySWCallOriginal(filePath); }]; }), @@ -105,6 +109,7 @@ + (void)swizzle return [SentryNSDataSwizzling.shared.tracker measureNSDataFromURL:url options:options + origin:SentryTraceOrigin.autoNSData error:error method:^NSData *(NSURL *fileUrl, NSDataReadingOptions options, NSError **outError) { diff --git a/Sources/Sentry/SentryNSFileManagerSwizzling.m b/Sources/Sentry/SentryNSFileManagerSwizzling.m index dea214238a..234c27f94b 100644 --- a/Sources/Sentry/SentryNSFileManagerSwizzling.m +++ b/Sources/Sentry/SentryNSFileManagerSwizzling.m @@ -59,6 +59,7 @@ + (void)swizzle measureNSFileManagerCreateFileAtPath:path data:data attributes:attributes + origin:SentryTraceOrigin.autoNSData method:^BOOL(NSString *path, NSData *data, NSDictionary *attributes) { diff --git a/Sources/Sentry/include/SentryFileIOTracker.h b/Sources/Sentry/include/SentryFileIOTracker.h index fd5316bfe7..d45f437122 100644 --- a/Sources/Sentry/include/SentryFileIOTracker.h +++ b/Sources/Sentry/include/SentryFileIOTracker.h @@ -21,6 +21,7 @@ SENTRY_NO_INIT - (BOOL)measureNSData:(NSData *)data writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile + origin:(NSString *)origin method:(BOOL (^)(NSString *, BOOL))method; /** @@ -29,6 +30,7 @@ SENTRY_NO_INIT - (BOOL)measureNSData:(NSData *)data writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method; @@ -36,6 +38,7 @@ SENTRY_NO_INIT * Measure NSData 'initWithContentsOfFile:' method. */ - (nullable NSData *)measureNSDataFromFile:(NSString *)path + origin:(NSString *)origin method:(NSData *_Nullable (^)(NSString *))method; /** @@ -43,6 +46,7 @@ SENTRY_NO_INIT */ - (nullable NSData *)measureNSDataFromFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(NSData *_Nullable (^)( NSString *, NSDataReadingOptions, NSError **))method; @@ -52,6 +56,7 @@ SENTRY_NO_INIT */ - (nullable NSData *)measureNSDataFromURL:(NSURL *)url options:(NSDataReadingOptions)readOptionsMask + origin:(NSString *)origin error:(NSError **)error method:(NSData *_Nullable (^)( NSURL *, NSDataReadingOptions, NSError **))method; @@ -62,6 +67,7 @@ SENTRY_NO_INIT - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path data:(NSData *)data attributes:(NSDictionary *)attributes + origin:(NSString *)origin method:(BOOL (^)(NSString *, NSData *, NSDictionary *))method; diff --git a/Sources/Swift/Transactions/SentrySpanDataKey.swift b/Sources/Swift/Transactions/SentrySpanDataKey.swift new file mode 100644 index 0000000000..8e18dd69e0 --- /dev/null +++ b/Sources/Swift/Transactions/SentrySpanDataKey.swift @@ -0,0 +1,24 @@ +import Foundation + +/** + * Constants for span data field keys. + * + * These keys are used to attach additional data to spans in a standardized way. + * + * The keys follow [OpenTelemetry's semantic conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/trace.md) + * for attributes and must be: + * - Lowercase + * - Use underscores for word separation + * - Follow the format `.` (e.g. `file.size`) + * + * See [Sentry SDK development documentation](https://develop.sentry.dev/sdk/telemetry/traces/span-data-conventions/) for + * more information. + */ +@objcMembers @objc(SentrySpanDataKey) +class SentrySpanDataKey: NSObject { + /// Used to set the number of bytes processed in a file span operation + static let fileSize = "file.size" + + /// Used to set the path of the file in a file span operation + static let filePath = "file.path" +} diff --git a/Sources/Swift/Transactions/SentrySpanOperation.swift b/Sources/Swift/Transactions/SentrySpanOperation.swift index 30ccf342ea..5b47f12496 100644 --- a/Sources/Swift/Transactions/SentrySpanOperation.swift +++ b/Sources/Swift/Transactions/SentrySpanOperation.swift @@ -1,5 +1,13 @@ import Foundation +/** + * Span operations are short string identifiers that categorize the type of operation a span is measuring. + * + * They follow a hierarchical dot notation format (e.g. 'ui.load.initial_display') to group related operations. + * These identifiers help organize and analyze performance data across different types of operations. + * + * See [Sentry SDK development documentation](https://develop.sentry.dev/sdk/telemetry/traces/span-operations/) for more information. + */ @objcMembers @objc(SentrySpanOperation) class SentrySpanOperation: NSObject { static let appLifecycle = "app.lifecycle" @@ -9,6 +17,9 @@ class SentrySpanOperation: NSObject { static let fileRead = "file.read" static let fileWrite = "file.write" + static let fileCopy = "file.copy" + static let fileRename = "file.rename" + static let fileDelete = "file.delete" static let networkRequestOperation = "http.client" diff --git a/Sources/Swift/Transactions/SentryTraceOrigin.swift b/Sources/Swift/Transactions/SentryTraceOrigin.swift index 34725f51a1..3a8661bdc7 100644 --- a/Sources/Swift/Transactions/SentryTraceOrigin.swift +++ b/Sources/Swift/Transactions/SentryTraceOrigin.swift @@ -11,5 +11,6 @@ class SentryTraceOrigin: NSObject { static let autoUITimeToDisplay = "auto.ui.time_to_display" static let autoUIViewController = "auto.ui.view_controller" static let manual = "manual" + static let manualFileData = "manual.file.data" static let manualUITimeToDisplay = "manual.ui.time_to_display" } diff --git a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift index ec7eeb4970..33fea1a4a0 100644 --- a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift @@ -5,9 +5,11 @@ import XCTest class SentryFileIOTrackerTests: XCTestCase { private class Fixture { - + let filePath = "Some Path" - let sentryPath = try! TestFileManager(options: Options()).sentryPath + let fileURL = URL(fileURLWithPath: "Some Path") + let sentryPath = try! TestFileManager(options: Options()).sentryPath + let sentryUrl = URL(fileURLWithPath: try! TestFileManager(options: Options()).sentryPath) let dateProvider = TestCurrentDateProvider() let data = "SOME DATA".data(using: .utf8)! let threadInspector = TestThreadInspector.instance @@ -28,9 +30,9 @@ class SentryFileIOTrackerTests: XCTestCase { return result } } - + private var fixture: Fixture! - + override func setUp() { super.setUp() fixture = Fixture() @@ -39,59 +41,58 @@ class SentryFileIOTrackerTests: XCTestCase { $0.removeAllIntegrations() } } - + override func tearDown() { super.tearDown() clearTestState() } - - func testConstants() { - //A test to ensure this constants don't accidentally change - XCTAssertEqual("file.read", SentrySpanOperation.fileRead) - XCTAssertEqual("file.write", SentrySpanOperation.fileWrite) - } - + func testWritePathAtomically() { let sut = fixture.getSut() var methodPath: String? var methodAuxiliareFile: Bool? - - var result = sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false) { path, useAuxiliareFile -> Bool in + + var result = sut.measure( + fixture.data, + writeToFile: fixture.filePath, + atomically: false, + origin: "custom.origin" + ) { path, useAuxiliareFile -> Bool in methodPath = path methodAuxiliareFile = useAuxiliareFile return false } - + XCTAssertEqual(fixture.filePath, methodPath) XCTAssertFalse(methodAuxiliareFile!) XCTAssertFalse(result) - - result = sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: true) { _, useAuxiliareFile -> Bool in + + result = sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: true, origin: "custom.origin") { _, useAuxiliareFile -> Bool in methodAuxiliareFile = useAuxiliareFile return true } - + XCTAssertTrue(methodAuxiliareFile!) XCTAssertTrue(result) } - + func testWritePathOptionsError() { let sut = fixture.getSut() var methodPath: String? var methodOptions: NSData.WritingOptions? var methodError: NSError? - - try! sut.measure(fixture.data, writeToFile: fixture.filePath, options: .atomic) { path, writingOption, _ -> Bool in + + try! sut.measure(fixture.data, writeToFile: fixture.filePath, options: .atomic, origin: "custom.origin") { path, writingOption, _ -> Bool in methodPath = path methodOptions = writingOption return true } - + XCTAssertEqual(fixture.filePath, methodPath) XCTAssertEqual(methodOptions, .atomic) - + do { - try sut.measure(fixture.data, writeToFile: fixture.filePath, options: .withoutOverwriting) { _, writingOption, errorPointer -> Bool in + try sut.measure(fixture.data, writeToFile: fixture.filePath, options: .withoutOverwriting, origin: "custom.origin") { _, writingOption, errorPointer -> Bool in methodOptions = writingOption errorPointer?.pointee = NSError(domain: "Test Error", code: -2, userInfo: nil) return false @@ -99,25 +100,25 @@ class SentryFileIOTrackerTests: XCTestCase { } catch { methodError = error as NSError? } - + XCTAssertEqual(methodOptions, .withoutOverwriting) XCTAssertEqual(methodError?.domain, "Test Error") } - + func testWriteAtomically_CheckTrace() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false) { _, _ -> Bool in + sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false, origin: "custom.origin") { _, _ -> Bool in span = self.firstSpan(transaction) XCTAssertFalse(span?.isFinished ?? true) self.advanceTime(bySeconds: 4) return true } - + assertSpanDuration(span: span, expectedDuration: 4) - assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileWrite, size: fixture.data.count) + assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileWrite, size: fixture.data.count, origin: "custom.origin") } func testWriteAtomically_CheckTransaction_DebugImages() { @@ -125,7 +126,7 @@ class SentryFileIOTrackerTests: XCTestCase { let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false) { _, _ -> Bool in + sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false, origin: "custom.origin") { _, _ -> Bool in span = self.firstSpan(transaction) XCTAssertFalse(span?.isFinished ?? true) self.advanceTime(bySeconds: 4) @@ -150,7 +151,7 @@ class SentryFileIOTrackerTests: XCTestCase { var span: SentrySpan? - sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false) { _, _ -> Bool in + sut.measure(fixture.data, writeToFile: fixture.filePath, atomically: false, origin: "custom.origin") { _, _ -> Bool in span = self.firstSpan(transaction) as? SentrySpan XCTAssertFalse(span?.isFinished ?? true) return true @@ -168,7 +169,7 @@ class SentryFileIOTrackerTests: XCTestCase { let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - sut.measure(self.fixture.data, writeToFile: self.fixture.filePath, atomically: false) { _, _ -> Bool in + sut.measure(self.fixture.data, writeToFile: self.fixture.filePath, atomically: false, origin: "custom.origin") { _, _ -> Bool in span = self.firstSpan(transaction) XCTAssertFalse(span?.isFinished ?? true) self.advanceTime(bySeconds: 4) @@ -176,84 +177,84 @@ class SentryFileIOTrackerTests: XCTestCase { } self.assertSpanDuration(span: span, expectedDuration: 4) - self.assertDataSpan(span, path: self.fixture.filePath, operation: SentrySpanOperation.fileWrite, size: self.fixture.data.count, mainThread: false) + self.assertDataSpan(span, path: self.fixture.filePath, operation: SentrySpanOperation.fileWrite, size: self.fixture.data.count, origin: "custom.origin", mainThread: false) expect.fulfill() } wait(for: [expect], timeout: 0.1) } - + func testWriteWithOptionsAndError_CheckTrace() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - - try! sut.measure(fixture.data, writeToFile: fixture.filePath, options: .atomic) { _, _, _ -> Bool in + + try! sut.measure(fixture.data, writeToFile: fixture.filePath, options: .atomic, origin: "custom.origin") { _, _, _ -> Bool in span = self.firstSpan(transaction) XCTAssertFalse(span?.isFinished ?? true) self.advanceTime(bySeconds: 3) return true } - + assertSpanDuration(span: span, expectedDuration: 3) - assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileWrite, size: fixture.data.count) + assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileWrite, size: fixture.data.count, origin: "custom.origin") } - + func testDontTrackSentryFilesWrites() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - + let expect = expectation(description: "") - try! sut.measure(fixture.data, writeToFile: fixture.sentryPath, options: .atomic) { _, _, _ -> Bool in + try! sut.measure(fixture.data, writeToFile: fixture.sentryPath, options: .atomic, origin: "custom.origin") { _, _, _ -> Bool in span = self.firstSpan(transaction) expect.fulfill() return true } - + XCTAssertNil(span) wait(for: [expect], timeout: 0.1) } - + func testReadFromString() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? var usedPath: String? - - let data = sut.measureNSData(fromFile: fixture.filePath) { path in + + let data = sut.measureNSData(fromFile: fixture.filePath, origin: "custom.origin") { path in span = self.firstSpan(transaction) usedPath = path return self.fixture.data } - + XCTAssertEqual(usedPath, fixture.filePath) XCTAssertEqual(data?.count, fixture.data.count) - - assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileRead, size: fixture.data.count) + + assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileRead, size: fixture.data.count, origin: "custom.origin") } - + func testReadFromStringOptionsError() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? var usedPath: String? var usedOptions: NSData.ReadingOptions? - - let data = try? sut.measureNSData(fromFile: self.fixture.filePath, options: .uncached) { path, options, _ -> Data in + + let data = try? sut.measureNSData(fromFile: self.fixture.filePath, options: .uncached, origin: "custom.origin") { path, options, _ -> Data in span = self.firstSpan(transaction) usedOptions = options usedPath = path return self.fixture.data } - + XCTAssertEqual(usedPath, fixture.filePath) XCTAssertEqual(data?.count, fixture.data.count) XCTAssertEqual(usedOptions, .uncached) - - assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileRead, size: fixture.data.count) + + assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileRead, size: fixture.data.count, origin: "custom.origin") } - + func testReadFromURLOptionsError() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) @@ -261,21 +262,21 @@ class SentryFileIOTrackerTests: XCTestCase { var usedUrl: URL? let url = URL(fileURLWithPath: fixture.filePath) var usedOptions: NSData.ReadingOptions? - - let data = try? sut.measureNSData(from: url, options: .uncached) { url, options, _ in + + let data = try? sut.measureNSData(from: url, options: .uncached, origin: "custom.origin") { url, options, _ in span = self.firstSpan(transaction) usedOptions = options usedUrl = url return self.fixture.data } - + XCTAssertEqual(usedUrl, url) XCTAssertEqual(data?.count, fixture.data.count) XCTAssertEqual(usedOptions, .uncached) - - assertDataSpan(span, path: url.path, operation: SentrySpanOperation.fileRead, size: fixture.data.count) + + assertDataSpan(span, path: url.path, operation: SentrySpanOperation.fileRead, size: fixture.data.count, origin: "custom.origin") } - + func testCreateFile() { let sut = fixture.getSut() @@ -288,7 +289,7 @@ class SentryFileIOTrackerTests: XCTestCase { sut.measureNSFileManagerCreateFile(atPath: fixture.filePath, data: fixture.data, attributes: [ FileAttributeKey.size: 123 - ], method: { path, data, attributes in + ], origin: "custom.origin", method: { path, data, attributes in methodPath = path methodData = data methodAttributes = attributes @@ -304,63 +305,95 @@ class SentryFileIOTrackerTests: XCTestCase { XCTAssertEqual(methodAttributes?[FileAttributeKey.size] as? Int, 123) assertSpanDuration(span: span, expectedDuration: 4) - assertDataSpan(span, path: fixture.filePath, operation: SentrySpanOperation.fileWrite, size: fixture.data.count) + assertDataSpan( + span, + path: fixture.filePath, + operation: SentrySpanOperation.fileWrite, + size: fixture.data.count, origin: "custom.origin" + ) } - + func testDontTrackSentryFilesRead() { let sut = fixture.getSut() let transaction = SentrySDK.startTransaction(name: "Transaction", operation: "Test", bindToScope: true) var span: Span? - + let expect = expectation(description: "") - let _ = sut.measureNSData(fromFile: fixture.sentryPath) { _ in + let _ = sut.measureNSData(fromFile: fixture.sentryPath, origin: "custom.origin") { _ in span = self.firstSpan(transaction) expect.fulfill() return nil } - + XCTAssertNil(span) wait(for: [expect], timeout: 0.1) } - + private func firstSpan(_ transaction: Span) -> Span? { let result = Dynamic(transaction).children as [Span]? return result?.first } - - private func assertDataSpan(_ span: Span?, path: String, operation: String, size: Int, mainThread: Bool = true ) { - XCTAssertNotNil(span) - XCTAssertEqual(span?.operation, operation) - XCTAssertEqual(span?.origin, "auto.file.ns_data") - XCTAssertTrue(span?.isFinished ?? false) - XCTAssertEqual(span?.data["file.size"] as? Int, size) - XCTAssertEqual(span?.data["file.path"] as? String, path) + + private func assertDataSpan( + _ span: Span?, + url: URL, + operation: String, + size: Int, + origin: String, + mainThread: Bool = true, + file: StaticString = #file, + line: UInt = #line + ) { + assertDataSpan(span, path: url.path, operation: operation, size: size, origin: origin, file: file, line: line) + } + + private func assertDataSpan( + _ span: Span?, + path: String, + operation: String, + size: Int, + origin: String, + mainThread: Bool = true, + file: StaticString = #file, + line: UInt = #line + ) { + XCTAssertNotNil(span, file: file, line: line) + XCTAssertEqual(span?.operation, operation, file: file, line: line) + XCTAssertEqual(span?.origin, origin, file: file, line: line) + XCTAssertTrue(span?.isFinished ?? false, file: file, line: line) + XCTAssertEqual(span?.data["file.size"] as? Int, size, file: file, line: line) + XCTAssertEqual(span?.data["file.path"] as? String, path, file: file, line: line) XCTAssertEqual(span?.data["blocked_main_thread"] as? Bool ?? false, mainThread) if mainThread { guard let frames = (span as? SentrySpan)?.frames else { - XCTFail("File IO Span in the main thread has no frames") + XCTFail("File IO Span in the main thread has no frames", file: file, line: line) return } - XCTAssertEqual(frames.first, TestData.mainFrame) - XCTAssertEqual(frames.last, TestData.testFrame) + XCTAssertEqual(frames.first, TestData.mainFrame, file: file, line: line) + XCTAssertEqual(frames.last, TestData.testFrame, file: file, line: line) } - + let lastComponent = (path as NSString).lastPathComponent - + if operation == SentrySpanOperation.fileRead { - XCTAssertEqual(span?.spanDescription, lastComponent) + XCTAssertEqual(span?.spanDescription, lastComponent, file: file, line: line) } else { let bytesDescription = SentryByteCountFormatter.bytesCountDescription( UInt(size)) - XCTAssertEqual(span?.spanDescription ?? "", "\(lastComponent) (\(bytesDescription))") + XCTAssertEqual(span?.spanDescription ?? "", "\(lastComponent) (\(bytesDescription))", file: file, line: line) } } - - private func assertSpanDuration(span: Span?, expectedDuration: TimeInterval) { + + private func assertSpanDuration( + span: Span?, + expectedDuration: TimeInterval, + file: StaticString = #file, + line: UInt = #line + ) { let duration = span?.timestamp?.timeIntervalSince(span!.startTimestamp!) - XCTAssertEqual(duration, expectedDuration) + XCTAssertEqual(duration, expectedDuration, file: file, line: line) } - + private func advanceTime(bySeconds: TimeInterval) { fixture.dateProvider.setDate(date: fixture.dateProvider.date().addingTimeInterval(bySeconds)) } diff --git a/Tests/SentryTests/Transactions/SentrySpanKeyTests.swift b/Tests/SentryTests/Transactions/SentrySpanKeyTests.swift new file mode 100644 index 0000000000..2699ec8b58 --- /dev/null +++ b/Tests/SentryTests/Transactions/SentrySpanKeyTests.swift @@ -0,0 +1,12 @@ +@testable import Sentry +import XCTest + +class SentrySpanDataKeyTests: XCTestCase { + func testFileSize_shouldBeExpectedValue() { + XCTAssertEqual(SentrySpanDataKey.fileSize, "file.size") + } + + func testFilePath_shouldBeExpectedValue() { + XCTAssertEqual(SentrySpanDataKey.filePath, "file.path") + } +} diff --git a/Tests/SentryTests/Transactions/SentrySpanOperationTests.swift b/Tests/SentryTests/Transactions/SentrySpanOperationTests.swift index f6e45bb3c4..2832175170 100644 --- a/Tests/SentryTests/Transactions/SentrySpanOperationTests.swift +++ b/Tests/SentryTests/Transactions/SentrySpanOperationTests.swift @@ -22,6 +22,18 @@ class SentrySpanOperationTests: XCTestCase { XCTAssertEqual(SentrySpanOperation.fileWrite, "file.write") } + func testFileCopy_shouldBeExpectedValue() { + XCTAssertEqual(SentrySpanOperation.fileCopy, "file.copy") + } + + func testFileRename_shouldBeExpectedValue() { + XCTAssertEqual(SentrySpanOperation.fileRename, "file.rename") + } + + func testFileDelete_shouldBeExpectedValue() { + XCTAssertEqual(SentrySpanOperation.fileDelete, "file.delete") + } + func testNetworkRequestOperation_shouldBeExpectedValue() { XCTAssertEqual(SentrySpanOperation.networkRequestOperation, "http.client") } diff --git a/Tests/SentryTests/Transactions/SentryTraceOriginTests.swift b/Tests/SentryTests/Transactions/SentryTraceOriginTests.swift index 1d50c02034..57702751bf 100644 --- a/Tests/SentryTests/Transactions/SentryTraceOriginTests.swift +++ b/Tests/SentryTests/Transactions/SentryTraceOriginTests.swift @@ -38,6 +38,10 @@ class SentryTraceOriginTestsTests: XCTestCase { XCTAssertEqual(SentryTraceOrigin.manual, "manual") } + func testManualFileData_shouldBeExpectedValue() { + XCTAssertEqual(SentryTraceOrigin.manualFileData, "manual.file.data") + } + func testManualUITimeToDisplay_shouldBeExpectedValue() { XCTAssertEqual(SentryTraceOrigin.manualUITimeToDisplay, "manual.ui.time_to_display") } From d33f2c8c125bb85270563a071f05812090b4d4d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:54:24 +0100 Subject: [PATCH 2/2] chore(deps): bump nokogiri from 1.16.7 to 1.18.3 (#4870) Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.7 to 1.18.3. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/v1.18.3/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.7...v1.18.3) --- updated-dependencies: - dependency-name: nokogiri dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 78fd6319f1..c28d616d7f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -232,7 +232,7 @@ GEM mime-types-data (3.2023.1003) mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.7) + mini_portile2 (2.8.8) minitest (5.25.1) molinillo (0.8.0) multi_json (1.15.0) @@ -242,7 +242,7 @@ GEM naturally (2.2.1) netrc (0.11.0) nkf (0.2.0) - nokogiri (1.16.7) + nokogiri (1.18.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) optparse (0.5.0)