From 2b1ce849619079de72921dded994f06c750e9954 Mon Sep 17 00:00:00 2001 From: Marc Schultz Date: Sun, 23 Jun 2024 16:42:39 +0200 Subject: [PATCH] Fix image loading crashes --- Sources/SnapshotTesting/AssertSnapshot.swift | 2 +- Sources/SnapshotTesting/Diffing.swift | 4 ++-- .../Extensions/Data+XImage.swift | 21 +++++++++++++++++++ .../Snapshotting/ImageConversionError.swift | 3 +++ .../Snapshotting/NSImage.swift | 2 +- .../Snapshotting/UIImage.swift | 2 +- .../SnapshotTestingTests.swift | 7 +++++++ ...pshot_whenReferenceImageLoadingFails.1.png | 1 + 8 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 Sources/SnapshotTesting/Extensions/Data+XImage.swift create mode 100644 Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageSnapshot_whenReferenceImageLoadingFails.1.png diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index 53f80cb45..bf68fca29 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -276,7 +276,7 @@ public func verifySnapshot( } let data = try Data(contentsOf: snapshotFileUrl) - let reference = snapshotting.diffing.fromData(data) + let reference = try snapshotting.diffing.fromData(data) guard let (failureMessage, attachments) = snapshotting.diffing.diff(reference, diffable) else { return nil diff --git a/Sources/SnapshotTesting/Diffing.swift b/Sources/SnapshotTesting/Diffing.swift index f6cfd87d8..ada5280e1 100644 --- a/Sources/SnapshotTesting/Diffing.swift +++ b/Sources/SnapshotTesting/Diffing.swift @@ -7,7 +7,7 @@ public struct Diffing { public var toData: (Value) throws -> Data /// Produces a value _from_ data. - public var fromData: (Data) -> Value + public var fromData: (Data) throws -> Value /// Compares two values. If the values do not match, returns a failure message and artifacts /// describing the failure. @@ -26,7 +26,7 @@ public struct Diffing { /// - rhs: Another value to compare. public init( toData: @escaping (_ value: Value) throws -> Data, - fromData: @escaping (_ data: Data) -> Value, + fromData: @escaping (_ data: Data) throws -> Value, diff: @escaping (_ lhs: Value, _ rhs: Value) -> (String, [XCTAttachment])? ) { self.toData = toData diff --git a/Sources/SnapshotTesting/Extensions/Data+XImage.swift b/Sources/SnapshotTesting/Extensions/Data+XImage.swift new file mode 100644 index 000000000..7e9064cac --- /dev/null +++ b/Sources/SnapshotTesting/Extensions/Data+XImage.swift @@ -0,0 +1,21 @@ +import Foundation + +extension Data { +#if os(iOS) || os(tvOS) + func image(scale: CGFloat) throws -> XImage { + let image = XImage(data: self, scale: scale) + guard let image else { + throw ImageConversionError.invalidImageData + } + return image + } +#elseif os(macOS) + func image() throws -> XImage { + let image = XImage(data: self) + guard let image else { + throw ImageConversionError.invalidImageData + } + return image + } +#endif +} diff --git a/Sources/SnapshotTesting/Snapshotting/ImageConversionError.swift b/Sources/SnapshotTesting/Snapshotting/ImageConversionError.swift index 4336eed42..beb4cfe24 100644 --- a/Sources/SnapshotTesting/Snapshotting/ImageConversionError.swift +++ b/Sources/SnapshotTesting/Snapshotting/ImageConversionError.swift @@ -3,6 +3,7 @@ import Foundation enum ImageConversionError: Error { case cgImageConversionFailed + case invalidImageData case pngDataConversionFailed case zeroHeight case zeroSize @@ -14,6 +15,8 @@ extension ImageConversionError: LocalizedError { switch self { case .cgImageConversionFailed, .pngDataConversionFailed: return "Snapshot could not be processed" + case .invalidImageData: + return "Invalid image data" case .zeroHeight: return "Snapshot has a height of zero" case .zeroSize: diff --git a/Sources/SnapshotTesting/Snapshotting/NSImage.swift b/Sources/SnapshotTesting/Snapshotting/NSImage.swift index 43640bfbb..82f5dbd9e 100644 --- a/Sources/SnapshotTesting/Snapshotting/NSImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/NSImage.swift @@ -18,7 +18,7 @@ public static func image(precision: Float = 1, perceptualPrecision: Float = 1) -> Diffing { return .init( toData: convertToData, - fromData: { NSImage(data: $0)! } + fromData: { try $0.image() } ) { old, new in do { let result = try compare(old, new, precision: precision, perceptualPrecision: perceptualPrecision) diff --git a/Sources/SnapshotTesting/Snapshotting/UIImage.swift b/Sources/SnapshotTesting/Snapshotting/UIImage.swift index c00ba2121..24697a86b 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIImage.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIImage.swift @@ -22,7 +22,7 @@ ) -> Diffing { Diffing( toData: convertToData, - fromData: { UIImage(data: $0, scale: scale)! } + fromData: { try $0.image(scale: scale) } ) { old, new in do { let result = try compare(old, new, precision: precision, perceptualPrecision: perceptualPrecision) diff --git a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift index 99d1e0977..3fd015ac9 100644 --- a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift +++ b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift @@ -1137,6 +1137,13 @@ final class SnapshotTestingTests: XCTestCase { XCTAssertEqual(firstLine, "[macos] Snapshot size (123.0, 123.0) is unequal to expected size (100.0, 100.0)") #endif } + + func testImageSnapshot_whenReferenceImageLoadingFails() { + let size = CGSize(width: 100, height: 100) + let view = XView(frame: .init(origin: .zero, size: size)) + let message = verifySnapshot(of: view, as: .image) + XCTAssertEqual(message, "Invalid image data") + } #endif #if canImport(SwiftUI) diff --git a/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageSnapshot_whenReferenceImageLoadingFails.1.png b/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageSnapshot_whenReferenceImageLoadingFails.1.png new file mode 100644 index 000000000..0c47971df --- /dev/null +++ b/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testImageSnapshot_whenReferenceImageLoadingFails.1.png @@ -0,0 +1 @@ +THIS IS NOT AN IMAGE