Skip to content

Commit b3d3fd7

Browse files
authored
Add a dedicated TestContentKind type to _TestDiscovery. (#1019)
This PR adds a `TestContentKind` type to `_TestDiscovery` that represents the `kind` field of a record and allows initialization from a string literal (i.e. a FourCC) or an integer literal: ```swift extension MyType: DiscoverableAsTestContent { static var testContentKind: TestContentKind { "moof" } } ``` At this time, it's not possible to use this type directly in test content records emitted by our macros because the type cannot be made public, but it allows for types conforming to `DiscoverableAsTestContent` to be a bit more expressive. Test library authors who are experimenting with `_TestDiscovery` may opt to export the symbols from `_TestDiscovery` and thus could use the type directly in their emitted records (but keep in mind this module is still experimental!) The new type is `@frozen` and mostly `@inlinable` because its layout is set in stone by virtue of the test content record `kind` field having a fixed layout. Resolves rdar://146855125. ### 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.
1 parent 0018a42 commit b3d3fd7

File tree

10 files changed

+287
-133
lines changed

10 files changed

+287
-133
lines changed

Documentation/ABI/TestContent.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ protocol:
243243

244244
```swift
245245
extension FoodTruckDiagnostic: DiscoverableAsTestContent {
246-
static var testContentKind: UInt32 { /* Your `kind` value here. */ }
246+
static var testContentKind: TestContentKind { /* Your `kind` value here. */ }
247247
}
248248
```
249249

@@ -258,7 +258,7 @@ a custom `hint` type for your accessor functions, you can set
258258

259259
```swift
260260
extension FoodTruckDiagnostic: DiscoverableAsTestContent {
261-
static var testContentKind: UInt32 { /* Your `kind` value here. */ }
261+
static var testContentKind: TestContentKind { /* Your `kind` value here. */ }
262262

263263
typealias TestContentContext = UnsafePointer<FoodTruck.Name>
264264
typealias TestContentAccessorHint = String

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,11 @@ extension ExitTest {
239239
// MARK: - Discovery
240240

241241
extension ExitTest: DiscoverableAsTestContent {
242-
static var testContentKind: UInt32 {
243-
0x65786974
242+
fileprivate static var testContentKind: TestContentKind {
243+
"exit"
244244
}
245245

246-
typealias TestContentAccessorHint = ID
246+
fileprivate typealias TestContentAccessorHint = ID
247247

248248
/// Store the exit test into the given memory.
249249
///

Sources/Testing/Test+Discovery.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ extension Test {
2020
/// instances of ``Test``, but functions are non-nominal types and cannot
2121
/// directly conform to protocols.
2222
fileprivate struct Generator: DiscoverableAsTestContent, RawRepresentable {
23-
static var testContentKind: UInt32 {
24-
0x74657374
23+
static var testContentKind: TestContentKind {
24+
"test"
2525
}
2626

2727
var rawValue: @Sendable () async -> Test

Sources/TestingMacros/Support/TestContentGeneration.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import SwiftSyntaxMacros
1717
///
1818
/// When adding cases to this enumeration, be sure to also update the
1919
/// corresponding enumeration in TestContent.md.
20+
///
21+
/// - Bug: This type should be imported directly from `_TestDiscovery` instead
22+
/// of being redefined (differently) here.
2023
enum TestContentKind: UInt32 {
2124
/// A test or suite declaration.
2225
case testDeclaration = 0x74657374

Sources/_TestDiscovery/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(_TestDiscovery STATIC
1010
Additions/WinSDKAdditions.swift
1111
DiscoverableAsTestContent.swift
1212
SectionBounds.swift
13+
TestContentKind.swift
1314
TestContentRecord.swift)
1415

1516
target_link_libraries(_TestDiscovery PRIVATE

Sources/_TestDiscovery/DiscoverableAsTestContent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public protocol DiscoverableAsTestContent: Sendable, ~Copyable {
2222
///
2323
/// The value of this property is reserved for each test content type. See
2424
/// `ABI/TestContent.md` for a list of values and corresponding types.
25-
static var testContentKind: UInt32 { get }
25+
static var testContentKind: TestContentKind { get }
2626

2727
/// The type of the `context` field in test content records associated with
2828
/// this type.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
private import _TestingInternals
12+
13+
/// A type representing a test content record's `kind` field.
14+
///
15+
/// Test content kinds are 32-bit unsigned integers and are stored as such when
16+
/// test content records are emitted at compile time.
17+
///
18+
/// This type lets you represent a kind value as an integer literal or as a
19+
/// string literal in Swift code. In particular, when adding a conformance to
20+
/// the ``DiscoverableAsTestContent`` protocol, the protocol's
21+
/// ``DiscoverableAsTestContent/testContentKind`` property must be an instance
22+
/// of this type.
23+
///
24+
/// For a list of reserved values, or to reserve a value for your own use, see
25+
/// `ABI/TestContent.md`.
26+
///
27+
/// @Comment {
28+
/// This type is `@frozen` and most of its members are `@inlinable` because it
29+
/// represents the underlying `kind` field which has a fixed layout. In the
30+
/// future, we may want to use this type in test content records, but that
31+
/// will require the type be publicly visible and that `@const` is implemented
32+
/// in the compiler.
33+
/// }
34+
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
35+
@frozen public struct TestContentKind: Sendable, RawRepresentable {
36+
public var rawValue: UInt32
37+
38+
@inlinable public init(rawValue: UInt32) {
39+
self.rawValue = rawValue
40+
}
41+
}
42+
43+
// MARK: - Equatable, Hashable
44+
45+
extension TestContentKind: Equatable, Hashable {
46+
@inlinable public static func ==(lhs: Self, rhs: Self) -> Bool {
47+
lhs.rawValue == rhs.rawValue
48+
}
49+
50+
@inlinable public func hash(into hasher: inout Hasher) {
51+
hasher.combine(rawValue)
52+
}
53+
}
54+
55+
// MARK: - Codable
56+
57+
extension TestContentKind: Codable {}
58+
59+
// MARK: - ExpressibleByStringLiteral, ExpressibleByIntegerLiteral
60+
61+
extension TestContentKind: ExpressibleByStringLiteral, ExpressibleByIntegerLiteral {
62+
@inlinable public init(stringLiteral stringValue: StaticString) {
63+
precondition(stringValue.utf8CodeUnitCount == MemoryLayout<UInt32>.stride, #""\#(stringValue)".utf8CodeUnitCount = \#(stringValue.utf8CodeUnitCount), expected \#(MemoryLayout<UInt32>.stride)"#)
64+
let rawValue = stringValue.withUTF8Buffer { stringValue in
65+
let bigEndian = UnsafeRawBufferPointer(stringValue).loadUnaligned(as: UInt32.self)
66+
return UInt32(bigEndian: bigEndian)
67+
}
68+
self.init(rawValue: rawValue)
69+
}
70+
71+
@inlinable public init(integerLiteral: UInt32) {
72+
self.init(rawValue: integerLiteral)
73+
}
74+
}
75+
76+
// MARK: - CustomStringConvertible
77+
78+
extension TestContentKind: CustomStringConvertible {
79+
/// This test content type's kind value as an ASCII string (of the form
80+
/// `"abcd"`) if it looks like it might be a [FourCC](https://en.wikipedia.org/wiki/FourCC)
81+
/// value, or `nil` if not.
82+
private var _fourCCValue: String? {
83+
withUnsafeBytes(of: rawValue.bigEndian) { bytes in
84+
if bytes.allSatisfy(Unicode.ASCII.isASCII) {
85+
let characters = String(decoding: bytes, as: Unicode.ASCII.self)
86+
let allAlphanumeric = characters.allSatisfy { $0.isLetter || $0.isWholeNumber }
87+
if allAlphanumeric {
88+
return characters
89+
}
90+
}
91+
return nil
92+
}
93+
}
94+
95+
public var description: String {
96+
let hexValue = "0x" + String(rawValue, radix: 16)
97+
if let fourCCValue = _fourCCValue {
98+
return "'\(fourCCValue)' (\(hexValue))"
99+
}
100+
return hexValue
101+
}
102+
}

Sources/_TestDiscovery/TestContentRecord.swift

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,22 @@ public struct TestContentRecord<T> where T: DiscoverableAsTestContent & ~Copyabl
108108
}
109109

110110
fileprivate init(imageAddress: UnsafeRawPointer?, recordAddress: UnsafePointer<_TestContentRecord>) {
111-
precondition(recordAddress.pointee.kind == T.testContentKind)
111+
precondition(recordAddress.pointee.kind == T.testContentKind.rawValue)
112112
self.imageAddress = imageAddress
113113
self._recordStorage = .atAddress(recordAddress)
114114
}
115115

116116
fileprivate init(imageAddress: UnsafeRawPointer?, record: _TestContentRecord) {
117-
precondition(record.kind == T.testContentKind)
117+
precondition(record.kind == T.testContentKind.rawValue)
118118
self.imageAddress = imageAddress
119119
self._recordStorage = .inline(record)
120120
}
121121

122+
/// The kind of this test content record.
123+
public var kind: TestContentKind {
124+
TestContentKind(rawValue: _record.kind)
125+
}
126+
122127
/// The type of the ``context`` property.
123128
public typealias Context = T.TestContentContext
124129

@@ -181,28 +186,8 @@ extension TestContentRecord: Sendable where Context: Sendable {}
181186
// MARK: - CustomStringConvertible
182187

183188
extension TestContentRecord: CustomStringConvertible {
184-
/// This test content type's kind value as an ASCII string (of the form
185-
/// `"abcd"`) if it looks like it might be a [FourCC](https://en.wikipedia.org/wiki/FourCC)
186-
/// value, or `nil` if not.
187-
private static var _asciiKind: String? {
188-
return withUnsafeBytes(of: T.testContentKind.bigEndian) { bytes in
189-
if bytes.allSatisfy(Unicode.ASCII.isASCII) {
190-
let characters = String(decoding: bytes, as: Unicode.ASCII.self)
191-
let allAlphanumeric = characters.allSatisfy { $0.isLetter || $0.isWholeNumber }
192-
if allAlphanumeric {
193-
return characters
194-
}
195-
}
196-
return nil
197-
}
198-
}
199-
200189
public var description: String {
201190
let typeName = String(describing: Self.self)
202-
let hexKind = "0x" + String(T.testContentKind, radix: 16)
203-
let kind = Self._asciiKind.map { asciiKind in
204-
"'\(asciiKind)' (\(hexKind))"
205-
} ?? hexKind
206191
switch _recordStorage {
207192
case let .atAddress(recordAddress):
208193
let recordAddress = imageAddress.map { imageAddress in
@@ -232,11 +217,14 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
232217
/// }
233218
public static func allTestContentRecords() -> AnySequence<TestContentRecord<Self>> {
234219
validateMemoryLayout()
220+
221+
let kind = testContentKind.rawValue
222+
235223
let result = SectionBounds.all(.testContent).lazy.flatMap { sb in
236224
sb.buffer.withMemoryRebound(to: _TestContentRecord.self) { records in
237225
(0 ..< records.count).lazy
238226
.map { (records.baseAddress! + $0) as UnsafePointer<_TestContentRecord> }
239-
.filter { $0.pointee.kind == testContentKind }
227+
.filter { $0.pointee.kind == kind }
240228
.map { TestContentRecord<Self>(imageAddress: sb.imageAddress, recordAddress: $0) }
241229
}
242230
}
@@ -275,7 +263,7 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
275263
validateMemoryLayout()
276264

277265
let typeNameHint = _testContentTypeNameHint
278-
let kind = testContentKind
266+
let kind = testContentKind.rawValue
279267
let loader: @Sendable (Any.Type) -> _TestContentRecord? = { type in
280268
withUnsafeTemporaryAllocation(of: _TestContentRecord.self, capacity: 1) { buffer in
281269
// Load the record from the container type.

0 commit comments

Comments
 (0)