diff --git a/Documentation/ABI/TestContent.md b/Documentation/ABI/TestContent.md index c1a90aff1..4f1346b95 100644 --- a/Documentation/ABI/TestContent.md +++ b/Documentation/ABI/TestContent.md @@ -63,6 +63,17 @@ struct SWTTestContentRecord { }; ``` +Do not use the `__TestContentRecord` typealias defined in the testing library. +This type exists to support the testing library's macros and may change in the +future (e.g. to accomodate a generic argument or to make use of one of the +reserved fields.) + +Instead, define your own copy of this type where needed—you can copy the +definition above _verbatim_. If your test record type's `context` field (as +described below) is a pointer type, make sure to change its type in your version +of `TestContentRecord` accordingly so that, on systems with pointer +authentication enabled, the pointer is correctly resigned at load time. + ### Record content #### The kind field @@ -79,6 +90,9 @@ record's kind is a 32-bit unsigned value. The following kinds are defined: +If a test content record's `kind` field equals `0x00000000`, the values of all +other fields in that record are undefined. + #### The accessor field The function `accessor` is a C function. When called, it initializes the memory diff --git a/Sources/Testing/Discovery+Platform.swift b/Sources/Testing/Discovery+Platform.swift index 9c352bb41..a42800577 100644 --- a/Sources/Testing/Discovery+Platform.swift +++ b/Sources/Testing/Discovery+Platform.swift @@ -230,7 +230,7 @@ private func _findSection(named sectionName: String, in hModule: HMODULE) -> Sec /// /// - Returns: An array of structures describing the bounds of all known test /// content sections in the current process. -private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { let sectionName = switch kind { case .testContent: ".sw5test" diff --git a/Sources/Testing/Discovery.swift b/Sources/Testing/Discovery.swift index 2ad44b324..647da522f 100644 --- a/Sources/Testing/Discovery.swift +++ b/Sources/Testing/Discovery.swift @@ -48,15 +48,7 @@ protocol TestContent: ~Copyable { /// `ABI/TestContent.md` for a list of values and corresponding types. static var testContentKind: UInt32 { get } - /// The type of value returned by the test content accessor for this type. - /// - /// This type may or may not equal `Self` depending on the type's compile-time - /// and runtime requirements. If it does not equal `Self`, it should equal a - /// type whose instances can be converted to instances of `Self` (e.g. by - /// calling them if they are functions.) - associatedtype TestContentAccessorResult: ~Copyable - - /// A type of "hint" passed to ``discover(withHint:)`` to help the testing + /// A type of "hint" passed to ``allTestContentRecords()`` to help the testing /// library find the correct result. /// /// By default, this type equals `Never`, indicating that this type of test @@ -75,7 +67,7 @@ protocol TestContent: ~Copyable { /// This type is not part of the public interface of the testing library. In the /// future, we could make it public if we want to support runtime discovery of /// test content by second- or third-party code. -struct TestContentRecord: Sendable where T: ~Copyable { +struct TestContentRecord: Sendable where T: TestContent & ~Copyable { /// The base address of the image containing this instance, if known. /// /// On platforms such as WASI that statically link to the testing library, the @@ -93,11 +85,7 @@ struct TestContentRecord: Sendable where T: ~Copyable { self.imageAddress = imageAddress self._record = record } -} -// This `T: TestContent` constraint is in an extension in order to work around a -// compiler crash. SEE: rdar://143049814 -extension TestContentRecord where T: TestContent & ~Copyable { /// The context value for this test content record. var context: UInt { _record.context @@ -109,18 +97,18 @@ extension TestContentRecord where T: TestContent & ~Copyable { /// - hint: An optional hint value. If not `nil`, this value is passed to /// the accessor function of the underlying test content record. /// - /// - Returns: An instance of the associated ``TestContentAccessorResult`` - /// type, or `nil` if the underlying test content record did not match - /// `hint` or otherwise did not produce a value. + /// - Returns: An instance of the test content type `T`, or `nil` if the + /// underlying test content record did not match `hint` or otherwise did not + /// produce a value. /// /// If this function is called more than once on the same instance, a new /// value is created on each call. - func load(withHint hint: T.TestContentAccessorHint? = nil) -> T.TestContentAccessorResult? { + func load(withHint hint: T.TestContentAccessorHint? = nil) -> T? { guard let accessor = _record.accessor else { return nil } - return withUnsafeTemporaryAllocation(of: T.TestContentAccessorResult.self, capacity: 1) { buffer in + return withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buffer in let initialized = if let hint { withUnsafePointer(to: hint) { hint in accessor(buffer.baseAddress!, hint) diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index a3a92c036..f6ea88d22 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -228,7 +228,6 @@ extension ExitTest: TestContent { 0x65786974 } - typealias TestContentAccessorResult = Self typealias TestContentAccessorHint = ID } diff --git a/Sources/Testing/Test+Discovery.swift b/Sources/Testing/Test+Discovery.swift index 939e85947..015a0b1c8 100644 --- a/Sources/Testing/Test+Discovery.swift +++ b/Sources/Testing/Test+Discovery.swift @@ -10,12 +10,27 @@ private import _TestingInternals -extension Test: TestContent { - static var testContentKind: UInt32 { - 0x74657374 - } +extension Test { + /// A type that encapsulates test content records that produce instances of + /// ``Test``. + /// + /// This type is necessary because such test content records produce an + /// indirect `async` accessor function rather than directly producing + /// instances of ``Test``, but functions are non-nominal types and cannot + /// directly conform to protocols. + /// + /// - Note: This helper type must have the exact in-memory layout of the + /// `async` accessor function. Do not add any additional cases or associated + /// values. The layout of this type is [guaranteed](https://github.com/swiftlang/swift/blob/main/docs/ABI/TypeLayout.rst#fragile-enum-layout) + /// by the Swift ABI. + /* @frozen */ private enum _Record: TestContent { + static var testContentKind: UInt32 { + 0x74657374 + } - typealias TestContentAccessorResult = @Sendable () async -> Self + /// The actual (asynchronous) accessor function. + case generator(@Sendable () async -> Test) + } /// All available ``Test`` instances in the process, according to the runtime. /// @@ -43,7 +58,12 @@ extension Test: TestContent { // Walk all test content and gather generator functions, then call them in // a task group and collate their results. if useNewMode { - let generators = Self.allTestContentRecords().lazy.compactMap { $0.load() } + let generators = _Record.allTestContentRecords().lazy.compactMap { record in + if case let .generator(generator) = record.load() { + return generator + } + return nil // currently unreachable, but not provably so + } await withTaskGroup(of: Self.self) { taskGroup in for generator in generators { taskGroup.addTask(operation: generator) diff --git a/Tests/TestingTests/MiscellaneousTests.swift b/Tests/TestingTests/MiscellaneousTests.swift index 9cc3b7910..a172f7d5a 100644 --- a/Tests/TestingTests/MiscellaneousTests.swift +++ b/Tests/TestingTests/MiscellaneousTests.swift @@ -583,7 +583,8 @@ struct MiscellaneousTests { #if !SWT_NO_DYNAMIC_LINKING && hasFeature(SymbolLinkageMarkers) struct DiscoverableTestContent: TestContent { typealias TestContentAccessorHint = UInt32 - typealias TestContentAccessorResult = UInt32 + + var value: UInt32 static var testContentKind: UInt32 { record.kind @@ -593,7 +594,7 @@ struct MiscellaneousTests { 0x01020304 } - static var expectedResult: TestContentAccessorResult { + static var expectedValue: UInt32 { 0xCAFEF00D } @@ -618,7 +619,7 @@ struct MiscellaneousTests { if let hint, hint.load(as: TestContentAccessorHint.self) != expectedHint { return false } - _ = outValue.initializeMemory(as: TestContentAccessorResult.self, to: expectedResult) + _ = outValue.initializeMemory(as: Self.self, to: .init(value: expectedValue)) return true }, UInt(truncatingIfNeeded: UInt64(0x0204060801030507)), @@ -628,32 +629,33 @@ struct MiscellaneousTests { @Test func testDiscovery() async { // Check the type of the test record sequence (it should be lazy.) - let allRecords = DiscoverableTestContent.allTestContentRecords() + let allRecordsSeq = DiscoverableTestContent.allTestContentRecords() #if SWT_FIXED_143080508 - #expect(allRecords is any LazySequenceProtocol) - #expect(!(allRecords is [TestContentRecord])) + #expect(allRecordsSeq is any LazySequenceProtocol) + #expect(!(allRecordsSeq is [TestContentRecord])) #endif // It should have exactly one matching record (because we only emitted one.) - #expect(Array(allRecords).count == 1) + let allRecords = Array(allRecordsSeq) + #expect(allRecords.count == 1) // Can find a single test record #expect(allRecords.contains { record in - record.load() == DiscoverableTestContent.expectedResult + record.load()?.value == DiscoverableTestContent.expectedValue && record.context == DiscoverableTestContent.expectedContext }) // Can find a test record with matching hint #expect(allRecords.contains { record in let hint = DiscoverableTestContent.expectedHint - return record.load(withHint: hint) == DiscoverableTestContent.expectedResult + return record.load(withHint: hint)?.value == DiscoverableTestContent.expectedValue && record.context == DiscoverableTestContent.expectedContext }) // Doesn't find a test record with a mismatched hint #expect(!allRecords.contains { record in let hint = ~DiscoverableTestContent.expectedHint - return record.load(withHint: hint) == DiscoverableTestContent.expectedResult + return record.load(withHint: hint)?.value == DiscoverableTestContent.expectedValue && record.context == DiscoverableTestContent.expectedContext }) }