From 51851b071769e734d32c72a9c003b09ea89402f1 Mon Sep 17 00:00:00 2001 From: Leandro Demarco Vedelago Date: Fri, 6 Sep 2024 16:01:09 -0300 Subject: [PATCH 1/3] Diff now returns optionally the diff value and AssertSnapshot writes it to disk --- Sources/SnapshotTesting/AssertSnapshot.swift | 32 +++++++++++++++++- Sources/SnapshotTesting/Diffing.swift | 5 +-- .../Internal/Deprecations.swift | 2 +- .../SnapshotTesting/Snapshotting/Data.swift | 2 +- .../SnapshotTesting/Snapshotting/String.swift | 33 ++++++++++--------- .../Snapshotting/UIImage.swift | 3 +- 6 files changed, 55 insertions(+), 22 deletions(-) diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index 24868e082..e91e7c535 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -403,7 +403,7 @@ public func verifySnapshot( } #endif - guard let (failure, attachments) = snapshotting.diffing.diff(reference, diffable) else { + guard let (failure, attachments, diffValue) = snapshotting.diffing.diff(reference, diffable) else { return nil } @@ -413,10 +413,23 @@ public func verifySnapshot( ) let artifactsSubUrl = artifactsUrl.appendingPathComponent(fileName) try fileManager.createDirectory(at: artifactsSubUrl, withIntermediateDirectories: true) + + func artifactFileURL( + for artifactType: ArtifactType, + artifactsDirectory: URL, + pathExtension: String + ) -> URL { + let baseFileName = "\(testName).\(identifier)" + let artifactFileName = "\(baseFileName)\(artifactType.suffix)" + let result = URL(fileURLWithPath: artifactFileName, isDirectory: false) + return result.appendingPathExtension(snapshotting.pathExtension ?? "") + } + let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent( snapshotFileUrl.lastPathComponent) try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl) + if !attachments.isEmpty { #if !os(Linux) && !os(Windows) if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { @@ -498,3 +511,20 @@ private class CleanCounterBetweenTestCases: NSObject, XCTestObservation { } } } + +private enum ArtifactType { + case failure + case reference + case diff + + var suffix: String { + switch self { + case .failure: + return "_failure" + case .reference: + return "_reference" + case .diff: + return "_diff" + } + } +} diff --git a/Sources/SnapshotTesting/Diffing.swift b/Sources/SnapshotTesting/Diffing.swift index c189578ec..0e8ab93f1 100644 --- a/Sources/SnapshotTesting/Diffing.swift +++ b/Sources/SnapshotTesting/Diffing.swift @@ -11,7 +11,8 @@ public struct Diffing { /// Compares two values. If the values do not match, returns a failure message and artifacts /// describing the failure. - public var diff: (Value, Value) -> (String, [XCTAttachment])? + public typealias DiffingResult = (String, [XCTAttachment], Value?) + public var diff: (Value, Value) -> DiffingResult? /// Creates a new `Diffing` on `Value`. /// @@ -22,7 +23,7 @@ public struct Diffing { public init( toData: @escaping (_ value: Value) -> Data, fromData: @escaping (_ data: Data) -> Value, - diff: @escaping (_ lhs: Value, _ rhs: Value) -> (String, [XCTAttachment])? + diff: @escaping (_ lhs: Value, _ rhs: Value) -> DiffingResult? ) { self.toData = toData self.fromData = fromData diff --git a/Sources/SnapshotTesting/Internal/Deprecations.swift b/Sources/SnapshotTesting/Internal/Deprecations.swift index ce93f2f7f..1c283b8a8 100644 --- a/Sources/SnapshotTesting/Internal/Deprecations.swift +++ b/Sources/SnapshotTesting/Internal/Deprecations.swift @@ -94,7 +94,7 @@ public func _verifyInlineSnapshot( let trimmedReference = reference.trimmingCharacters(in: .whitespacesAndNewlines) // Always perform diff, and return early on success! - guard let (failure, attachments) = snapshotting.diffing.diff(trimmedReference, diffable) else { + guard let (failure, attachments, diffValue) = snapshotting.diffing.diff(trimmedReference, diffable) else { return nil } diff --git a/Sources/SnapshotTesting/Snapshotting/Data.swift b/Sources/SnapshotTesting/Snapshotting/Data.swift index 9d2eb467a..7d651fff6 100644 --- a/Sources/SnapshotTesting/Snapshotting/Data.swift +++ b/Sources/SnapshotTesting/Snapshotting/Data.swift @@ -12,7 +12,7 @@ extension Snapshotting where Value == Data, Format == Data { old.count == new.count ? "Expected data to match" : "Expected \(new) to match \(old)" - return (message, []) + return (message, [], nil) } ) } diff --git a/Sources/SnapshotTesting/Snapshotting/String.swift b/Sources/SnapshotTesting/Snapshotting/String.swift index 44aeab0b4..0626fc074 100644 --- a/Sources/SnapshotTesting/Snapshotting/String.swift +++ b/Sources/SnapshotTesting/Snapshotting/String.swift @@ -10,20 +10,21 @@ extension Diffing where Value == String { /// A line-diffing strategy for UTF-8 text. public static let lines = Diffing( toData: { Data($0.utf8) }, - fromData: { String(decoding: $0, as: UTF8.self) } - ) { old, new in - guard old != new else { return nil } - let hunks = chunk( - diff: SnapshotTesting.diff( - old.split(separator: "\n", omittingEmptySubsequences: false).map(String.init), - new.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) - )) - let failure = - hunks - .flatMap { [$0.patchMark] + $0.lines } - .joined(separator: "\n") - let attachment = XCTAttachment( - data: Data(failure.utf8), uniformTypeIdentifier: "public.patch-file") - return (failure, [attachment]) - } + fromData: { String(decoding: $0, as: UTF8.self) }, + diff: { old, new in + guard old != new else { return nil } + let hunks = chunk( + diff: SnapshotTesting.diff( + old.split(separator: "\n", omittingEmptySubsequences: false).map(String.init), + new.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) + )) + let failure = + hunks + .flatMap { [$0.patchMark] + $0.lines } + .joined(separator: "\n") + let attachment = XCTAttachment( + data: Data(failure.utf8), uniformTypeIdentifier: "public.patch-file") + return (failure, [attachment], nil) + } + ) } diff --git a/Sources/SnapshotTesting/Snapshotting/UIImage.swift b/Sources/SnapshotTesting/Snapshotting/UIImage.swift index 3d1bb5319..cec970378 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIImage.swift @@ -45,7 +45,8 @@ differenceAttachment.name = "difference" return ( message, - [oldAttachment, newAttachment, differenceAttachment] + [oldAttachment, newAttachment, differenceAttachment], + nil ) } } From 4d9b669352103e4111a7a628f1a12031010dc844 Mon Sep 17 00:00:00 2001 From: Leandro Demarco Vedelago Date: Fri, 6 Sep 2024 17:52:34 -0300 Subject: [PATCH 2/3] Write failed, reference and diff values to disk --- Sources/SnapshotTesting/AssertSnapshot.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index e91e7c535..ae298556d 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -414,20 +414,22 @@ public func verifySnapshot( let artifactsSubUrl = artifactsUrl.appendingPathComponent(fileName) try fileManager.createDirectory(at: artifactsSubUrl, withIntermediateDirectories: true) - func artifactFileURL( - for artifactType: ArtifactType, - artifactsDirectory: URL, - pathExtension: String - ) -> URL { + func artifactFileURL(for artifactType: ArtifactType) -> String { let baseFileName = "\(testName).\(identifier)" let artifactFileName = "\(baseFileName)\(artifactType.suffix)" let result = URL(fileURLWithPath: artifactFileName, isDirectory: false) - return result.appendingPathExtension(snapshotting.pathExtension ?? "") + return result.appendingPathExtension(snapshotting.pathExtension ?? "").path } - let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent( - snapshotFileUrl.lastPathComponent) + let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent(artifactFileURL(for: .failure)) try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl) + let referenceSnapshotFileURL = artifactsSubUrl.appendingPathComponent(artifactFileURL(for: .reference)) + try snapshotting.diffing.toData(reference).write(to: referenceSnapshotFileURL) + + if let diffValue { + let diffFileURL = artifactsSubUrl.appendingPathComponent(artifactFileURL(for: .diff)) + try snapshotting.diffing.toData(diffValue).write(to: diffFileURL) + } if !attachments.isEmpty { From 2ceb9c10d750dcedbc2e286191debb978d64faba Mon Sep 17 00:00:00 2001 From: Leandro Demarco Vedelago Date: Fri, 6 Sep 2024 18:02:58 -0300 Subject: [PATCH 3/3] Return diff value for NSImage and UIImage --- Sources/SnapshotTesting/Snapshotting/NSImage.swift | 3 ++- Sources/SnapshotTesting/Snapshotting/UIImage.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/SnapshotTesting/Snapshotting/NSImage.swift b/Sources/SnapshotTesting/Snapshotting/NSImage.swift index be4fd7cd4..a09e68340 100644 --- a/Sources/SnapshotTesting/Snapshotting/NSImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/NSImage.swift @@ -33,7 +33,8 @@ differenceAttachment.name = "difference" return ( message, - [oldAttachment, newAttachment, differenceAttachment] + [oldAttachment, newAttachment, differenceAttachment], + difference ) } } diff --git a/Sources/SnapshotTesting/Snapshotting/UIImage.swift b/Sources/SnapshotTesting/Snapshotting/UIImage.swift index cec970378..4bf82f3a2 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIImage.swift @@ -46,7 +46,7 @@ return ( message, [oldAttachment, newAttachment, differenceAttachment], - nil + difference ) } }