Skip to content

Commit bf8d8c2

Browse files
authored
Leverage atexit instead of test observers (#1050)
* Leverage `atexit` instead of test observers * wip
1 parent cc051f1 commit bf8d8c2

File tree

2 files changed

+81
-69
lines changed

2 files changed

+81
-69
lines changed

Sources/InlineSnapshotTesting/AssertInlineSnapshot.swift

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import Foundation
22

3-
#if canImport(Testing)
4-
import Testing
5-
#endif
6-
73
#if canImport(SwiftSyntax509)
84
@_spi(Internals) import SnapshotTesting
95
import SwiftParser
@@ -103,17 +99,19 @@ import Foundation
10399
let expected = expected?()
104100
func recordSnapshot() {
105101
// NB: Write snapshot state before calling `XCTFail` in case `continueAfterFailure = false`
106-
inlineSnapshotState[File(path: filePath), default: []].append(
107-
InlineSnapshot(
108-
expected: expected,
109-
actual: actual,
110-
wasRecording: record == .all || record == .failed,
111-
syntaxDescriptor: syntaxDescriptor,
112-
function: "\(function)",
113-
line: line,
114-
column: column
102+
inlineSnapshotState.withLock { [actual] in
103+
$0[File(path: filePath), default: []].append(
104+
InlineSnapshot(
105+
expected: expected,
106+
actual: actual,
107+
wasRecording: record == .all || record == .failed,
108+
syntaxDescriptor: syntaxDescriptor,
109+
function: "\(function)",
110+
line: line,
111+
column: column
112+
)
115113
)
116-
)
114+
}
117115
}
118116
guard
119117
record != .all,
@@ -339,22 +337,8 @@ public struct InlineSnapshotSyntaxDescriptor: Hashable {
339337

340338
#if canImport(SwiftSyntax509)
341339
private let installTestObserver: Void = {
342-
final class InlineSnapshotObserver: NSObject, XCTestObservation {
343-
func testBundleDidFinish(_ testBundle: Bundle) {
344-
writeInlineSnapshots()
345-
}
346-
}
347-
#if canImport(Testing)
348-
if Test.current != nil {
349-
return
350-
}
351-
#endif
352-
if Thread.isMainThread {
353-
XCTestObservationCenter.shared.addTestObserver(InlineSnapshotObserver())
354-
} else {
355-
DispatchQueue.main.sync {
356-
XCTestObservationCenter.shared.addTestObserver(InlineSnapshotObserver())
357-
}
340+
atexit {
341+
writeInlineSnapshots()
358342
}
359343
}()
360344

@@ -378,7 +362,8 @@ public struct InlineSnapshotSyntaxDescriptor: Hashable {
378362
public var column: UInt
379363
}
380364

381-
@_spi(Internals) public var inlineSnapshotState: [File: [InlineSnapshot]] = [:]
365+
@_spi(Internals)
366+
public var inlineSnapshotState: LockIsolated<[File: [InlineSnapshot]]> = LockIsolated([:])
382367

383368
private struct TestSource {
384369
let source: String
@@ -407,29 +392,31 @@ public struct InlineSnapshotSyntaxDescriptor: Hashable {
407392
private var testSourceCache: [File: TestSource] = [:]
408393

409394
private func writeInlineSnapshots() {
410-
defer { inlineSnapshotState.removeAll() }
411-
for (file, snapshots) in inlineSnapshotState {
412-
let line = snapshots.first?.line ?? 1
413-
guard let testSource = try? testSource(file: file)
414-
else {
415-
fatalError("Couldn't load snapshot from disk", file: file.path, line: line)
416-
}
417-
let snapshotRewriter = SnapshotRewriter(
418-
file: file,
419-
snapshots: snapshots.sorted {
420-
$0.line != $1.line
421-
? $0.line < $1.line
422-
: $0.syntaxDescriptor.trailingClosureOffset < $1.syntaxDescriptor.trailingClosureOffset
423-
},
424-
sourceLocationConverter: testSource.sourceLocationConverter
425-
)
426-
let updatedSource = snapshotRewriter.visit(testSource.sourceFile).description
427-
do {
428-
if testSource.source != updatedSource {
429-
try updatedSource.write(toFile: "\(file.path)", atomically: true, encoding: .utf8)
395+
inlineSnapshotState.withLock { inlineSnapshotState in
396+
defer { inlineSnapshotState.removeAll() }
397+
for (file, snapshots) in inlineSnapshotState {
398+
let line = snapshots.first?.line ?? 1
399+
guard let testSource = try? testSource(file: file)
400+
else {
401+
fatalError("Couldn't load snapshot from disk", file: file.path, line: line)
402+
}
403+
let snapshotRewriter = SnapshotRewriter(
404+
file: file,
405+
snapshots: snapshots.sorted {
406+
$0.line != $1.line
407+
? $0.line < $1.line
408+
: $0.syntaxDescriptor.trailingClosureOffset < $1.syntaxDescriptor.trailingClosureOffset
409+
},
410+
sourceLocationConverter: testSource.sourceLocationConverter
411+
)
412+
let updatedSource = snapshotRewriter.visit(testSource.sourceFile).description
413+
do {
414+
if testSource.source != updatedSource {
415+
try updatedSource.write(toFile: "\(file.path)", atomically: true, encoding: .utf8)
416+
}
417+
} catch {
418+
fatalError("Threw error: \(error)", file: file.path, line: line)
430419
}
431-
} catch {
432-
fatalError("Threw error: \(error)", file: file.path, line: line)
433420
}
434421
}
435422
}
@@ -773,3 +760,24 @@ public struct InlineSnapshotSyntaxDescriptor: Hashable {
773760
}
774761
}
775762
#endif
763+
764+
import Foundation
765+
766+
@_spi(Internals)
767+
public final class LockIsolated<Value>: @unchecked Sendable {
768+
private var _value: Value
769+
private let lock = NSLock()
770+
init(_ value: @autoclosure @Sendable () throws -> Value) rethrows {
771+
self._value = try value()
772+
}
773+
@_spi(Internals)
774+
public func withLock<T: Sendable>(
775+
_ operation: @Sendable (inout Value) throws -> T
776+
) rethrows -> T {
777+
lock.lock()
778+
defer { lock.unlock() }
779+
var value = _value
780+
defer { _value = value }
781+
return try operation(&value)
782+
}
783+
}

Tests/InlineSnapshotTestingTests/InlineSnapshotTestingTests.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,8 @@ final class InlineSnapshotTestingTests: BaseTestCase {
283283

284284
#if canImport(Darwin)
285285
func testRecordFailed_IncorrectExpectation() throws {
286-
let initialInlineSnapshotState = inlineSnapshotState
287-
defer { inlineSnapshotState = initialInlineSnapshotState }
286+
let initialInlineSnapshotState = inlineSnapshotState.withLock { $0 }
287+
defer { inlineSnapshotState.withLock { $0 = initialInlineSnapshotState } }
288288

289289
XCTExpectFailure {
290290
withSnapshotTesting(record: .failed) {
@@ -306,19 +306,21 @@ final class InlineSnapshotTestingTests: BaseTestCase {
306306
"""
307307
}
308308

309-
XCTAssertEqual(inlineSnapshotState.count, 1)
310-
XCTAssertEqual(
311-
String(describing: inlineSnapshotState.keys.first!.path)
312-
.hasSuffix("InlineSnapshotTestingTests.swift"),
313-
true
314-
)
309+
inlineSnapshotState.withLock { inlineSnapshotState in
310+
XCTAssertEqual(inlineSnapshotState.count, 1)
311+
XCTAssertEqual(
312+
String(describing: inlineSnapshotState.keys.first!.path)
313+
.hasSuffix("InlineSnapshotTestingTests.swift"),
314+
true
315+
)
316+
}
315317
}
316318
#endif
317319

318320
#if canImport(Darwin)
319321
func testRecordFailed_MissingExpectation() throws {
320-
let initialInlineSnapshotState = inlineSnapshotState
321-
defer { inlineSnapshotState = initialInlineSnapshotState }
322+
let initialInlineSnapshotState = inlineSnapshotState.withLock { $0 }
323+
defer { inlineSnapshotState.withLock { $0 = initialInlineSnapshotState } }
322324

323325
XCTExpectFailure {
324326
withSnapshotTesting(record: .failed) {
@@ -336,12 +338,14 @@ final class InlineSnapshotTestingTests: BaseTestCase {
336338
"""
337339
}
338340

339-
XCTAssertEqual(inlineSnapshotState.count, 1)
340-
XCTAssertEqual(
341-
String(describing: inlineSnapshotState.keys.first!.path)
342-
.hasSuffix("InlineSnapshotTestingTests.swift"),
343-
true
344-
)
341+
inlineSnapshotState.withLock { inlineSnapshotState in
342+
XCTAssertEqual(inlineSnapshotState.count, 1)
343+
XCTAssertEqual(
344+
String(describing: inlineSnapshotState.keys.first!.path)
345+
.hasSuffix("InlineSnapshotTestingTests.swift"),
346+
true
347+
)
348+
}
345349
}
346350
#endif
347351
}

0 commit comments

Comments
 (0)