From a5dbd5df3d327a99209177043c627048ae08c765 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Jan 2025 10:20:42 -0800 Subject: [PATCH 1/4] Update `assertInlineSnapshot` to support record mode (#947) It still took a simple boolean, but should support things like `.failed`. --- Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift b/Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift index 1ca6ddf8e..142cec513 100644 --- a/Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift +++ b/Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift @@ -37,7 +37,7 @@ import Foundation of value: @autoclosure () throws -> Value?, as snapshotting: Snapshotting, message: @autoclosure () -> String = "", - record isRecording: Bool? = nil, + record: SnapshotTestingConfiguration.Record? = nil, timeout: TimeInterval = 5, syntaxDescriptor: InlineSnapshotSyntaxDescriptor = InlineSnapshotSyntaxDescriptor(), matches expected: (() -> String)? = nil, @@ -47,10 +47,7 @@ import Foundation line: UInt = #line, column: UInt = #column ) { - let record = - (isRecording == true ? .all : isRecording == false ? .missing : nil) - ?? SnapshotTestingConfiguration.current?.record - ?? _record + let record = record ?? SnapshotTestingConfiguration.current?.record ?? _record withSnapshotTesting(record: record) { let _: Void = installTestObserver do { From de5f975ac7927ff6542a87ee982ba3ad849d2837 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Fri, 24 Jan 2025 14:04:57 -0500 Subject: [PATCH 2/4] Android support (#939) * Android support * Android support * CI updates * Run swift-format * CI updates * CI updates * Run swift-format * CI updates * CI updates * CI updates * Run swift-format * CI updates * CI updates * Run swift-format * CI fixes * Run swift-format * CI fixes * Run swift-format * CI fixes * Update AssertSnapshot.swift --------- Co-authored-by: marcprux Co-authored-by: Stephen Celis Co-authored-by: Stephen Celis --- .github/workflows/ci.yml | 14 +++++++++++ Sources/SnapshotTesting/AssertSnapshot.swift | 25 ++++++++++++------- .../Common/XCTAttachment.swift | 2 +- .../Internal/Deprecations.swift | 2 +- Tests/SnapshotTestingTests/RecordTests.swift | 3 +++ 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6a6d40ab..0936938bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,3 +63,17 @@ jobs: - uses: actions/checkout@v4 - run: swift build - run: swift test + + android: + strategy: + matrix: + swift: + - "6.0.2" + name: Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: skiptools/swift-android-action@v2 + with: + swift-version: ${{ matrix.swift }} + copy-files: ${GITHUB_WORKSPACE}/Tests/SnapshotTestingTests/__Snapshots__ diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index 63be5bf3e..92be23083 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -296,12 +296,17 @@ public func verifySnapshot( let fileUrl = URL(fileURLWithPath: "\(filePath)", isDirectory: false) let fileName = fileUrl.deletingPathExtension().lastPathComponent + #if os(Android) + // When running tests on Android, the CI script copies the Tests/SnapshotTestingTests/__Snapshots__ up to the temporary folder + let snapshotsBaseUrl = URL( + fileURLWithPath: "/data/local/tmp/android-xctest", isDirectory: true) + #else + let snapshotsBaseUrl = fileUrl.deletingLastPathComponent() + #endif + let snapshotDirectoryUrl = snapshotDirectory.map { URL(fileURLWithPath: $0, isDirectory: true) } - ?? fileUrl - .deletingLastPathComponent() - .appendingPathComponent("__Snapshots__") - .appendingPathComponent(fileName) + ?? snapshotsBaseUrl.appendingPathComponent("__Snapshots__").appendingPathComponent(fileName) let identifier: String if let name = name { @@ -316,10 +321,12 @@ public func verifySnapshot( } let testName = sanitizePathComponent(testName) - let snapshotFileUrl = + var snapshotFileUrl = snapshotDirectoryUrl .appendingPathComponent("\(testName).\(identifier)") - .appendingPathExtension(snapshotting.pathExtension ?? "") + if let ext = snapshotting.pathExtension { + snapshotFileUrl = snapshotFileUrl.appendingPathExtension(ext) + } let fileManager = FileManager.default try fileManager.createDirectory(at: snapshotDirectoryUrl, withIntermediateDirectories: true) @@ -359,7 +366,7 @@ public func verifySnapshot( try snapshotData.write(to: snapshotFileUrl) } - #if !os(Linux) && !os(Windows) + #if !os(Android) && !os(Linux) && !os(Windows) if !isSwiftTesting, ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { @@ -446,7 +453,7 @@ public func verifySnapshot( try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl) if !attachments.isEmpty { - #if !os(Linux) && !os(Windows) + #if !os(Linux) && !os(Android) && !os(Windows) if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS"), !isSwiftTesting { @@ -501,7 +508,7 @@ func sanitizePathComponent(_ string: String) -> String { .replacingOccurrences(of: "^-|-$", with: "", options: .regularExpression) } -#if !os(Linux) && !os(Windows) +#if !os(Android) && !os(Linux) && !os(Windows) import CoreServices func uniformTypeIdentifier(fromExtension pathExtension: String) -> String? { diff --git a/Sources/SnapshotTesting/Common/XCTAttachment.swift b/Sources/SnapshotTesting/Common/XCTAttachment.swift index b74c5688d..117dd26c0 100644 --- a/Sources/SnapshotTesting/Common/XCTAttachment.swift +++ b/Sources/SnapshotTesting/Common/XCTAttachment.swift @@ -1,4 +1,4 @@ -#if os(Linux) || os(Windows) +#if os(Linux) || os(Android) || os(Windows) import Foundation public struct XCTAttachment { diff --git a/Sources/SnapshotTesting/Internal/Deprecations.swift b/Sources/SnapshotTesting/Internal/Deprecations.swift index ce93f2f7f..9a5b9f217 100644 --- a/Sources/SnapshotTesting/Internal/Deprecations.swift +++ b/Sources/SnapshotTesting/Internal/Deprecations.swift @@ -136,7 +136,7 @@ public func _verifyInlineSnapshot( /// Did not successfully record, so we will fail. if !attachments.isEmpty { - #if !os(Linux) && !os(Windows) + #if !os(Linux) && !os(Android) && !os(Windows) if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { XCTContext.runActivity(named: "Attached Failure Diff") { activity in attachments.forEach { diff --git a/Tests/SnapshotTestingTests/RecordTests.swift b/Tests/SnapshotTestingTests/RecordTests.swift index 26e9c6644..e3939b0ae 100644 --- a/Tests/SnapshotTestingTests/RecordTests.swift +++ b/Tests/SnapshotTestingTests/RecordTests.swift @@ -162,6 +162,9 @@ class RecordTests: XCTestCase { #endif func testRecordFailed_NoFailure() throws { + #if os(Android) + throw XCTSkip("cannot save next to file on Android") + #endif try Data("42".utf8).write(to: snapshotURL) let modifiedDate = try FileManager.default From eb31d41a6cf5bde387c020bfc00f646532bad093 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Fri, 24 Jan 2025 14:06:50 -0500 Subject: [PATCH 3/4] Use alternate means to detect if running on main thread to avoid problems with MainActor on Linux (#946) --- Sources/SnapshotTesting/AssertSnapshot.swift | 30 +++++++++++-------- .../AssertSnapshotSwiftTests.swift | 14 +++++++++ .../AssertSnapshotSwiftTests/dump.2.txt | 4 +++ 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.2.txt diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index 92be23083..a0365b1e4 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -523,27 +523,33 @@ func sanitizePathComponent(_ string: String) -> String { } #endif +extension DispatchQueue { + private static let key = DispatchSpecificKey() + private static let value: UInt8 = 0 + + fileprivate static func mainSync(execute block: () -> R) -> R { + Self.main.setSpecific(key: key, value: value) + if getSpecific(key: key) == value { + return block() + } else { + return main.sync(execute: block) + } + } +} + // We need to clean counter between tests executions in order to support test-iterations. private class CleanCounterBetweenTestCases: NSObject, XCTestObservation { private static var registered = false static func registerIfNeeded() { - if Thread.isMainThread { - doRegisterIfNeeded() - } else { - DispatchQueue.main.sync { - doRegisterIfNeeded() + DispatchQueue.mainSync { + if !registered { + registered = true + XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases()) } } } - private static func doRegisterIfNeeded() { - if !registered { - registered = true - XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases()) - } - } - func testCaseDidFinish(_ testCase: XCTestCase) { counterQueue.sync { counterMap = [:] diff --git a/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift b/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift index aee524aff..7e609bce5 100644 --- a/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift +++ b/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift @@ -15,4 +15,18 @@ assertSnapshot(of: user, as: .dump) } } + + @MainActor + @Suite( + .snapshots( + record: .missing + ) + ) + struct MainActorTests { + @Test func dump() { + struct User { let id: Int, name: String, bio: String } + let user = User(id: 1, name: "Blobby", bio: "Blobbed around the world.") + assertSnapshot(of: user, as: .dump) + } + } #endif diff --git a/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.2.txt b/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.2.txt new file mode 100644 index 000000000..0e3f0406c --- /dev/null +++ b/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.2.txt @@ -0,0 +1,4 @@ +▿ User + - bio: "Blobbed around the world." + - id: 1 + - name: "Blobby" From 008509a57355eac9a7e71e670b97cbff16fa4cbf Mon Sep 17 00:00:00 2001 From: stephencelis Date: Fri, 24 Jan 2025 19:07:25 +0000 Subject: [PATCH 4/4] Run swift-format --- Sources/SnapshotTesting/AssertSnapshot.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index a0365b1e4..0ea1e5709 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -544,8 +544,8 @@ private class CleanCounterBetweenTestCases: NSObject, XCTestObservation { static func registerIfNeeded() { DispatchQueue.mainSync { if !registered { - registered = true - XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases()) + registered = true + XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases()) } } }