Skip to content

Commit 334c3c7

Browse files
committed
Make all test content types directly conform to TestContent.
This PR eliminates the `TestContentAccessorResult` associated type from the (currently internal, potentially eventually API) `TestContent` protocol. This associated type needed to be `~Copyable` so `ExitTest` could be used with it, but that appears to pose some _problems_ for the compiler (rdar://143049814&143080508). Instead, we remove the associated type and just say "the test content record is the type that conforms to `TestContent`". `ExitTest` is happy with this, but `Test`'s produced type is a non-nominal function type, so we wrap that function in a small private type with identical layout and have that type conform. The ultimate purpose of this PR is to get us a bit closer to turning `TestContent` into a public or tools-SPI protocol that other components can use for test discovery.
1 parent a5921e0 commit 334c3c7

File tree

4 files changed

+31
-28
lines changed

4 files changed

+31
-28
lines changed

Sources/Testing/Discovery.swift

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,6 @@ protocol TestContent: ~Copyable {
4848
/// `ABI/TestContent.md` for a list of values and corresponding types.
4949
static var testContentKind: UInt32 { get }
5050

51-
/// The type of value returned by the test content accessor for this type.
52-
///
53-
/// This type may or may not equal `Self` depending on the type's compile-time
54-
/// and runtime requirements. If it does not equal `Self`, it should equal a
55-
/// type whose instances can be converted to instances of `Self` (e.g. by
56-
/// calling them if they are functions.)
57-
associatedtype TestContentAccessorResult: ~Copyable
58-
5951
/// A type of "hint" passed to ``discover(withHint:)`` to help the testing
6052
/// library find the correct result.
6153
///
@@ -75,7 +67,7 @@ protocol TestContent: ~Copyable {
7567
/// This type is not part of the public interface of the testing library. In the
7668
/// future, we could make it public if we want to support runtime discovery of
7769
/// test content by second- or third-party code.
78-
struct TestContentRecord<T>: Sendable where T: ~Copyable {
70+
struct TestContentRecord<T>: Sendable where T: TestContent & ~Copyable {
7971
/// The base address of the image containing this instance, if known.
8072
///
8173
/// On platforms such as WASI that statically link to the testing library, the
@@ -93,11 +85,7 @@ struct TestContentRecord<T>: Sendable where T: ~Copyable {
9385
self.imageAddress = imageAddress
9486
self._record = record
9587
}
96-
}
9788

98-
// This `T: TestContent` constraint is in an extension in order to work around a
99-
// compiler crash. SEE: rdar://143049814
100-
extension TestContentRecord where T: TestContent & ~Copyable {
10189
/// The context value for this test content record.
10290
var context: UInt {
10391
_record.context
@@ -109,18 +97,18 @@ extension TestContentRecord where T: TestContent & ~Copyable {
10997
/// - hint: An optional hint value. If not `nil`, this value is passed to
11098
/// the accessor function of the underlying test content record.
11199
///
112-
/// - Returns: An instance of the associated ``TestContentAccessorResult``
113-
/// type, or `nil` if the underlying test content record did not match
114-
/// `hint` or otherwise did not produce a value.
100+
/// - Returns: An instance of the test content type `T`, or `nil` if the
101+
/// underlying test content record did not match `hint` or otherwise did not
102+
/// produce a value.
115103
///
116104
/// If this function is called more than once on the same instance, a new
117105
/// value is created on each call.
118-
func load(withHint hint: T.TestContentAccessorHint? = nil) -> T.TestContentAccessorResult? {
106+
func load(withHint hint: T.TestContentAccessorHint? = nil) -> T? {
119107
guard let accessor = _record.accessor.map(swt_resign) else {
120108
return nil
121109
}
122110

123-
return withUnsafeTemporaryAllocation(of: T.TestContentAccessorResult.self, capacity: 1) { buffer in
111+
return withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buffer in
124112
let initialized = if let hint {
125113
withUnsafePointer(to: hint) { hint in
126114
accessor(buffer.baseAddress!, hint)

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ extension ExitTest: TestContent {
228228
0x65786974
229229
}
230230

231-
typealias TestContentAccessorResult = Self
232231
typealias TestContentAccessorHint = ID
233232
}
234233

Sources/Testing/Test+Discovery.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,28 @@
1010

1111
private import _TestingInternals
1212

13-
extension Test: TestContent {
13+
/// A type that encapsulates test content records that produce instances of
14+
/// ``Test``.
15+
///
16+
/// This type is necessary because such test content records produce an indirect
17+
/// `async` accessor function rather than directly producing instances of
18+
/// ``Test``, but functions are non-nominal types and cannot directly conform to
19+
/// protocols.
20+
///
21+
/// - Note: This helper type must have the exact in-memory layout of the `async`
22+
/// accessor function. Do not add any additional stored properties. The layout
23+
/// of this type is _de facto_ [guaranteed](https://github.com/swiftlang/swift/blob/main/docs/ABI/TypeLayout.rst)
24+
/// by the Swift ABI.
25+
/* @frozen */ private struct _TestRecord: TestContent {
1426
static var testContentKind: UInt32 {
1527
0x74657374
1628
}
1729

18-
typealias TestContentAccessorResult = @Sendable () async -> Self
30+
/// This instance's actual (asynchronous) accessor function.
31+
var asyncAccessor: @Sendable () async -> Test
32+
}
1933

34+
extension Test {
2035
/// All available ``Test`` instances in the process, according to the runtime.
2136
///
2237
/// The order of values in this sequence is unspecified.
@@ -43,7 +58,7 @@ extension Test: TestContent {
4358
// Walk all test content and gather generator functions, then call them in
4459
// a task group and collate their results.
4560
if useNewMode {
46-
let generators = Self.allTestContentRecords().lazy.compactMap { $0.load() }
61+
let generators = _TestRecord.allTestContentRecords().lazy.compactMap { $0.load()?.asyncAccessor }
4762
await withTaskGroup(of: Self.self) { taskGroup in
4863
for generator in generators {
4964
taskGroup.addTask(operation: generator)

Tests/TestingTests/MiscellaneousTests.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,8 @@ struct MiscellaneousTests {
583583
#if !SWT_NO_DYNAMIC_LINKING && hasFeature(SymbolLinkageMarkers)
584584
struct DiscoverableTestContent: TestContent {
585585
typealias TestContentAccessorHint = UInt32
586-
typealias TestContentAccessorResult = UInt32
586+
587+
var value: UInt32
587588

588589
static var testContentKind: UInt32 {
589590
record.kind
@@ -593,7 +594,7 @@ struct MiscellaneousTests {
593594
0x01020304
594595
}
595596

596-
static var expectedResult: TestContentAccessorResult {
597+
static var expectedValue: UInt32 {
597598
0xCAFEF00D
598599
}
599600

@@ -618,7 +619,7 @@ struct MiscellaneousTests {
618619
if let hint, hint.load(as: TestContentAccessorHint.self) != expectedHint {
619620
return false
620621
}
621-
_ = outValue.initializeMemory(as: TestContentAccessorResult.self, to: expectedResult)
622+
_ = outValue.initializeMemory(as: Self.self, to: .init(value: expectedValue))
622623
return true
623624
},
624625
UInt(truncatingIfNeeded: UInt64(0x0204060801030507)),
@@ -639,21 +640,21 @@ struct MiscellaneousTests {
639640

640641
// Can find a single test record
641642
#expect(allRecords.contains { record in
642-
record.load() == DiscoverableTestContent.expectedResult
643+
record.load()?.value == DiscoverableTestContent.expectedValue
643644
&& record.context == DiscoverableTestContent.expectedContext
644645
})
645646

646647
// Can find a test record with matching hint
647648
#expect(allRecords.contains { record in
648649
let hint = DiscoverableTestContent.expectedHint
649-
return record.load(withHint: hint) == DiscoverableTestContent.expectedResult
650+
return record.load(withHint: hint)?.value == DiscoverableTestContent.expectedValue
650651
&& record.context == DiscoverableTestContent.expectedContext
651652
})
652653

653654
// Doesn't find a test record with a mismatched hint
654655
#expect(!allRecords.contains { record in
655656
let hint = ~DiscoverableTestContent.expectedHint
656-
return record.load(withHint: hint) == DiscoverableTestContent.expectedResult
657+
return record.load(withHint: hint)?.value == DiscoverableTestContent.expectedValue
657658
&& record.context == DiscoverableTestContent.expectedContext
658659
})
659660
}

0 commit comments

Comments
 (0)