11import 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+ }
0 commit comments