diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f67de6e0a..7cdf40e1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,3 +61,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/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 { diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index 63be5bf3e..0ea1e5709 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? { @@ -516,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/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/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/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 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"