1111@_spi ( Experimental) @_spi ( ForToolsIntegrationOnly) private import _TestDiscovery
1212private import _TestingInternals
1313
14- @_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
14+ /// A type representing a testing library such as Swift Testing or XCTest.
15+ @_spi ( Experimental)
1516public struct Library : Sendable {
16- /* @c */ fileprivate struct Record {
17- typealias EntryPoint = @Sendable @convention ( c) (
18- _ configurationJSON: UnsafeRawPointer ,
19- _ configurationJSONByteCount: Int ,
20- _ reserved: UInt ,
21- _ context: UnsafeRawPointer ,
22- _ recordJSONHandler: RecordJSONHandler ,
23- _ completionHandler: CompletionHandler
24- ) -> Void
25-
26- typealias RecordJSONHandler = @Sendable @convention ( c) (
27- _ recordJSON: UnsafeRawPointer ,
28- _ recordJSONByteCount: Int ,
29- _ reserved: UInt ,
30- _ context: UnsafeRawPointer
31- ) -> Void
32-
33- typealias CompletionHandler = @Sendable @convention ( c) (
34- _ exitCode: CInt ,
35- _ reserved: UInt ,
36- _ context: UnsafeRawPointer
37- ) -> Void
38-
39- nonisolated ( unsafe) var name: UnsafePointer < CChar >
40- var entryPoint : EntryPoint
41- var reserved : UInt
42- }
17+ /// - Important: The in-memory layout of ``Library`` must _exactly_ match the
18+ /// layout of this type. As such, it must not contain any other stored
19+ /// properties.
20+ private nonisolated ( unsafe) var _library: SWTLibrary
4321
44- private var _record : Record
22+ fileprivate init ( _ library: SWTLibrary ) {
23+ _library = library
24+ }
4525
26+ /// The human-readable name of this library.
27+ ///
28+ /// For example, the value of this property for an instance of this type that
29+ /// represents the Swift Testing library is `"Swift Testing"`.
4630 public var name : String {
47- String ( validatingCString: _record . name) ?? " "
31+ String ( validatingCString: _library . name) ?? " "
4832 }
4933
34+ /// Call the entry point function of this library.
35+ @_spi ( ForToolsIntegrationOnly)
5036 public func callEntryPoint(
5137 passing args: __CommandLineArguments_v0 ? = nil ,
5238 recordHandler: @escaping @Sendable (
@@ -61,7 +47,7 @@ public struct Library: Sendable {
6147 }
6248 } catch {
6349 // TODO: more advanced error recovery?
64- return EINVAL
50+ return EXIT_FAILURE
6551 }
6652
6753 return await withCheckedContinuation { continuation in
@@ -76,20 +62,20 @@ public struct Library: Sendable {
7662 ) as AnyObject
7763 ) . toOpaque ( )
7864 configurationJSON. withUnsafeBytes { configurationJSON in
79- _record . entryPoint (
65+ _library . entryPoint (
8066 configurationJSON. baseAddress!,
8167 configurationJSON. count,
8268 0 ,
8369 context,
8470 /* recordJSONHandler: */ { recordJSON, recordJSONByteCount, _, context in
85- guard let context = Unmanaged < AnyObject > . fromOpaque ( context) . takeUnretainedValue ( ) as? Context else {
71+ guard let context = Unmanaged < AnyObject > . fromOpaque ( context! ) . takeUnretainedValue ( ) as? Context else {
8672 return
8773 }
8874 let recordJSON = UnsafeRawBufferPointer ( start: recordJSON, count: recordJSONByteCount)
8975 context. recordHandler ( recordJSON)
9076 } ,
9177 /* completionHandler: */ { exitCode, _, context in
92- guard let context = Unmanaged < AnyObject > . fromOpaque ( context) . takeRetainedValue ( ) as? Context else {
78+ guard let context = Unmanaged < AnyObject > . fromOpaque ( context! ) . takeRetainedValue ( ) as? Context else {
9379 return
9480 }
9581 context. continuation. resume ( returning: exitCode)
@@ -102,22 +88,33 @@ public struct Library: Sendable {
10288
10389// MARK: - Discovery
10490
105- @_spi ( Experimental) @_spi ( ForToolsIntegrationOnly)
106- extension Library . Record : DiscoverableAsTestContent {
107- static var testContentKind : TestContentKind {
91+ /// A helper protocol that prevents the conformance of ``Library`` to
92+ /// ``DiscoverableAsTestContent`` from being emitted into the testing library's
93+ /// Swift module or interface files.
94+ private protocol _DiscoverableAsTestContent : DiscoverableAsTestContent { }
95+
96+ extension Library : _DiscoverableAsTestContent {
97+ fileprivate static var testContentKind : TestContentKind {
10898 " main "
10999 }
110100
111- typealias TestContentAccessorHint = UnsafePointer < CChar >
101+ fileprivate typealias TestContentAccessorHint = UnsafePointer < CChar >
112102}
113103
114- @_spi ( Experimental) @ _spi ( ForToolsIntegrationOnly )
104+ @_spi ( Experimental)
115105extension Library {
106+ private static let _validateMemoryLayout : Void = {
107+ assert ( MemoryLayout< Library> . size == MemoryLayout< SWTLibrary> . size, " Library.size ( \( MemoryLayout< Library> . size) ) != SWTLibrary.size ( \( MemoryLayout< SWTLibrary> . size) ). Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new " )
108+ assert ( MemoryLayout< Library> . stride == MemoryLayout< SWTLibrary> . stride, " Library.stride ( \( MemoryLayout< Library> . stride) ) != SWTLibrary.stride ( \( MemoryLayout< SWTLibrary> . stride) ). Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new " )
109+ assert ( MemoryLayout< Library> . alignment == MemoryLayout< SWTLibrary> . alignment, " Library.alignment ( \( MemoryLayout< Library> . alignment) ) != SWTLibrary.alignment ( \( MemoryLayout< SWTLibrary> . alignment) ). Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new " )
110+ } ( )
111+
112+ @_spi ( ForToolsIntegrationOnly)
116113 public init ? ( named name: String ) {
114+ Self . _validateMemoryLayout
117115 let result = name. withCString { name in
118- Record . allTestContentRecords ( ) . lazy
116+ Library . allTestContentRecords ( ) . lazy
119117 . compactMap { $0. load ( withHint: name) }
120- . map ( Self . init ( _record: ) )
121118 . first
122119 }
123120 if let result {
@@ -127,60 +124,83 @@ extension Library {
127124 }
128125 }
129126
127+ @_spi ( ForToolsIntegrationOnly)
130128 public static var all : some Sequence < Self > {
131- Record . allTestContentRecords ( ) . lazy
132- . compactMap { $0. load ( ) }
133- . map ( Self . init ( _record: ) )
129+ Self . _validateMemoryLayout
130+ return Library . allTestContentRecords ( ) . lazy. compactMap { $0. load ( ) }
134131 }
135132}
136133
137134// MARK: - Our very own entry point
138135
139- private let testingLibraryDiscoverableEntryPoint : Library . Record . EntryPoint = { configurationJSON, configurationJSONByteCount, _, context, recordJSONHandler, completionHandler in
136+ private let _discoverableEntryPoint : SWTLibraryEntryPoint = { configurationJSON, configurationJSONByteCount, _, context, recordJSONHandler, completionHandler in
137+ // Capture appropriate state from the arguments to forward into the canonical
138+ // entry point function.
139+ let contextBitPattern = UInt ( bitPattern: context)
140+ let configurationJSON = UnsafeRawBufferPointer ( start: configurationJSON, count: configurationJSONByteCount)
141+ var args : __CommandLineArguments_v0
142+ let eventHandler : Event . Handler
140143 do {
141- nonisolated ( unsafe) let context = context
142- let configurationJSON = UnsafeRawBufferPointer ( start: configurationJSON, count: configurationJSONByteCount)
143- let args = try JSON . decode ( __CommandLineArguments_v0. self, from: configurationJSON)
144- let eventHandler = try eventHandlerForStreamingEvents ( withVersionNumber: args. eventStreamVersionNumber, encodeAsJSONLines: false ) { recordJSON in
145- recordJSONHandler ( recordJSON. baseAddress!, recordJSON. count, 0 , context)
146- }
147-
148- Task . detached {
149- let exitCode = await Testing . entryPoint ( passing: args, eventHandler: eventHandler)
150- completionHandler ( exitCode, 0 , context)
144+ args = try JSON . decode ( __CommandLineArguments_v0. self, from: configurationJSON)
145+ eventHandler = try eventHandlerForStreamingEvents ( withVersionNumber: args. eventStreamVersionNumber, encodeAsJSONLines: false ) { recordJSON in
146+ let context = UnsafeRawPointer ( bitPattern: contextBitPattern) !
147+ recordJSONHandler ( recordJSON. baseAddress!, recordJSON. count, 0 , context)
151148 }
152149 } catch {
153150 // TODO: more advanced error recovery?
154- return completionHandler ( EINVAL , 0 , context)
151+ return completionHandler ( EXIT_FAILURE , 0 , context)
155152 }
153+
154+ // Avoid infinite recursion. (Other libraries don't need to clear this field.)
155+ args. testingLibrary = nil
156+
157+ #if !SWT_NO_UNSTRUCTURED_TASKS
158+ Task . detached { [ args] in
159+ let context = UnsafeRawPointer ( bitPattern: contextBitPattern) !
160+ let exitCode = await Testing . entryPoint ( passing: args, eventHandler: eventHandler)
161+ completionHandler ( exitCode, 0 , context)
162+ }
163+ #else
164+ let exitCode = Task . runInline { [ args] in
165+ await Testing . entryPoint ( passing: args, eventHandler: eventHandler)
166+ }
167+ completionHandler ( exitCode, 0 , context)
168+ #endif
156169}
157170
158- private func testingLibraryDiscoverableAccessor( _ outValue: UnsafeMutableRawPointer , _ type: UnsafeRawPointer , _ hint: UnsafeRawPointer ? , _ reserved: UInt ) -> CBool {
171+ private func _discoverableAccessor( _ outValue: UnsafeMutableRawPointer , _ type: UnsafeRawPointer , _ hint: UnsafeRawPointer ? , _ reserved: UInt ) -> CBool {
172+ #if !hasFeature(Embedded)
173+ // Make sure that the caller supplied the right Swift type. If a testing
174+ // library is implemented in a language other than Swift, they can either:
175+ // ignore this argument; or ask the Swift runtime for the type metadata
176+ // pointer and compare it against the value `type.pointee` (`*type` in C).
177+ guard type. load ( as: Any . Type. self) == Library . self else {
159178 return false
160- // #if !hasFeature(Embedded)
161- // guard type.load(as: Any.Type.self) == Library.Record.self else {
162- // return false
163- // }
164- // #endif
165- // let hint = hint.map { $0.load(as: UnsafePointer<CChar>.self) }
166- // if let hint {
167- // guard let hint = String(validatingCString: hint),
168- // String(hint.filter(\.isLetter)).lowercased() == "swifttesting" else {
169- // return false
170- // }
171- // }
172- // let name: StaticString = "Swift Testing"
173- // name.utf8Start.withMemoryRebound(to: CChar.self, capacity: name.utf8CodeUnitCount + 1) { name in
174- // _ = outValue.initializeMemory(
175- // as: Library.Record.self,
176- // to: .init(
177- // name: name,
178- // entryPoint: testingLibraryDiscoverableEntryPoint,
179- // reserved: 0
180- // )
181- // )
182- // }
183- // return true
179+ }
180+ #endif
181+
182+ // Check if the name of the testing library the caller wants is equivalent to
183+ // "Swift Testing", ignoring case and punctuation. (If the caller did not
184+ // specify a library name, the caller wants records for all libraries.)
185+ let hint = hint. map { $0. load ( as: UnsafePointer< CChar> . self ) }
186+ if let hint {
187+ guard let hint = String ( validatingCString: hint) ,
188+ String ( hint. filter ( \. isLetter) ) . lowercased ( ) == " swifttesting " else {
189+ return false
190+ }
191+ }
192+
193+ // Initialize the provided memory to the (ABI-stable) library structure.
194+ _ = outValue. initializeMemory (
195+ as: SWTLibrary . self,
196+ to: . init(
197+ name: swt_getSwiftTestingLibraryName ( ) ,
198+ entryPoint: _discoverableEntryPoint,
199+ reserved: ( 0 , 0 , 0 , 0 , 0 , 0 )
200+ )
201+ )
202+
203+ return true
184204}
185205
186206#if compiler(>=6.3)
@@ -194,11 +214,22 @@ private func testingLibraryDiscoverableAccessor(_ outValue: UnsafeMutableRawPoin
194214//@__testing(warning: "Platform-specific implementation missing: test content section name unavailable")
195215#endif
196216@used
217+ #else
218+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS)
219+ @_section ( " __DATA_CONST,__swift5_tests " )
220+ #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
221+ @_section ( " swift5_tests " )
222+ #elseif os(Windows)
223+ @_section ( " .sw5test$B " )
224+ #else
225+ //@__testing(warning: "Platform-specific implementation missing: test content section name unavailable")
226+ #endif
227+ @_used
228+ #endif
197229private let testingLibraryRecord : __TestContentRecord = (
198230 0x6D61696E , /* 'main' */
199231 0 ,
200- testingLibraryDiscoverableAccessor ,
232+ _discoverableAccessor ,
201233 0 ,
202234 0
203235)
204- #endif
0 commit comments