Skip to content

Commit 345b06a

Browse files
authored
Bring Linux SnapshotTestCase up to date (#156)
1 parent 9b2c74a commit 345b06a

File tree

2 files changed

+196
-95
lines changed

2 files changed

+196
-95
lines changed

Sources/SnapshotTesting/AssertSnapshot.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public func assertSnapshot<Value, Format>(
4545
XCTFail(message, file: file, line: line)
4646
}
4747

48-
/// Asserts that a given value matches a reference on disk.
48+
/// Asserts that a given value matches references on disk.
4949
///
5050
/// - Parameters:
5151
/// - value: A value to compare against a reference.
@@ -79,7 +79,7 @@ public func assertSnapshots<Value, Format>(
7979
}
8080
}
8181

82-
/// Asserts that a given value matches a reference on disk.
82+
/// Asserts that a given value matches references on disk.
8383
///
8484
/// - Parameters:
8585
/// - value: A value to compare against a reference.

Sources/SnapshotTesting/SnapshotTestCase.swift

Lines changed: 194 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ open class SnapshotTestCase: XCTestCase {
2323
/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
2424
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
2525
public func assertSnapshot<Value, Format>(
26-
matching value: Value,
26+
matching value: @autoclosure () throws -> Value,
2727
as snapshotting: Snapshotting<Value, Format>,
2828
named name: String? = nil,
2929
record recording: Bool = false,
@@ -33,114 +33,215 @@ open class SnapshotTestCase: XCTestCase {
3333
line: UInt = #line
3434
) {
3535

36-
let recording = recording || self.record
37-
38-
do {
39-
let fileUrl = URL(fileURLWithPath: "\(file)")
40-
let fileName = fileUrl.deletingPathExtension().lastPathComponent
41-
let directoryUrl = fileUrl.deletingLastPathComponent()
42-
let snapshotDirectoryUrl: URL = directoryUrl
43-
.appendingPathComponent("__Snapshots__")
44-
.appendingPathComponent(fileName)
45-
46-
let identifier: String
47-
if let name = name {
48-
identifier = sanitizePathComponent(name)
49-
} else {
50-
identifier = String(counter)
51-
counter += 1
52-
}
36+
let failure = verifySnapshot(
37+
matching: value,
38+
as: snapshotting,
39+
named: name,
40+
record: recording,
41+
timeout: timeout,
42+
file: file,
43+
testName: testName,
44+
line: line
45+
)
46+
guard let message = failure else { return }
47+
XCTFail(message, file: file, line: line)
48+
}
5349

54-
let testName = sanitizePathComponent(testName)
55-
let snapshotFileUrl = snapshotDirectoryUrl
56-
.appendingPathComponent("\(testName).\(identifier)")
57-
.appendingPathExtension(snapshotting.pathExtension ?? "")
58-
let fileManager = FileManager.default
59-
try fileManager.createDirectory(at: snapshotDirectoryUrl, withIntermediateDirectories: true)
60-
61-
let tookSnapshot = self.expectation(description: "Took snapshot")
62-
var optionalDiffable: Format?
63-
snapshotting.snapshot(value).run { b in
64-
optionalDiffable = b
65-
tookSnapshot.fulfill()
66-
}
67-
#if os(Linux)
68-
self.waitForExpectations(timeout: timeout)
69-
#else
70-
self.wait(for: [tookSnapshot], timeout: timeout)
71-
#endif
72-
73-
guard let diffable = optionalDiffable else {
74-
XCTFail("Couldn't snapshot value", file: file, line: line)
75-
return
76-
}
50+
/// Asserts that a given value matches references on disk.
51+
///
52+
/// - Parameters:
53+
/// - value: A value to compare against a reference.
54+
/// - snapshotting: An dictionnay of names and strategies for serializing, deserializing, and comparing values.
55+
/// - recording: Whether or not to record a new reference.
56+
/// - timeout: The amount of time a snapshot must be generated in.
57+
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
58+
/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
59+
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
60+
public func assertSnapshots<Value, Format>(
61+
matching value: @autoclosure () throws -> Value,
62+
as strategies: [String: Snapshotting<Value, Format>],
63+
record recording: Bool = false,
64+
timeout: TimeInterval = 5,
65+
file: StaticString = #file,
66+
testName: String = #function,
67+
line: UInt = #line
68+
) {
7769

78-
guard !recording, fileManager.fileExists(atPath: snapshotFileUrl.path) else {
79-
let diffMessage = (try? Data(contentsOf: snapshotFileUrl))
80-
.flatMap { data in snapshotting.diffing.diff(snapshotting.diffing.fromData(data), diffable) }
81-
.map { diff, _ in diff.trimmingCharacters(in: .whitespacesAndNewlines) }
82-
?? "Recorded snapshot: …"
70+
strategies.forEach { name, strategy in
71+
assertSnapshot(
72+
matching: value,
73+
as: strategy,
74+
named: name,
75+
record: recording,
76+
timeout: timeout,
77+
file: file,
78+
testName: testName,
79+
line: line
80+
)
81+
}
82+
}
8383

84-
try snapshotting.diffing.toData(diffable).write(to: snapshotFileUrl)
85-
let message = recording
86-
? """
87-
Record mode is on. Turn record mode off and re-run "\(testName)" to test against the newly-recorded snapshot.
84+
/// Asserts that a given value matches references on disk.
85+
///
86+
/// - Parameters:
87+
/// - value: A value to compare against a reference.
88+
/// - snapshotting: An array of strategies for serializing, deserializing, and comparing values.
89+
/// - recording: Whether or not to record a new reference.
90+
/// - timeout: The amount of time a snapshot must be generated in.
91+
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
92+
/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
93+
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
94+
public func assertSnapshots<Value, Format>(
95+
matching value: @autoclosure () throws -> Value,
96+
as strategies: [Snapshotting<Value, Format>],
97+
record recording: Bool = false,
98+
timeout: TimeInterval = 5,
99+
file: StaticString = #file,
100+
testName: String = #function,
101+
line: UInt = #line
102+
) {
88103

89-
open "\(snapshotFileUrl.path)"
104+
strategies.forEach { strategy in
105+
assertSnapshot(
106+
matching: value,
107+
as: strategy,
108+
record: recording,
109+
timeout: timeout,
110+
file: file,
111+
testName: testName,
112+
line: line
113+
)
114+
}
115+
}
116+
117+
/// Verifies that a given value matches a reference on disk.
118+
///
119+
/// - Parameters:
120+
/// - value: A value to compare against a reference.
121+
/// - snapshotting: A strategy for serializing, deserializing, and comparing values.
122+
/// - name: An optional description of the snapshot.
123+
/// - recording: Whether or not to record a new reference.
124+
/// - timeout: The amount of time a snapshot must be generated in.
125+
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
126+
/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
127+
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
128+
/// - Returns: A failure message or, if the value matches, nil.
129+
public func verifySnapshot<Value, Format>(
130+
matching value: @autoclosure () throws -> Value,
131+
as snapshotting: Snapshotting<Value, Format>,
132+
named name: String? = nil,
133+
record recording: Bool = false,
134+
timeout: TimeInterval = 5,
135+
file: StaticString = #file,
136+
testName: String = #function,
137+
line: UInt = #line
138+
)
139+
-> String? {
140+
141+
let recording = recording || self.record
142+
143+
do {
144+
let fileUrl = URL(fileURLWithPath: "\(file)")
145+
let fileName = fileUrl.deletingPathExtension().lastPathComponent
146+
let directoryUrl = fileUrl.deletingLastPathComponent()
147+
let snapshotDirectoryUrl: URL = directoryUrl
148+
.appendingPathComponent("__Snapshots__")
149+
.appendingPathComponent(fileName)
150+
151+
let identifier: String
152+
if let name = name {
153+
identifier = sanitizePathComponent(name)
154+
} else {
155+
identifier = String(counter)
156+
counter += 1
157+
}
90158

91-
\(diffMessage)
92-
"""
93-
: """
94-
No reference was found on disk. Automatically recorded snapshot: …
159+
let testName = sanitizePathComponent(testName)
160+
let snapshotFileUrl = snapshotDirectoryUrl
161+
.appendingPathComponent("\(testName).\(identifier)")
162+
.appendingPathExtension(snapshotting.pathExtension ?? "")
163+
let fileManager = FileManager.default
164+
try fileManager.createDirectory(at: snapshotDirectoryUrl, withIntermediateDirectories: true)
165+
166+
let tookSnapshot = self.expectation(description: "Took snapshot")
167+
var optionalDiffable: Format?
168+
snapshotting.snapshot(try value()).run { b in
169+
optionalDiffable = b
170+
tookSnapshot.fulfill()
171+
}
172+
#if os(Linux)
173+
self.waitForExpectations(timeout: timeout)
174+
#else
175+
self.wait(for: [tookSnapshot], timeout: timeout)
176+
#endif
95177

96-
open "\(snapshotFileUrl.path)"
178+
guard let diffable = optionalDiffable else {
179+
return "Couldn't snapshot value"
180+
}
97181

98-
Re-run "\(testName)" to test against the newly-recorded snapshot.
99-
"""
182+
guard !recording, fileManager.fileExists(atPath: snapshotFileUrl.path) else {
183+
let diffMessage = (try? Data(contentsOf: snapshotFileUrl))
184+
.flatMap { data in snapshotting.diffing.diff(snapshotting.diffing.fromData(data), diffable) }
185+
.map { diff, _ in diff.trimmingCharacters(in: .whitespacesAndNewlines) }
186+
?? "Recorded snapshot: …"
100187

101-
XCTFail(message, file: file, line: line)
102-
return
103-
}
188+
try snapshotting.diffing.toData(diffable).write(to: snapshotFileUrl)
189+
return recording
190+
? """
191+
Record mode is on. Turn record mode off and re-run "\(testName)" to test against the newly-recorded snapshot.
104192
105-
let data = try Data(contentsOf: snapshotFileUrl)
106-
let reference = snapshotting.diffing.fromData(data)
193+
open "\(snapshotFileUrl.path)"
107194
108-
guard let (failure, attachments) = snapshotting.diffing.diff(reference, diffable) else {
109-
return
110-
}
195+
\(diffMessage)
196+
"""
197+
: """
198+
No reference was found on disk. Automatically recorded snapshot: …
111199
112-
let artifactsUrl = URL(
113-
fileURLWithPath: ProcessInfo.processInfo.environment["SNAPSHOT_ARTIFACTS"] ?? NSTemporaryDirectory()
114-
)
115-
let artifactsSubUrl = artifactsUrl.appendingPathComponent(fileName)
116-
try fileManager.createDirectory(at: artifactsSubUrl, withIntermediateDirectories: true)
117-
let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent(snapshotFileUrl.lastPathComponent)
118-
try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl)
119-
120-
if !attachments.isEmpty {
121-
#if !os(Linux)
122-
XCTContext.runActivity(named: "Attached Failure Diff") { activity in
123-
attachments.forEach {
124-
activity.add($0)
200+
open "\(snapshotFileUrl.path)"
201+
202+
Re-run "\(testName)" to test against the newly-recorded snapshot.
203+
"""
204+
}
205+
206+
let data = try Data(contentsOf: snapshotFileUrl)
207+
let reference = snapshotting.diffing.fromData(data)
208+
209+
guard let (failure, attachments) = snapshotting.diffing.diff(reference, diffable) else {
210+
return nil
211+
}
212+
213+
let artifactsUrl = URL(
214+
fileURLWithPath: ProcessInfo.processInfo.environment["SNAPSHOT_ARTIFACTS"] ?? NSTemporaryDirectory()
215+
)
216+
let artifactsSubUrl = artifactsUrl.appendingPathComponent(fileName)
217+
try fileManager.createDirectory(at: artifactsSubUrl, withIntermediateDirectories: true)
218+
let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent(snapshotFileUrl.lastPathComponent)
219+
try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl)
220+
221+
if !attachments.isEmpty {
222+
#if !os(Linux)
223+
XCTContext.runActivity(named: "Attached Failure Diff") { activity in
224+
attachments.forEach {
225+
activity.add($0)
226+
}
125227
}
228+
#endif
126229
}
127-
#endif
128-
}
129230

130-
let diffMessage = self.diffTool
131-
.map { "\($0) \"\(snapshotFileUrl.path)\" \"\(failedSnapshotFileUrl.path)\"" }
132-
?? "@\(minus)\n\"\(snapshotFileUrl.path)\"\n@\(plus)\n\"\(failedSnapshotFileUrl.path)\""
133-
let message = """
134-
Snapshot does not match reference.
231+
let diffMessage = self.diffTool
232+
.map { "\($0) \"\(snapshotFileUrl.path)\" \"\(failedSnapshotFileUrl.path)\"" }
233+
?? "@\(minus)\n\"\(snapshotFileUrl.path)\"\n@\(plus)\n\"\(failedSnapshotFileUrl.path)\""
234+
let message = """
235+
Snapshot does not match reference.
135236
136-
\(diffMessage)
237+
\(diffMessage)
137238
138-
\(failure.trimmingCharacters(in: .whitespacesAndNewlines))
139-
"""
140-
XCTFail(message, file: file, line: line)
141-
} catch {
142-
XCTFail(error.localizedDescription, file: file, line: line)
143-
}
239+
\(failure.trimmingCharacters(in: .whitespacesAndNewlines))
240+
"""
241+
return message
242+
} catch {
243+
return error.localizedDescription
244+
}
144245
}
145246

146247
private var counter = 1

0 commit comments

Comments
 (0)