diff --git a/Documentation/ABI/TestContent.md b/Documentation/ABI/TestContent.md index be2493530..2d0b9a4b8 100644 --- a/Documentation/ABI/TestContent.md +++ b/Documentation/ABI/TestContent.md @@ -149,25 +149,12 @@ The fourth argument to this function, `reserved`, is reserved for future use. Accessor functions should assume it is `0` and must not access it. The concrete Swift type of the value written to `outValue`, the type pointed to -by `type`, and the value pointed to by `hint` depend on the kind of record: +by `type`, and the value pointed to by `hint` depend on the kind of record. -- For test or suite declarations (kind `0x74657374`), the accessor produces a - structure of type `Testing.Test.Generator` that the testing library can use - to generate the corresponding test[^notAccessorSignature]. - - [^notAccessorSignature]: This level of indirection is necessary because - loading a test or suite declaration is an asynchronous operation, but C - functions cannot be `async`. - - Test content records of this kind do not specify a type for `hint`. Always - pass `nil`. - -- For exit test declarations (kind `0x65786974`), the accessor produces a - structure describing the exit test (of type `Testing.ExitTest`.) - - Test content records of this kind accept a `hint` of type `Testing.ExitTest.ID`. - They only produce a result if they represent an exit test declared with the - same ID (or if `hint` is `nil`.) +The record kinds defined by Swift Testing (kinds `0x74657374` and `0x65786974`) +make use of the `DiscoverableAsTestContent` protocol in the `_TestDiscovery` +module and do not publicly expose the types of their accessor functions' +arguments. Do not call the accessor functions for these records directly. > [!WARNING] > Calling code should use [`withUnsafeTemporaryAllocation(of:capacity:_:)`](https://developer.apple.com/documentation/swift/withunsafetemporaryallocation(of:capacity:_:)) @@ -274,7 +261,8 @@ extension FoodTruckDiagnostic: DiscoverableAsTestContent { ``` If you customize `TestContentContext`, be aware that the type you specify must -have the same stride and alignment as `UInt`. +have the same stride as `UInt` and must have an alignment less than or equal to +that of `UInt`. When you are done configuring your type's protocol conformance, you can then enumerate all test content records matching it as instances of diff --git a/Sources/Testing/Discovery+Macro.swift b/Sources/Testing/Discovery+Macro.swift index 97b925e55..35b276efe 100644 --- a/Sources/Testing/Discovery+Macro.swift +++ b/Sources/Testing/Discovery+Macro.swift @@ -8,15 +8,6 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // -@_spi(Experimental) @_spi(ForToolsIntegrationOnly) internal import _TestDiscovery - -/// A shadow declaration of `_TestDiscovery.DiscoverableAsTestContent` that -/// allows us to add public conformances to it without causing the -/// `_TestDiscovery` module to appear in `Testing.private.swiftinterface`. -/// -/// This protocol is not part of the public interface of the testing library. -protocol DiscoverableAsTestContent: _TestDiscovery.DiscoverableAsTestContent, ~Copyable {} - /// The type of the accessor function used to access a test content record. /// /// The signature of this function type must match that of the corresponding diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index baabb7eaa..2ad905379 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -66,15 +66,18 @@ public struct ExitTest: Sendable, ~Copyable { @_spi(ForToolsIntegrationOnly) public var id: ID - /// The body closure of the exit test. + /// An exit test body function. /// /// - Parameters: /// - exitTest: The exit test to which this body closure belongs. + fileprivate typealias Body = @Sendable (_ exitTest: inout Self) async throws -> Void + + /// The body closure of the exit test. /// /// Do not invoke this closure directly. Instead, invoke ``callAsFunction()`` /// to run the exit test. Running the exit test will always terminate the /// current process. - fileprivate var body: @Sendable (_ exitTest: inout Self) async throws -> Void = { _ in } + fileprivate var body: Body = { _ in } /// Storage for ``observedValues``. /// @@ -275,12 +278,34 @@ extension ExitTest { // MARK: - Discovery -extension ExitTest: DiscoverableAsTestContent { - fileprivate static var testContentKind: TestContentKind { - "exit" - } +extension ExitTest { + /// A type representing an exit test as a test content record. + fileprivate struct Record: Sendable, DiscoverableAsTestContent { + static var testContentKind: TestContentKind { + "exit" + } + + typealias TestContentAccessorHint = ID - fileprivate typealias TestContentAccessorHint = ID + /// The ID of the represented exit test. + var id: ExitTest.ID + + /// The body of the represented exit test. + var body: ExitTest.Body + + /// The set of values captured in the parent process before the exit test is + /// called. + var capturedValues = [CapturedValue]() + + /// Make the exit test represented by this instance. + /// + /// - Returns: A new exit test as represented by this instance. + func makeExitTest() -> ExitTest { + var exitTest = ExitTest(id: id, body: body) + exitTest.capturedValues = capturedValues + return exitTest + } + } /// Store the exit test into the given memory. /// @@ -305,9 +330,7 @@ extension ExitTest: DiscoverableAsTestContent { ) -> CBool where repeat each T: Codable & Sendable { #if !hasFeature(Embedded) // Check that the type matches. - let callerExpectedType = TypeInfo(describing: typeAddress.load(as: Any.Type.self)) - let selfType = TypeInfo(describing: Self.self) - guard callerExpectedType == selfType else { + guard typeAddress.load(as: Any.Type.self) == Record.self else { return false } #endif @@ -320,15 +343,15 @@ extension ExitTest: DiscoverableAsTestContent { // Wrap the body function in a thunk that decodes any captured state and // passes it along. - let body: @Sendable (inout Self) async throws -> Void = { exitTest in + let body: ExitTest.Body = { exitTest in let values: (repeat each T) = try exitTest.capturedValues.takeCapturedValues() try await body(repeat each values) } - // Construct and return the instance. - var exitTest = Self(id: id, body: body) - exitTest.capturedValues = Array(repeat (each T).self) - outValue.initializeMemory(as: Self.self, to: exitTest) + // Construct and return the record. + var record = Record(id: id, body: body) + record.capturedValues = Array(repeat (each T).self) + outValue.initializeMemory(as: Record.self, to: record) return true } } @@ -343,16 +366,16 @@ extension ExitTest { /// - Returns: The specified exit test function, or `nil` if no such exit test /// could be found. public static func find(identifiedBy id: ExitTest.ID) -> Self? { - for record in Self.allTestContentRecords() { - if let exitTest = record.load(withHint: id) { + for record in Record.allTestContentRecords() { + if let exitTest = record.load(withHint: id)?.makeExitTest() { return exitTest } } #if !SWT_NO_LEGACY_TEST_DISCOVERY // Call the legacy lookup function that discovers tests embedded in types. - for record in Self.allTypeMetadataBasedTestContentRecords() { - if let exitTest = record.load(withHint: id) { + for record in Record.allTypeMetadataBasedTestContentRecords() { + if let exitTest = record.load(withHint: id)?.makeExitTest() { return exitTest } } diff --git a/Sources/_TestDiscovery/TestContentRecord.swift b/Sources/_TestDiscovery/TestContentRecord.swift index d893664ee..9224fc2ea 100644 --- a/Sources/_TestDiscovery/TestContentRecord.swift +++ b/Sources/_TestDiscovery/TestContentRecord.swift @@ -52,7 +52,7 @@ extension DiscoverableAsTestContent where Self: ~Copyable { /// ([swift-#79667](https://github.com/swiftlang/swift/issues/79667)) fileprivate static func validateMemoryLayout() { precondition(MemoryLayout.stride == MemoryLayout.stride, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have the same stride as 'UInt'.") - precondition(MemoryLayout.alignment == MemoryLayout.alignment, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have the same alignment as 'UInt'.") + precondition(MemoryLayout.alignment <= MemoryLayout.alignment, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have an alignment less than or equal to that of 'UInt'.") } } diff --git a/Tests/TestingTests/DiscoveryTests.swift b/Tests/TestingTests/DiscoveryTests.swift index 2b53cd467..8ec185813 100644 --- a/Tests/TestingTests/DiscoveryTests.swift +++ b/Tests/TestingTests/DiscoveryTests.swift @@ -59,7 +59,7 @@ struct DiscoveryTests { #endif #if !SWT_NO_DYNAMIC_LINKING && hasFeature(SymbolLinkageMarkers) - struct MyTestContent: Testing.DiscoverableAsTestContent { + struct MyTestContent: DiscoverableAsTestContent { typealias TestContentAccessorHint = UInt32 var value: UInt32