Skip to content

Commit 4dd5548

Browse files
authored
Add a new _TestDiscovery library/target. (#981)
This PR factors out our test discovery logic into a separate module that can be imported and linked to without needing to build all of Swift Syntax and Swift Testing. This allows test library developers to start experimenting with using the new (still experimental) test content section without needing to link to a package copy of Swift Testing. Resolves rdar://145694068. ### 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 ade64fd commit 4dd5548

17 files changed

+610
-302
lines changed

Documentation/ABI/TestContent.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ Third-party test content should set the `kind` field to a unique value only used
200200
by that tool, or used by that tool in collaboration with other compatible tools.
201201
At runtime, Swift Testing ignores test content records with unrecognized `kind`
202202
values. To reserve a new unique `kind` value, open a [GitHub issue](https://github.com/swiftlang/swift-testing/issues/new/choose)
203-
against Swift Testing.
203+
against Swift Testing. The value you reserve does not need to be representable
204+
as a [FourCC](https://en.wikipedia.org/wiki/FourCC) value, but it can be helpful
205+
for debugging purposes.
204206

205207
The layout of third-party test content records must be compatible with that of
206208
`TestContentRecord` as specified above. Third-party tools are ultimately
@@ -213,3 +215,78 @@ TODO: elaborate further, give examples
213215
TODO: standardize a mechanism for third parties to produce `Test` instances
214216
since we don't have a public initializer for the `Test` type.
215217
-->
218+
219+
## Discovering previously-emitted test content
220+
221+
<!--
222+
TODO: add more detail here about how to set up a package
223+
-->
224+
225+
To add test content discovery support to your package, add a dependency on the
226+
`_TestDiscovery` module in the `swift-testing` package (not the copy of Swift
227+
Testing included with the Swift toolchain or Xcode), then import the module with
228+
SPI enabled:
229+
230+
```swift
231+
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) import _TestDiscovery
232+
```
233+
234+
> [!IMPORTANT]
235+
> Don't add a dependency on the `swift-testing` package's `Testing` module. If
236+
> you add a dependency on this module, it will cause you to build and link Swift
237+
> Testing every time you build your package. You only need the `_TestDiscovery`
238+
> module in order to discover your own test content types.
239+
240+
After importing `_TestDiscovery`, find the type in your module that should be
241+
discoverable at runtime and add conformance to the `DiscoverableAsTestContent`
242+
protocol:
243+
244+
```swift
245+
extension FoodTruckDiagnostic: DiscoverableAsTestContent {
246+
static var testContentKind: UInt32 { /* Your `kind` value here. */ }
247+
}
248+
```
249+
250+
This type does not need to be publicly visible. However, if the values produced
251+
by your accessor functions are members of a public type, you may be able to
252+
simplify your code by using the same type.
253+
254+
If you have defined a custom `context` type other than `UInt`, you can specify
255+
it here by setting the associated `TestContentContext` type. If you have defined
256+
a custom `hint` type for your accessor functions, you can set
257+
`TestContentAccessorHint`:
258+
259+
```swift
260+
extension FoodTruckDiagnostic: DiscoverableAsTestContent {
261+
static var testContentKind: UInt32 { /* Your `kind` value here. */ }
262+
263+
typealias TestContentContext = UnsafePointer<FoodTruck.Name>
264+
typealias TestContentAccessorHint = String
265+
}
266+
```
267+
268+
If you customize `TestContentContext`, be aware that the type you specify must
269+
have the same stride and alignment as `UInt`.
270+
271+
When you are done configuring your type's protocol conformance, you can then
272+
enumerate all test content records matching it as instances of
273+
`TestContentRecord`.
274+
275+
You can use the `context` property to access the `context` field of the record
276+
(as emitted into the test content section). The testing library will
277+
automatically cast the value of the field to an instance of `TestContentContext`
278+
for you.
279+
280+
If you find a record you wish to resolve to an instance of your conforming type,
281+
call its `load()` function. `load()` calls the record's accessor function and,
282+
if you have set a hint type, lets you pass an optional instance of that type:
283+
284+
```swift
285+
for diagnosticRecord in FoodTruckDiagnostic.allTestContentRecords() {
286+
if diagnosticRecord.context.pointee == .briansBranMuffins {
287+
if let diagnostic = diagnosticRecord.load(withHint: "...") {
288+
diagnostic.run()
289+
}
290+
}
291+
}
292+
```

Package.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,36 @@ let package = Package(
3232
.visionOS(.v1),
3333
],
3434

35-
products: [
36-
{
35+
products: {
36+
var result = [Product]()
37+
3738
#if os(Windows)
39+
result.append(
3840
.library(
3941
name: "Testing",
4042
type: .dynamic, // needed so Windows exports ABI entry point symbols
4143
targets: ["Testing"]
4244
)
45+
)
4346
#else
47+
result.append(
4448
.library(
4549
name: "Testing",
4650
targets: ["Testing"]
4751
)
52+
)
4853
#endif
49-
}()
50-
],
54+
55+
result.append(
56+
.library(
57+
name: "_TestDiscovery",
58+
type: .static,
59+
targets: ["_TestDiscovery"]
60+
)
61+
)
62+
63+
return result
64+
}(),
5165

5266
dependencies: [
5367
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "601.0.0-latest"),
@@ -57,6 +71,7 @@ let package = Package(
5771
.target(
5872
name: "Testing",
5973
dependencies: [
74+
"_TestDiscovery",
6075
"_TestingInternals",
6176
"TestingMacros",
6277
],
@@ -108,13 +123,20 @@ let package = Package(
108123
}()
109124
),
110125

111-
// "Support" targets: These contain C family code and are used exclusively
112-
// by other targets above, not directly included in product libraries.
126+
// "Support" targets: These targets are not meant to be used directly by
127+
// test authors.
113128
.target(
114129
name: "_TestingInternals",
115130
exclude: ["CMakeLists.txt"],
116131
cxxSettings: .packageSettings
117132
),
133+
.target(
134+
name: "_TestDiscovery",
135+
dependencies: ["_TestingInternals",],
136+
exclude: ["CMakeLists.txt"],
137+
cxxSettings: .packageSettings,
138+
swiftSettings: .packageSettings
139+
),
118140

119141
// Cross-import overlays (not supported by Swift Package Manager)
120142
.target(

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ endif()
103103

104104
include(AvailabilityDefinitions)
105105
include(CompilerSettings)
106+
add_subdirectory(_TestDiscovery)
106107
add_subdirectory(_TestingInternals)
107108
add_subdirectory(Overlays)
108109
add_subdirectory(Testing)

Sources/Testing/CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ add_library(Testing
8383
Support/Locked.swift
8484
Support/Locked+Platform.swift
8585
Support/Versions.swift
86-
Discovery.swift
87-
Discovery+Platform.swift
86+
Discovery+Macro.swift
8887
Test.ID.Selection.swift
8988
Test.ID.swift
9089
Test.swift
@@ -107,6 +106,7 @@ add_library(Testing
107106
Traits/TimeLimitTrait.swift
108107
Traits/Trait.swift)
109108
target_link_libraries(Testing PRIVATE
109+
_TestDiscovery
110110
_TestingInternals)
111111
if(NOT APPLE)
112112
if(NOT CMAKE_SYSTEM_NAME STREQUAL WASI)
@@ -121,8 +121,9 @@ if(NOT APPLE)
121121
endif()
122122
if(NOT BUILD_SHARED_LIBS)
123123
# When building a static library, tell clients to autolink the internal
124-
# library.
124+
# libraries.
125125
target_compile_options(Testing PRIVATE
126+
"SHELL:-Xfrontend -public-autolink-library -Xfrontend _TestDiscovery"
126127
"SHELL:-Xfrontend -public-autolink-library -Xfrontend _TestingInternals")
127128
endif()
128129
add_dependencies(Testing

Sources/Testing/Discovery+Macro.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
@_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+
20+
/// The type of the accessor function used to access a test content record.
21+
///
22+
/// The signature of this function type must match that of the corresponding
23+
/// type in the `_TestDiscovery` module. For more information, see
24+
/// `ABI/TestContent.md`.
25+
///
26+
/// - Warning: This type is used to implement the `@Test` macro. Do not use it
27+
/// directly.
28+
public typealias __TestContentRecordAccessor = @convention(c) (
29+
_ outValue: UnsafeMutableRawPointer,
30+
_ type: UnsafeRawPointer,
31+
_ hint: UnsafeRawPointer?
32+
) -> CBool
33+
34+
/// The content of a test content record.
35+
///
36+
/// The layout of this type must match that of the corresponding type
37+
/// in the `_TestDiscovery` module. For more information, see
38+
/// `ABI/TestContent.md`.
39+
///
40+
/// - Warning: This type is used to implement the `@Test` macro. Do not use it
41+
/// directly.
42+
public typealias __TestContentRecord = (
43+
kind: UInt32,
44+
reserved1: UInt32,
45+
accessor: __TestContentRecordAccessor?,
46+
context: UInt,
47+
reserved2: UInt
48+
)

0 commit comments

Comments
 (0)