Skip to content

Commit c8998c7

Browse files
authored
Discover test content stored in the test content section of loaded images. (#893)
This PR implements discovery, _but not emission_, of test content that has been added to a loaded image's test content metadata section at compile time. Loading this data from a dedicated section has several benefits over our current model, which involves walking Swift's type metadata table looking for types that conform to a protocol: 1. We don't need to define that protocol as public API in Swift Testing, 1. We don't need to emit type metadata (much larger than what we really need) for every test function, 1. We don't need to duplicate a large chunk of the Swift ABI sources in order to walk the type metadata table correctly, and 1. Almost all the new code is written in Swift, whereas the code it is intended to replace could not be fully represented in Swift and needed to be written in C++. The change also opens up the possibility of supporting generic types in the future because we can emit metadata without needing to emit a nested type (which is not always valid in a generic context.) That's a "future direction" and not covered by this PR specifically. I've defined a layout for entries in the new `swift5_tests` section that should be flexible enough for us in the short-to-medium term and which lets us define additional arbitrary test content record types. The layout of this section is covered in depth in the new [TestContent.md](Documentation/ABI/TestContent.md) article. This PR does **not** include the code necessary to _emit_ test content records into images at compile time. That part of the change is covered by #880 and requires a new language feature to control which section data is emitted to. An experimental version of that language feature is currently available under the `"SymbolLinkageMarkers"` label. Because there is no test content in the test content section yet, this PR does not remove the "legacy" codepaths that discover tests in the type metadata section. > [!NOTE] > This change is experimental. ### See Also #880 #735 swiftlang/swift#76698 swiftlang/swift#78411 ### 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 65b7ef2 commit c8998c7

File tree

15 files changed

+978
-86
lines changed

15 files changed

+978
-86
lines changed

Documentation/ABI/TestContent.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Runtime-discoverable test content
2+
3+
<!--
4+
This source file is part of the Swift.org open source project
5+
6+
Copyright (c) 2024 Apple Inc. and the Swift project authors
7+
Licensed under Apache License v2.0 with Runtime Library Exception
8+
9+
See https://swift.org/LICENSE.txt for license information
10+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
11+
-->
12+
13+
This document describes the format and location of test content that the testing
14+
library emits at compile time and can discover at runtime.
15+
16+
> [!WARNING]
17+
> The content of this document is subject to change pending efforts to define a
18+
> Swift-wide standard mechanism for runtime metadata emission and discovery.
19+
> Treat the information in this document as experimental.
20+
21+
## Basic format
22+
23+
Swift Testing stores test content records in a dedicated platform-specific
24+
section in built test products:
25+
26+
| Platform | Binary Format | Section Name |
27+
|-|:-:|-|
28+
| macOS, iOS, watchOS, tvOS, visionOS | Mach-O | `__DATA_CONST,__swift5_tests` |
29+
| Linux, FreeBSD, OpenBSD, Android | ELF | `swift5_tests` |
30+
| WASI | WebAssembly | `swift5_tests` |
31+
| Windows | PE/COFF | `.sw5test$B`[^windowsPadding] |
32+
33+
[^windowsPadding]: On Windows, the Swift compiler [emits](https://github.com/swiftlang/swift/blob/main/stdlib/public/runtime/SwiftRT-COFF.cpp)
34+
leading and trailing padding into this section, both zeroed and of size
35+
`MemoryLayout<UInt>.stride`. Code that walks this section must skip over this
36+
padding.
37+
38+
### Record layout
39+
40+
Regardless of platform, all test content records created and discoverable by the
41+
testing library have the following layout:
42+
43+
```swift
44+
typealias TestContentRecord = (
45+
kind: UInt32,
46+
reserved1: UInt32,
47+
accessor: (@convention(c) (_ outValue: UnsafeMutableRawPointer, _ hint: UnsafeRawPointer?) -> CBool)?,
48+
context: UInt,
49+
reserved2: UInt
50+
)
51+
```
52+
53+
This type has natural size, stride, and alignment. Its fields are native-endian.
54+
If needed, this type can be represented in C as a structure:
55+
56+
```c
57+
struct SWTTestContentRecord {
58+
uint32_t kind;
59+
uint32_t reserved1;
60+
bool (* _Nullable accessor)(void *outValue, const void *_Null_unspecified hint);
61+
uintptr_t context;
62+
uintptr_t reserved2;
63+
};
64+
```
65+
66+
### Record content
67+
68+
#### The kind field
69+
70+
Each record's _kind_ determines how the record will be interpreted at runtime. A
71+
record's kind is a 32-bit unsigned value. The following kinds are defined:
72+
73+
| As Hexadecimal | As [FourCC](https://en.wikipedia.org/wiki/FourCC) | Interpretation |
74+
|-:|:-:|-|
75+
| `0x00000000` | &ndash; | Reserved (**do not use**) |
76+
| `0x74657374` | `'test'` | Test or suite declaration |
77+
| `0x65786974` | `'exit'` | Exit test |
78+
79+
<!-- When adding cases to this enumeration, be sure to also update the
80+
corresponding enumeration in TestContentGeneration.swift. -->
81+
82+
#### The accessor field
83+
84+
The function `accessor` is a C function. When called, it initializes the memory
85+
at its argument `outValue` to an instance of some Swift type and returns `true`,
86+
or returns `false` if it could not generate the relevant content. On successful
87+
return, the caller is responsible for deinitializing the memory at `outValue`
88+
when done with it.
89+
90+
If `accessor` is `nil`, the test content record is ignored. The testing library
91+
may, in the future, define record kinds that do not provide an accessor function
92+
(that is, they represent pure compile-time information only.)
93+
94+
The second argument to this function, `hint`, is an optional input that can be
95+
passed to help the accessor function determine if its corresponding test content
96+
record matches what the caller is looking for. If the caller passes `nil` as the
97+
`hint` argument, the accessor behaves as if it matched (that is, no additional
98+
filtering is performed.)
99+
100+
The concrete Swift type of the value written to `outValue` and the value pointed
101+
to by `hint` depend on the kind of record:
102+
103+
- For test or suite declarations (kind `0x74657374`), the accessor produces an
104+
asynchronous Swift function that returns an instance of `Test`:
105+
106+
```swift
107+
@Sendable () async -> Test
108+
```
109+
110+
This signature is not the signature of `accessor`, but of the Swift function
111+
reference it writes to `outValue`. This level of indirection is necessary
112+
because loading a test or suite declaration is an asynchronous operation, but
113+
C functions cannot be `async`.
114+
115+
Test content records of this kind do not specify a type for `hint`. Always
116+
pass `nil`.
117+
118+
- For exit test declarations (kind `0x65786974`), the accessor produces a
119+
structure describing the exit test (of type `__ExitTest`.)
120+
121+
Test content records of this kind accept a `hint` of type `SourceLocation`.
122+
They only produce a result if they represent an exit test declared at the same
123+
source location (or if the hint is `nil`.)
124+
125+
#### The context field
126+
127+
This field can be used by test content to store additional context for a test
128+
content record that needs to be made available before the accessor is called:
129+
130+
- For test or suite declarations (kind `0x74657374`), this field contains a bit
131+
mask with the following flags currently defined:
132+
133+
| Bit | Value | Description |
134+
|-:|-:|-|
135+
| `1 << 0` | `1` | This record contains a suite declaration |
136+
| `1 << 1` | `2` | This record contains a parameterized test function declaration |
137+
138+
Other bits are reserved for future use and must be set to `0`.
139+
140+
- For exit test declarations (kind `0x65786974`), this field is reserved for
141+
future use and must be set to `0`.
142+
143+
#### The reserved1 and reserved2 fields
144+
145+
These fields are reserved for future use. Always set them to `0`.
146+
147+
## Third-party test content
148+
149+
Testing tools may make use of the same storage and discovery mechanisms by
150+
emitting their own test content records into the test record content section.
151+
152+
Third-party test content should set the `kind` field to a unique value only used
153+
by that tool, or used by that tool in collaboration with other compatible tools.
154+
At runtime, Swift Testing ignores test content records with unrecognized `kind`
155+
values. To reserve a new unique `kind` value, open a [GitHub issue](https://github.com/swiftlang/swift-testing/issues/new/choose)
156+
against Swift Testing.
157+
158+
The layout of third-party test content records must be compatible with that of
159+
`TestContentRecord` as specified above. Third-party tools are ultimately
160+
responsible for ensuring the values they emit into the test content section are
161+
correctly aligned and have sufficient padding; failure to do so may render
162+
downstream test code unusable.
163+
164+
<!--
165+
TODO: elaborate further, give examples
166+
TODO: standardize a mechanism for third parties to produce `Test` instances
167+
since we don't have a public initializer for the `Test` type.
168+
-->

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ let package = Package(
6767
"_Testing_CoreGraphics",
6868
"_Testing_Foundation",
6969
],
70-
swiftSettings: .packageSettings
70+
swiftSettings: .packageSettings + [
71+
// For testing test content section discovery only
72+
.enableExperimentalFeature("SymbolLinkageMarkers"),
73+
]
7174
),
7275

7376
.macro(

Sources/Testing/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ add_library(Testing
8181
Support/Locked.swift
8282
Support/SystemError.swift
8383
Support/Versions.swift
84+
Discovery.swift
85+
Discovery+Platform.swift
8486
Test.ID.Selection.swift
8587
Test.ID.swift
8688
Test.swift
8789
Test+Discovery.swift
90+
Test+Discovery+Legacy.swift
8891
Test+Macro.swift
8992
Traits/Bug.swift
9093
Traits/Comment.swift

0 commit comments

Comments
 (0)