@@ -24,60 +24,39 @@ private import _TestingInternals
2424
2525/// A type describing an exit test.
2626///
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
3728/// ``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.
5031@_spi ( Experimental)
5132#if SWT_NO_EXIT_TESTS
5233@available ( * , unavailable, message: " Exit tests are not available on this platform. " )
5334#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)
5638 public struct ID : Sendable , Equatable , Codable {
5739 /// An underlying UUID (stored as two `UInt64` values to avoid relying on
5840 /// `UUID` from Foundation or any platform-specific interfaces.)
5941 private var _lo : UInt64
6042 private var _hi : UInt64
6143
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 ) ) {
6745 self . _lo = uuid. 0
6846 self . _hi = uuid. 1
6947 }
7048 }
7149
7250 /// A value that uniquely identifies this instance.
51+ @_spi ( ForToolsIntegrationOnly)
7352 public var id : ID
7453
7554 /// The body closure of the exit test.
7655 ///
7756 /// Do not invoke this closure directly. Instead, invoke ``callAsFunction()``
7857 /// to run the exit test. Running the exit test will always terminate the
7958 /// current process.
80- fileprivate var body : @Sendable ( ) async throws -> Void
59+ fileprivate var body : @Sendable ( ) async throws -> Void = { }
8160
8261 /// Storage for ``observedValues``.
8362 ///
@@ -113,21 +92,52 @@ public struct __ExitTest: Sendable, ~Copyable {
11392 _observedValues = newValue
11493 }
11594 }
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 ? > ( )
116120
117- /// Initialize an exit test at runtime .
121+ /// The exit test that is running in the current process, if any .
118122 ///
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+ }
127138 }
128139}
129140
130- #if !SWT_NO_EXIT_TESTS
131141// MARK: - Invocation
132142
133143@_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
@@ -180,8 +190,7 @@ extension ExitTest {
180190 /// This function invokes the closure originally passed to
181191 /// `#expect(exitsWith:)` _in the current process_. That closure is expected
182192 /// 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.
185194 public consuming func callAsFunction( ) async -> Never {
186195 Self . _disableCrashReporting ( )
187196
@@ -209,6 +218,11 @@ extension ExitTest {
209218 }
210219#endif
211220
221+ // Set ExitTest.current before the test body runs.
222+ Self . _current. withLock { current in
223+ current = _CurrentContainer ( exitTest: self )
224+ }
225+
212226 do {
213227 try await body ( )
214228 } catch {
@@ -247,11 +261,15 @@ extension ExitTest {
247261 }
248262 }
249263
264+ #if !SWT_NO_LEGACY_TEST_DISCOVERY
250265 // Call the legacy lookup function that discovers tests embedded in types.
251266 return types ( withNamesContaining: exitTestContainerTypeNameMagic) . lazy
252267 . 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
255273 }
256274}
257275
@@ -280,7 +298,7 @@ extension ExitTest {
280298/// `await #expect(exitsWith:) { }` invocations regardless of calling
281299/// convention.
282300func callExitTest(
283- identifiedBy exitTestID: ExitTest . ID ,
301+ identifiedBy exitTestID: ( UInt64 , UInt64 ) ,
284302 exitsWith expectedExitCondition: ExitCondition ,
285303 observing observedValues: [ any PartialKeyPath < ExitTestArtifacts > & Sendable ] ,
286304 expression: __Expression ,
@@ -295,7 +313,7 @@ func callExitTest(
295313
296314 var result : ExitTestArtifacts
297315 do {
298- var exitTest = ExitTest ( __identifiedBy : exitTestID)
316+ var exitTest = ExitTest ( id : ExitTest . ID ( exitTestID) )
299317 exitTest. observedValues = observedValues
300318 result = try await configuration. exitTestHandler ( exitTest)
301319
@@ -426,10 +444,10 @@ extension ExitTest {
426444 /// configurations is undefined.
427445 static func findInEnvironmentForEntryPoint( ) -> Self ? {
428446 // 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 ?
430448 if var idString = Environment . variable ( named: " SWT_EXPERIMENTAL_EXIT_TEST_ID " ) {
431449 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) )
433451 }
434452 }
435453 guard let id else {
0 commit comments