From 4aaa2d7c3aaf0db42ded28a862a9c22becf7788c Mon Sep 17 00:00:00 2001 From: John Flanagan Date: Mon, 13 Jan 2025 16:42:10 -0600 Subject: [PATCH 1/2] Record Attachments in Never mode --- Sources/SnapshotTesting/AssertSnapshot.swift | 66 +++++++++++++++----- Tests/SnapshotTestingTests/RecordTests.swift | 2 +- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index fefe1e6f7..fbdcc2a97 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -352,42 +352,69 @@ public func verifySnapshot( return "Couldn't snapshot value" } - func recordSnapshot() throws { - try snapshotting.diffing.toData(diffable).write(to: snapshotFileUrl) + func recordSnapshot(writeToDisk: Bool) throws { + let snapshotData = snapshotting.diffing.toData(diffable) + + if writeToDisk { + try snapshotData.write(to: snapshotFileUrl) + } + #if !os(Linux) && !os(Windows) if !isSwiftTesting, ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { XCTContext.runActivity(named: "Attached Recorded Snapshot") { activity in - let attachment = XCTAttachment(contentsOfFile: snapshotFileUrl) - activity.add(attachment) + if writeToDisk { + // Snapshot was written to disk. Create attachment from file + let attachment = XCTAttachment(contentsOfFile: snapshotFileUrl) + activity.add(attachment) + } else { + // Snapshot was not written to disk. Create attachment from data and path extension + let typeIdentifier = snapshotting.pathExtension.flatMap(uniformTypeIdentifier(fromExtension:)) + + let attachment = XCTAttachment( + uniformTypeIdentifier: typeIdentifier, + name: snapshotFileUrl.lastPathComponent, + payload: snapshotData + ) + + activity.add(attachment) + } } } #endif } - guard - record != .all, - (record != .missing && record != .failed) - || fileManager.fileExists(atPath: snapshotFileUrl.path) - else { - try recordSnapshot() + if record == .all { + try recordSnapshot(writeToDisk: true) - return SnapshotTestingConfiguration.current?.record == .all - ? """ + return """ Record mode is on. Automatically recorded snapshot: … open "\(snapshotFileUrl.absoluteString)" Turn record mode off and re-run "\(testName)" to assert against the newly-recorded snapshot """ - : """ + } + + guard fileManager.fileExists(atPath: snapshotFileUrl.path) else { + if record == .never { + try recordSnapshot(writeToDisk: false) + + return """ + No reference was found on disk. New snapshot was not recorded because recording is disabled + """ + } else { + try recordSnapshot(writeToDisk: true) + + return """ No reference was found on disk. Automatically recorded snapshot: … open "\(snapshotFileUrl.absoluteString)" Re-run "\(testName)" to assert against the newly-recorded snapshot. """ + } } let data = try Data(contentsOf: snapshotFileUrl) @@ -444,7 +471,7 @@ public func verifySnapshot( } if record == .failed { - try recordSnapshot() + try recordSnapshot(writeToDisk: true) failureMessage += " A new snapshot was automatically recorded." } @@ -473,6 +500,17 @@ func sanitizePathComponent(_ string: String) -> String { .replacingOccurrences(of: "^-|-$", with: "", options: .regularExpression) } +func uniformTypeIdentifier(fromExtension pathExtension: String) -> String? { + // This can be much cleaner in macOS 11+ using UTType + let unmanagedString = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension as CFString, + pathExtension as CFString, + nil + ) + + return unmanagedString?.takeRetainedValue() as String? +} + // We need to clean counter between tests executions in order to support test-iterations. private class CleanCounterBetweenTestCases: NSObject, XCTestObservation { private static var registered = false diff --git a/Tests/SnapshotTestingTests/RecordTests.swift b/Tests/SnapshotTestingTests/RecordTests.swift index cfe40e83f..26e9c6644 100644 --- a/Tests/SnapshotTestingTests/RecordTests.swift +++ b/Tests/SnapshotTestingTests/RecordTests.swift @@ -44,7 +44,7 @@ class RecordTests: XCTestCase { } } issueMatcher: { $0.compactDescription == """ - failed - The file “testRecordNever.1.json” couldn’t be opened because there is no such file. + failed - No reference was found on disk. New snapshot was not recorded because recording is disabled """ } From 0c66249ef5b0bc3899cc5e6f7f8d52b035c0107c Mon Sep 17 00:00:00 2001 From: John Flanagan Date: Tue, 14 Jan 2025 10:52:00 -0600 Subject: [PATCH 2/2] PR Feedback --- .github/workflows/ci.yml | 2 +- Sources/SnapshotTesting/AssertSnapshot.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27815d448..f6a6d40ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: matrix: xcode: - 15.4 - - '16.0' + - '16.1' name: macOS runs-on: macos-14 diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index fbdcc2a97..391d30cbb 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -500,6 +500,7 @@ func sanitizePathComponent(_ string: String) -> String { .replacingOccurrences(of: "^-|-$", with: "", options: .regularExpression) } +#if !os(Linux) && !os(Windows) func uniformTypeIdentifier(fromExtension pathExtension: String) -> String? { // This can be much cleaner in macOS 11+ using UTType let unmanagedString = UTTypeCreatePreferredIdentifierForTag( @@ -510,6 +511,7 @@ func uniformTypeIdentifier(fromExtension pathExtension: String) -> String? { return unmanagedString?.takeRetainedValue() as String? } +#endif // We need to clean counter between tests executions in order to support test-iterations. private class CleanCounterBetweenTestCases: NSObject, XCTestObservation {