Skip to content

Commit b946b36

Browse files
authored
Add a document describing how to use the ABI-namespaced types. (#1631)
This PR adds a document that starts to outline how to use `ABI.Version`, `ABI.Record`, etc. for the benefit of tools authors. Since this document does not describe our API surface, it's not in the DocC bundle. View [here](https://github.com/swiftlang/swift-testing/blob/jgrynspan/abi.record-docs/Documentation/ABI/EventStreamHandling.md). ### 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 b5fa206 commit b946b36

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Working with the JSON event stream in Swift
2+
3+
<!--
4+
This source file is part of the Swift.org open source project
5+
6+
Copyright (c) 2026 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 outlines the `ABI.Record` and `ABI.Encoded*` Swift types and how
14+
to use them.
15+
16+
> [!NOTE]
17+
> These types are marked `@_spi(ForToolsIntegrationOnly)`, so they are only
18+
> available when you build Swift Testing from source.
19+
20+
## The `ABI` namespace
21+
22+
Swift Testing uses a Swift enumeration type named `ABI` to namespace types,
23+
constants, etc. that are related to Swift Testing's ABI-stable and -semi-stable
24+
interface but which are not exposed to test authors as API. These symbols are
25+
not meant to be used by test authors in typical workflows, but are useful for
26+
_tools_ authors who wish to integrate their tools with Swift Testing.
27+
28+
### The `ABI.Version` and `ABI.VersionNumber` types
29+
30+
Swift Testing uses the `ABI.Version` Swift protocol to define a particular ABI
31+
version, which as of Swift 6.3 is directly tied to a specific Swift toolchain
32+
release. We declare types under the `ABI` namespace that conform to this
33+
protocol. For example, `ABI.v6_3` is a type that conforms to `ABI.Version` and
34+
represents the Swift Testing ABI used in the Swift 6.3 toolchain.
35+
36+
> [!NOTE]
37+
> Unless otherwise stated, Swift toolchain patch releases share an ABI version
38+
> with their previous minor Swift toolchain release. For instance, Swift Testing
39+
> in the Swift 6.3.1 toolchain uses the same ABI as does Swift Testing in the
40+
> Swift 6.3.0 toolchain.
41+
42+
Each type that conforms to `ABI.Version` has a static `versionNumber` property
43+
of type `ABI.VersionNumber`, which is a `Comparable` and `Codable` value that
44+
represents that version. You can use this property to, for example, check if an
45+
`ABI.Version`-conforming type is newer than another:
46+
47+
```swift
48+
let abi: (some ABI.Version).Type
49+
let isNewerThan6_3 = abi.versionNumber > ABI.v6_3.versionNumber
50+
```
51+
52+
Swift Testing also defines `ABI.ExperimentalVersion` to represent experimental
53+
(and **unsupported!**) ABI variants.
54+
55+
## The JSON event stream
56+
57+
The JSON event stream Swift Testing produces at runtime is defined in
58+
[JSON.md](JSON.md). An instance of the Swift type `JSON.Record` represents an
59+
`<output-record>` value as defined in that file. `JSON.Record` itself is generic
60+
over some type conforming to `ABI.Version`; that type provides `JSON.Record`
61+
with the information it needs to correctly encode and decode JSON objects.
62+
63+
> [!NOTE]
64+
> If you are writing your tools using a language other than Swift, you can
65+
> decode the JSON in this stream using your language's JSON decoder and access
66+
> its fields directly. Using languages other than Swift is beyond the scope of
67+
> this document.
68+
69+
### Decoding a JSON event record
70+
71+
To decode an instance of `ABI.Record`, you'll first need to know what ABI
72+
version it was encoded with. We sometimes refer to this version as the JSON
73+
event stream's _schema version_.
74+
75+
To get the appropriate `ABI.Version`-conforming type for a known version number,
76+
you can use `ABI.version(forVersionNumber:)`. If you have a JSON object from the
77+
JSON event stream and do not know what version number to use to decode it, you
78+
can use `ABI.VersionNumber.init(fromRecordJSON:)` to find out.
79+
80+
You can use the following template to decode a JSON object from the JSON event
81+
stream:
82+
83+
```swift
84+
func handleJSONRecord(_ json: Data) throws {
85+
// Get the ABI version number from the JSON object. Note that this initializer
86+
// takes an UnsafeRawBufferPointer rather than a Data.
87+
let versionNumber = try json.withUnsafeBytes { try ABI.VersionNumber(fromRecordJSON: $0) }
88+
89+
guard let abi = ABI.version(forVersionNumber: versionNumber) else {
90+
// There is no Swift Testing ABI version associated with the version number
91+
// provided. Either the JSON object is malformed or the version number is
92+
// too new for the current version of Swift Testing to support.
93+
throw ...
94+
}
95+
try handleJSONRecord(json, using: abi)
96+
}
97+
98+
func handleJSONRecord<V: ABI.Version>(_ json: Data, using _: V.Type) throws {
99+
let record = try JSONDecoder().decode(ABI.Record<V>.self, from: json)
100+
switch record.kind {
101+
case let .test(test):
102+
// This record declares the existence of a test.
103+
try cacheTest(test)
104+
case let .event(event):
105+
// This record represents some event that has occurred.
106+
try handleEvent(event)
107+
}
108+
}
109+
```
110+
111+
The associated values `test` and `event` in the template above are instances of
112+
the Swift Testing types `ABI.EncodedTest` and `ABI.EncodedEvent`, respectively.
113+
These types represent the JSON-codable subset of information contained in Swift
114+
Testing's `Test` API type and `Event` SPI type. If needed, you can convert them
115+
back to instances of `Test` and `Event` using the `init?(decoding:)` initializer
116+
on either type:
117+
118+
```swift
119+
private let cachedTests = Mutex<[Test.ID: Test]>([:])
120+
121+
func cacheTest<V: ABI.Version>(_ test: ABI.EncodedTest<V>) throws {
122+
guard let test = Test(decoding: test) else {
123+
// Swift Testing could not recreate a copy of the original `Test` instance.
124+
throw ...
125+
}
126+
cachedTests.withLock { $0[test.id] = test }
127+
print("Discovered test '\(test.displayName ?? test.name)' at \(test.sourceLocation)")
128+
}
129+
```
130+
131+
> [!IMPORTANT]
132+
> When you convert an instance of `ABI.EncodedTest` to an instance of `Test` (or
133+
> `ABI.EncodedEvent` to `Event`, etc.), the conversion is lossy. Information
134+
> that was originally available in these values at runtime may not be available
135+
> in the copy you derive from the JSON event stream. In particular, you cannot
136+
> run a Swift Testing test function created in this manner as the body of the
137+
> test is not representable as JSON data.
138+
139+
<!-- TODO: document how to convert runtime Swift values to JSON objects (going the other direction) -->

Documentation/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ The [`ABI`](ABI/) directory contains documents related to Swift Testing's ABI:
6565
that is, parts of its interface that are intended to be stable over time and can
6666
be used without needing to write any code in Swift:
6767

68+
- [`ABI/EventStreamHandling.md`](ABI/EventStreamHandling.md) contains
69+
information about Swift Testing's SPI that you can use when working with the
70+
library's JSON event stream.
6871
- [`ABI/JSON.md`](ABI/JSON.md) contains Swift Testing's JSON specification that
6972
can be used by tools to interact with Swift Testing either directly or via the
7073
`swift test` command-line tool.

0 commit comments

Comments
 (0)