@@ -24,60 +24,39 @@ private import _TestingInternals
24
24
25
25
/// A type describing an exit test.
26
26
///
27
- /// Instances of this type describe an exit test defined by the test author and
28
- /// discovered or called at runtime. Tools that implement custom exit test
29
- /// handling will encounter instances of this type in two contexts:
30
- ///
31
- /// - When the current configuration's exit test handler, set with
32
- /// ``Configuration/exitTestHandler``, is called; and
33
- /// - When, in a child process, they need to look up the exit test to call.
34
- ///
35
- /// If you are writing tests, you don't usually need to interact directly with
36
- /// an instance of this type. To create an exit test, use the
27
+ /// Instances of this type describe exit tests you create using the
37
28
/// ``expect(exitsWith:_:sourceLocation:performing:)`` or
38
- /// ``require(exitsWith:_:sourceLocation:performing:)`` macro.
39
- @_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
40
- #if SWT_NO_EXIT_TESTS
41
- @available ( * , unavailable, message: " Exit tests are not available on this platform. " )
42
- #endif
43
- public typealias ExitTest = __ExitTest
44
-
45
- /// A type describing an exit test.
46
- ///
47
- /// - Warning: This type is used to implement the `#expect(exitsWith:)` macro.
48
- /// Do not use it directly. Tools can use the SPI ``ExitTest`` typealias if
49
- /// needed.
29
+ /// ``require(exitsWith:_:sourceLocation:performing:)`` macro. You don't usually
30
+ /// need to interact directly with an instance of this type.
50
31
@_spi ( Experimental)
51
32
#if SWT_NO_EXIT_TESTS
52
33
@available ( * , unavailable, message: " Exit tests are not available on this platform. " )
53
34
#endif
54
- public struct __ExitTest : Sendable , ~ Copyable {
55
- /// A type whose instances uniquely identify instances of `__ExitTest`.
35
+ public struct ExitTest : Sendable , ~ Copyable {
36
+ /// A type whose instances uniquely identify instances of ``ExitTest``.
37
+ @_spi ( ForToolsIntegrationOnly)
56
38
public struct ID : Sendable , Equatable , Codable {
57
39
/// An underlying UUID (stored as two `UInt64` values to avoid relying on
58
40
/// `UUID` from Foundation or any platform-specific interfaces.)
59
41
private var _lo : UInt64
60
42
private var _hi : UInt64
61
43
62
- /// Initialize an instance of this type.
63
- ///
64
- /// - Warning: This member is used to implement the `#expect(exitsWith:)`
65
- /// macro. Do not use it directly.
66
- public init ( __uuid uuid: ( UInt64 , UInt64 ) ) {
44
+ init ( _ uuid: ( UInt64 , UInt64 ) ) {
67
45
self . _lo = uuid. 0
68
46
self . _hi = uuid. 1
69
47
}
70
48
}
71
49
72
50
/// A value that uniquely identifies this instance.
51
+ @_spi ( ForToolsIntegrationOnly)
73
52
public var id : ID
74
53
75
54
/// The body closure of the exit test.
76
55
///
77
56
/// Do not invoke this closure directly. Instead, invoke ``callAsFunction()``
78
57
/// to run the exit test. Running the exit test will always terminate the
79
58
/// current process.
80
- fileprivate var body : @Sendable ( ) async throws -> Void
59
+ fileprivate var body : @Sendable ( ) async throws -> Void = { }
81
60
82
61
/// Storage for ``observedValues``.
83
62
///
@@ -113,21 +92,52 @@ public struct __ExitTest: Sendable, ~Copyable {
113
92
_observedValues = newValue
114
93
}
115
94
}
95
+ }
96
+
97
+ #if !SWT_NO_EXIT_TESTS
98
+ // MARK: - Current
99
+
100
+ @_spi ( Experimental)
101
+ extension ExitTest {
102
+ /// A container type to hold the current exit test.
103
+ ///
104
+ /// This class is temporarily necessary until `ManagedBuffer` is updated to
105
+ /// support storing move-only values. For more information, see [SE-NNNN](https://github.com/swiftlang/swift-evolution/pull/2657).
106
+ private final class _CurrentContainer : Sendable {
107
+ /// The exit test represented by this container.
108
+ ///
109
+ /// The value of this property must be optional to avoid a copy when reading
110
+ /// the value in ``ExitTest/current``.
111
+ let exitTest : ExitTest ?
112
+
113
+ init ( exitTest: borrowing ExitTest ) {
114
+ self . exitTest = ExitTest ( id: exitTest. id, body: exitTest. body, _observedValues: exitTest. _observedValues)
115
+ }
116
+ }
117
+
118
+ /// Storage for ``current``.
119
+ private static let _current = Locked < _CurrentContainer ? > ( )
116
120
117
- /// Initialize an exit test at runtime .
121
+ /// The exit test that is running in the current process, if any .
118
122
///
119
- /// - Warning: This initializer is used to implement the `#expect(exitsWith:)`
120
- /// macro. Do not use it directly.
121
- public init (
122
- __identifiedBy id: ID ,
123
- body: @escaping @Sendable ( ) async throws -> Void = { }
124
- ) {
125
- self . id = id
126
- self . body = body
123
+ /// If the current process was created to run an exit test, the value of this
124
+ /// property describes that exit test. If this process is the parent process
125
+ /// of an exit test, or if no exit test is currently running, the value of
126
+ /// this property is `nil`.
127
+ ///
128
+ /// The value of this property is constant across all tasks in the current
129
+ /// process.
130
+ public static var current : ExitTest ? {
131
+ _read {
132
+ if let current = _current. rawValue {
133
+ yield current. exitTest
134
+ } else {
135
+ yield nil
136
+ }
137
+ }
127
138
}
128
139
}
129
140
130
- #if !SWT_NO_EXIT_TESTS
131
141
// MARK: - Invocation
132
142
133
143
@_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
@@ -180,8 +190,7 @@ extension ExitTest {
180
190
/// This function invokes the closure originally passed to
181
191
/// `#expect(exitsWith:)` _in the current process_. That closure is expected
182
192
/// to terminate the process; if it does not, the testing library will
183
- /// terminate the process in a way that causes the corresponding expectation
184
- /// to fail.
193
+ /// terminate the process as if its `main()` function returned naturally.
185
194
public consuming func callAsFunction( ) async -> Never {
186
195
Self . _disableCrashReporting ( )
187
196
@@ -209,6 +218,11 @@ extension ExitTest {
209
218
}
210
219
#endif
211
220
221
+ // Set ExitTest.current before the test body runs.
222
+ Self . _current. withLock { current in
223
+ current = _CurrentContainer ( exitTest: self )
224
+ }
225
+
212
226
do {
213
227
try await body ( )
214
228
} catch {
@@ -247,11 +261,15 @@ extension ExitTest {
247
261
}
248
262
}
249
263
264
+ #if !SWT_NO_LEGACY_TEST_DISCOVERY
250
265
// Call the legacy lookup function that discovers tests embedded in types.
251
266
return types ( withNamesContaining: exitTestContainerTypeNameMagic) . lazy
252
267
. compactMap { $0 as? any __ExitTestContainer . Type }
253
- . first { $0. __id == id }
254
- . map { ExitTest ( __identifiedBy: $0. __id, body: $0. __body) }
268
+ . first { ID ( $0. __id) == id }
269
+ . map { ExitTest ( id: ID ( $0. __id) , body: $0. __body) }
270
+ #else
271
+ return nil
272
+ #endif
255
273
}
256
274
}
257
275
@@ -280,7 +298,7 @@ extension ExitTest {
280
298
/// `await #expect(exitsWith:) { }` invocations regardless of calling
281
299
/// convention.
282
300
func callExitTest(
283
- identifiedBy exitTestID: ExitTest . ID ,
301
+ identifiedBy exitTestID: ( UInt64 , UInt64 ) ,
284
302
exitsWith expectedExitCondition: ExitCondition ,
285
303
observing observedValues: [ any PartialKeyPath < ExitTestArtifacts > & Sendable ] ,
286
304
expression: __Expression ,
@@ -295,7 +313,7 @@ func callExitTest(
295
313
296
314
var result : ExitTestArtifacts
297
315
do {
298
- var exitTest = ExitTest ( __identifiedBy : exitTestID)
316
+ var exitTest = ExitTest ( id : ExitTest . ID ( exitTestID) )
299
317
exitTest. observedValues = observedValues
300
318
result = try await configuration. exitTestHandler ( exitTest)
301
319
@@ -426,10 +444,10 @@ extension ExitTest {
426
444
/// configurations is undefined.
427
445
static func findInEnvironmentForEntryPoint( ) -> Self ? {
428
446
// Find the ID of the exit test to run, if any, in the environment block.
429
- var id : __ExitTest . ID ?
447
+ var id : ExitTest . ID ?
430
448
if var idString = Environment . variable ( named: " SWT_EXPERIMENTAL_EXIT_TEST_ID " ) {
431
449
id = try ? idString. withUTF8 { idBuffer in
432
- try JSON . decode ( __ExitTest . ID. self, from: UnsafeRawBufferPointer ( idBuffer) )
450
+ try JSON . decode ( ExitTest . ID. self, from: UnsafeRawBufferPointer ( idBuffer) )
433
451
}
434
452
}
435
453
guard let id else {
0 commit comments