diff --git a/Sources/Testing/Test+Discovery+Legacy.swift b/Sources/Testing/Test+Discovery+Legacy.swift index 711f95e73..a3374b315 100644 --- a/Sources/Testing/Test+Discovery+Legacy.swift +++ b/Sources/Testing/Test+Discovery+Legacy.swift @@ -11,35 +11,37 @@ #if !SWT_NO_LEGACY_TEST_DISCOVERY @_spi(Experimental) @_spi(ForToolsIntegrationOnly) internal import _TestDiscovery -/// A shadow declaration of `_TestDiscovery.TestContentRecordContainer` 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. -@_alwaysEmitConformanceMetadata -protocol TestContentRecordContainer: _TestDiscovery.TestContentRecordContainer {} - -/// An abstract base class describing a type that contains tests. +/// A protocol base class describing a type that contains tests. /// /// - Warning: This class is used to implement the `@Test` macro. Do not use it /// directly. -open class __TestContentRecordContainer: TestContentRecordContainer { - /// The corresponding test content record. +@_alwaysEmitConformanceMetadata +public protocol __TestContentRecordContainer { + /// The test content record associated with this container. /// /// - Warning: This property is used to implement the `@Test` macro. Do not /// use it directly. - open nonisolated class var __testContentRecord: __TestContentRecord { - (0, 0, nil, 0, 0) - } + nonisolated static var __testContentRecord: __TestContentRecord { get } +} + +extension DiscoverableAsTestContent where Self: ~Copyable { + /// Get all test content of this type known to Swift and found in the current + /// process using the legacy discovery mechanism. + /// + /// - Returns: A sequence of instances of ``TestContentRecord``. Only test + /// content records matching this ``TestContent`` type's requirements are + /// included in the sequence. + static func allTypeMetadataBasedTestContentRecords() -> AnySequence> { + return allTypeMetadataBasedTestContentRecords { type, buffer in + guard let type = type as? any __TestContentRecordContainer.Type else { + return false + } - static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool { - outTestContentRecord.withMemoryRebound(to: __TestContentRecord.self, capacity: 1) { outTestContentRecord in - outTestContentRecord.initialize(to: __testContentRecord) + buffer.withMemoryRebound(to: __TestContentRecord.self) { buffer in + buffer.baseAddress!.initialize(to: type.__testContentRecord) + } return true } } } - -@available(*, unavailable) -extension __TestContentRecordContainer: Sendable {} #endif diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 43d28aa53..b7a1526c1 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -469,10 +469,10 @@ extension ExitTestConditionMacro { // Create another local type for legacy test discovery. var recordDecl: DeclSyntax? #if !SWT_NO_LEGACY_TEST_DISCOVERY - let className = context.makeUniqueName("__🟡$") + let legacyEnumName = context.makeUniqueName("__🟡$") recordDecl = """ - private final class \(className): Testing.__TestContentRecordContainer { - override nonisolated class var __testContentRecord: Testing.__TestContentRecord { + enum \(legacyEnumName): Testing.__TestContentRecordContainer { + nonisolated static var __testContentRecord: Testing.__TestContentRecord { \(enumName).testContentRecord } } diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index c90606577..791d08b82 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -166,12 +166,12 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable { #if !SWT_NO_LEGACY_TEST_DISCOVERY // Emit a type that contains a reference to the test content record. - let className = context.makeUniqueName("__🟡$") + let enumName = context.makeUniqueName("__🟡$") result.append( """ @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") - final class \(className): Testing.__TestContentRecordContainer { - override nonisolated class var __testContentRecord: Testing.__TestContentRecord { + enum \(enumName): Testing.__TestContentRecordContainer { + nonisolated static var __testContentRecord: Testing.__TestContentRecord { \(testContentRecordName) } } diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 3c6b12fe0..c94769d21 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -491,12 +491,12 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { #if !SWT_NO_LEGACY_TEST_DISCOVERY // Emit a type that contains a reference to the test content record. - let className = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$") + let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$") result.append( """ @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") - final class \(className): Testing.__TestContentRecordContainer { - override nonisolated class var __testContentRecord: Testing.__TestContentRecord { + enum \(enumName): Testing.__TestContentRecordContainer { + nonisolated static var __testContentRecord: Testing.__TestContentRecord { \(testContentRecordName) } } diff --git a/Sources/_TestDiscovery/DiscoverableAsTestContent.swift b/Sources/_TestDiscovery/DiscoverableAsTestContent.swift index 719b8dbe9..96a8c7698 100644 --- a/Sources/_TestDiscovery/DiscoverableAsTestContent.swift +++ b/Sources/_TestDiscovery/DiscoverableAsTestContent.swift @@ -39,4 +39,17 @@ public protocol DiscoverableAsTestContent: Sendable, ~Copyable { /// By default, this type equals `Never`, indicating that this type of test /// content does not support hinting during discovery. associatedtype TestContentAccessorHint = Never + +#if !SWT_NO_LEGACY_TEST_DISCOVERY + /// A string present in the names of types containing test content records + /// associated with this type. + @available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.") + static var _testContentTypeNameHint: String { get } +#endif +} + +extension DiscoverableAsTestContent where Self: ~Copyable { + public static var _testContentTypeNameHint: String { + "__🟡$" + } } diff --git a/Sources/_TestDiscovery/TestContentRecord.swift b/Sources/_TestDiscovery/TestContentRecord.swift index 8fee1827b..4c7ec8d4d 100644 --- a/Sources/_TestDiscovery/TestContentRecord.swift +++ b/Sources/_TestDiscovery/TestContentRecord.swift @@ -249,80 +249,17 @@ extension DiscoverableAsTestContent where Self: ~Copyable { private import _TestingInternals -/// A protocol describing a type, emitted at compile time or macro expansion -/// time, that represents a single test content record. -/// -/// Use this protocol to make discoverable any test content records contained in -/// the type metadata section (the "legacy discovery mechanism"). For example, -/// if you have creasted a test content record named `myRecord` and your test -/// content record typealias is named `MyRecordType`: -/// -/// ```swift -/// private enum MyRecordContainer: TestContentRecordContainer { -/// nonisolated static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool { -/// outTestContentRecord.initializeMemory(as: MyRecordType.self, to: myRecord) -/// return true -/// } -/// } -/// ``` -/// -/// Then, at discovery time, call ``DiscoverableAsTestContent/allTypeMetadataBasedTestContentRecords()`` -/// to look up `myRecord`. -/// -/// Types that represent test content and that should be discoverable at runtime -/// should not conform to this protocol. Instead, they should conform to -/// ``DiscoverableAsTestContent``. -@_spi(Experimental) @_spi(ForToolsIntegrationOnly) -@_alwaysEmitConformanceMetadata -@available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.") -public protocol TestContentRecordContainer { - /// Store this container's corresponding test content record to memory. - /// - /// - Parameters: - /// - outTestContentRecord: A pointer to uninitialized memory large enough - /// to hold a test content record. The memory is untyped so that client - /// code can use a custom definition of the test content record tuple - /// type. - /// - /// - Returns: Whether or not `outTestContentRecord` was initialized. If this - /// function returns `true`, the caller is responsible for deinitializing - /// said memory after it is done using it. - nonisolated static func storeTestContentRecord(to outTestContentRecord: UnsafeMutableRawPointer) -> Bool -} - extension DiscoverableAsTestContent where Self: ~Copyable { - /// Make a test content record of this type from the given test content record - /// container type if it matches this type's requirements. - /// - /// - Parameters: - /// - containerType: The test content record container type. - /// - sb: The section bounds containing `containerType` and, thus, the test - /// content record. - /// - /// - Returns: A new test content record value, or `nil` if `containerType` - /// failed to store a record or if the record's kind did not match this - /// type's ``testContentKind`` property. - private static func _makeTestContentRecord(from containerType: (some TestContentRecordContainer).Type, in sb: SectionBounds) -> TestContentRecord? { - withUnsafeTemporaryAllocation(of: _TestContentRecord.self, capacity: 1) { buffer in - // Load the record from the container type. - guard containerType.storeTestContentRecord(to: buffer.baseAddress!) else { - return nil - } - let record = buffer.baseAddress!.move() - - // Make sure that the record's kind matches. - guard record.kind == Self.testContentKind else { - return nil - } - - // Construct the TestContentRecord instance from the record. - return TestContentRecord(imageAddress: sb.imageAddress, record: record) - } - } - /// Get all test content of this type known to Swift and found in the current /// process using the legacy discovery mechanism. /// + /// - Parameters: + /// - baseType: The type which all discovered container types must + /// conform to or subclass. + /// - loader: A function that is called once per type conforming to or + /// subclassing `baseType`. This function should load the corresponding + /// test content record into the buffer passed to it. + /// /// - Returns: A sequence of instances of ``TestContentRecord``. Only test /// content records matching this ``TestContent`` type's requirements are /// included in the sequence. @@ -332,15 +269,30 @@ extension DiscoverableAsTestContent where Self: ~Copyable { /// opaque type due to a compiler crash. ([143080508](rdar://143080508)) /// } @available(swift, deprecated: 100000.0, message: "Do not adopt this functionality in new code. It will be removed in a future release.") - public static func allTypeMetadataBasedTestContentRecords() -> AnySequence> { + public static func allTypeMetadataBasedTestContentRecords( + loadingWith loader: @escaping @Sendable (Any.Type, UnsafeMutableRawBufferPointer) -> Bool + ) -> AnySequence> { validateMemoryLayout() + let typeNameHint = _testContentTypeNameHint + let kind = testContentKind + let loader: @Sendable (Any.Type) -> _TestContentRecord? = { type in + withUnsafeTemporaryAllocation(of: _TestContentRecord.self, capacity: 1) { buffer in + // Load the record from the container type. + guard loader(type, .init(buffer)) else { + return nil + } + return buffer.baseAddress!.move() + } + } + let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy - .compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: "__🟡$") } + .compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: typeNameHint) } .map { unsafeBitCast($0, to: Any.Type.self) } - .compactMap { $0 as? any TestContentRecordContainer.Type } - .compactMap { _makeTestContentRecord(from: $0, in: sb) } + .compactMap(loader) + .filter { $0.kind == kind } + .map { TestContentRecord(imageAddress: sb.imageAddress, record: $0) } } return AnySequence(result) }