Skip to content

Commit 9342542

Browse files
Relax the alignment requirement for DiscoverableAsTestContent.Context. (#1076)
This PR allows `DiscoverableAsTestContent.Context` to be less-aligned than `UInt` so long as its stride remains the same. It also removes the sneaky conformance of `ExitTest` to `DiscoverableAsTestContent`, opting instead to use an internal type. Since the conformance to `DiscoverableAsTestContent` and the implementation of `__store()` form a closed system (where all type information is controlled by Swift Testing at runtime), we can do this without breaking any ABI. I've updated ABI/TestContent.md to remove some of the relevant implementation details. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated. --------- Co-authored-by: Stuart Montgomery <[email protected]>
1 parent d75d0e3 commit 9342542

File tree

5 files changed

+51
-49
lines changed

5 files changed

+51
-49
lines changed

Documentation/ABI/TestContent.md

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,25 +149,12 @@ The fourth argument to this function, `reserved`, is reserved for future use.
149149
Accessor functions should assume it is `0` and must not access it.
150150

151151
The concrete Swift type of the value written to `outValue`, the type pointed to
152-
by `type`, and the value pointed to by `hint` depend on the kind of record:
152+
by `type`, and the value pointed to by `hint` depend on the kind of record.
153153

154-
- For test or suite declarations (kind `0x74657374`), the accessor produces a
155-
structure of type `Testing.Test.Generator` that the testing library can use
156-
to generate the corresponding test[^notAccessorSignature].
157-
158-
[^notAccessorSignature]: This level of indirection is necessary because
159-
loading a test or suite declaration is an asynchronous operation, but C
160-
functions cannot be `async`.
161-
162-
Test content records of this kind do not specify a type for `hint`. Always
163-
pass `nil`.
164-
165-
- For exit test declarations (kind `0x65786974`), the accessor produces a
166-
structure describing the exit test (of type `Testing.ExitTest`.)
167-
168-
Test content records of this kind accept a `hint` of type `Testing.ExitTest.ID`.
169-
They only produce a result if they represent an exit test declared with the
170-
same ID (or if `hint` is `nil`.)
154+
The record kinds defined by Swift Testing (kinds `0x74657374` and `0x65786974`)
155+
make use of the `DiscoverableAsTestContent` protocol in the `_TestDiscovery`
156+
module and do not publicly expose the types of their accessor functions'
157+
arguments. Do not call the accessor functions for these records directly.
171158

172159
> [!WARNING]
173160
> Calling code should use [`withUnsafeTemporaryAllocation(of:capacity:_:)`](https://developer.apple.com/documentation/swift/withunsafetemporaryallocation(of:capacity:_:))
@@ -274,7 +261,8 @@ extension FoodTruckDiagnostic: DiscoverableAsTestContent {
274261
```
275262

276263
If you customize `TestContentContext`, be aware that the type you specify must
277-
have the same stride and alignment as `UInt`.
264+
have the same stride as `UInt` and must have an alignment less than or equal to
265+
that of `UInt`.
278266

279267
When you are done configuring your type's protocol conformance, you can then
280268
enumerate all test content records matching it as instances of

Sources/Testing/Discovery+Macro.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@
88
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
//
1010

11-
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) internal import _TestDiscovery
12-
13-
/// A shadow declaration of `_TestDiscovery.DiscoverableAsTestContent` that
14-
/// allows us to add public conformances to it without causing the
15-
/// `_TestDiscovery` module to appear in `Testing.private.swiftinterface`.
16-
///
17-
/// This protocol is not part of the public interface of the testing library.
18-
protocol DiscoverableAsTestContent: _TestDiscovery.DiscoverableAsTestContent, ~Copyable {}
19-
2011
/// The type of the accessor function used to access a test content record.
2112
///
2213
/// The signature of this function type must match that of the corresponding

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,18 @@ public struct ExitTest: Sendable, ~Copyable {
6666
@_spi(ForToolsIntegrationOnly)
6767
public var id: ID
6868

69-
/// The body closure of the exit test.
69+
/// An exit test body function.
7070
///
7171
/// - Parameters:
7272
/// - exitTest: The exit test to which this body closure belongs.
73+
fileprivate typealias Body = @Sendable (_ exitTest: inout Self) async throws -> Void
74+
75+
/// The body closure of the exit test.
7376
///
7477
/// Do not invoke this closure directly. Instead, invoke ``callAsFunction()``
7578
/// to run the exit test. Running the exit test will always terminate the
7679
/// current process.
77-
fileprivate var body: @Sendable (_ exitTest: inout Self) async throws -> Void = { _ in }
80+
fileprivate var body: Body = { _ in }
7881

7982
/// Storage for ``observedValues``.
8083
///
@@ -275,12 +278,34 @@ extension ExitTest {
275278

276279
// MARK: - Discovery
277280

278-
extension ExitTest: DiscoverableAsTestContent {
279-
fileprivate static var testContentKind: TestContentKind {
280-
"exit"
281-
}
281+
extension ExitTest {
282+
/// A type representing an exit test as a test content record.
283+
fileprivate struct Record: Sendable, DiscoverableAsTestContent {
284+
static var testContentKind: TestContentKind {
285+
"exit"
286+
}
287+
288+
typealias TestContentAccessorHint = ID
282289

283-
fileprivate typealias TestContentAccessorHint = ID
290+
/// The ID of the represented exit test.
291+
var id: ExitTest.ID
292+
293+
/// The body of the represented exit test.
294+
var body: ExitTest.Body
295+
296+
/// The set of values captured in the parent process before the exit test is
297+
/// called.
298+
var capturedValues = [CapturedValue]()
299+
300+
/// Make the exit test represented by this instance.
301+
///
302+
/// - Returns: A new exit test as represented by this instance.
303+
func makeExitTest() -> ExitTest {
304+
var exitTest = ExitTest(id: id, body: body)
305+
exitTest.capturedValues = capturedValues
306+
return exitTest
307+
}
308+
}
284309

285310
/// Store the exit test into the given memory.
286311
///
@@ -305,9 +330,7 @@ extension ExitTest: DiscoverableAsTestContent {
305330
) -> CBool where repeat each T: Codable & Sendable {
306331
#if !hasFeature(Embedded)
307332
// Check that the type matches.
308-
let callerExpectedType = TypeInfo(describing: typeAddress.load(as: Any.Type.self))
309-
let selfType = TypeInfo(describing: Self.self)
310-
guard callerExpectedType == selfType else {
333+
guard typeAddress.load(as: Any.Type.self) == Record.self else {
311334
return false
312335
}
313336
#endif
@@ -320,15 +343,15 @@ extension ExitTest: DiscoverableAsTestContent {
320343

321344
// Wrap the body function in a thunk that decodes any captured state and
322345
// passes it along.
323-
let body: @Sendable (inout Self) async throws -> Void = { exitTest in
346+
let body: ExitTest.Body = { exitTest in
324347
let values: (repeat each T) = try exitTest.capturedValues.takeCapturedValues()
325348
try await body(repeat each values)
326349
}
327350

328-
// Construct and return the instance.
329-
var exitTest = Self(id: id, body: body)
330-
exitTest.capturedValues = Array(repeat (each T).self)
331-
outValue.initializeMemory(as: Self.self, to: exitTest)
351+
// Construct and return the record.
352+
var record = Record(id: id, body: body)
353+
record.capturedValues = Array(repeat (each T).self)
354+
outValue.initializeMemory(as: Record.self, to: record)
332355
return true
333356
}
334357
}
@@ -343,16 +366,16 @@ extension ExitTest {
343366
/// - Returns: The specified exit test function, or `nil` if no such exit test
344367
/// could be found.
345368
public static func find(identifiedBy id: ExitTest.ID) -> Self? {
346-
for record in Self.allTestContentRecords() {
347-
if let exitTest = record.load(withHint: id) {
369+
for record in Record.allTestContentRecords() {
370+
if let exitTest = record.load(withHint: id)?.makeExitTest() {
348371
return exitTest
349372
}
350373
}
351374

352375
#if !SWT_NO_LEGACY_TEST_DISCOVERY
353376
// Call the legacy lookup function that discovers tests embedded in types.
354-
for record in Self.allTypeMetadataBasedTestContentRecords() {
355-
if let exitTest = record.load(withHint: id) {
377+
for record in Record.allTypeMetadataBasedTestContentRecords() {
378+
if let exitTest = record.load(withHint: id)?.makeExitTest() {
356379
return exitTest
357380
}
358381
}

Sources/_TestDiscovery/TestContentRecord.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
5252
/// ([swift-#79667](https://github.com/swiftlang/swift/issues/79667))
5353
fileprivate static func validateMemoryLayout() {
5454
precondition(MemoryLayout<TestContentContext>.stride == MemoryLayout<UInt>.stride, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have the same stride as 'UInt'.")
55-
precondition(MemoryLayout<TestContentContext>.alignment == MemoryLayout<UInt>.alignment, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have the same alignment as 'UInt'.")
55+
precondition(MemoryLayout<TestContentContext>.alignment <= MemoryLayout<UInt>.alignment, "'\(self).TestContentContext' aka '\(TestContentContext.self)' must have an alignment less than or equal to that of 'UInt'.")
5656
}
5757
}
5858

Tests/TestingTests/DiscoveryTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct DiscoveryTests {
5959
#endif
6060

6161
#if !SWT_NO_DYNAMIC_LINKING && hasFeature(SymbolLinkageMarkers)
62-
struct MyTestContent: Testing.DiscoverableAsTestContent {
62+
struct MyTestContent: DiscoverableAsTestContent {
6363
typealias TestContentAccessorHint = UInt32
6464

6565
var value: UInt32

0 commit comments

Comments
 (0)