Working with the JSON event stream in Swift

March 18, 2026 ยท View on GitHub

This document outlines the ABI.Record and ABI.Encoded* Swift types and how to use them.

Note

These types are marked @_spi(ForToolsIntegrationOnly), so they are only available when you build Swift Testing from source.

The ABI namespace

Swift Testing uses a Swift enumeration type named ABI to namespace types, constants, etc. that are related to Swift Testing's ABI-stable and -semi-stable interface but which are not exposed to test authors as API. These symbols are not meant to be used by test authors in typical workflows, but are useful for tools authors who wish to integrate their tools with Swift Testing.

The ABI.Version and ABI.VersionNumber types

Swift Testing uses the ABI.Version Swift protocol to define a particular ABI version, which as of Swift 6.3 is directly tied to a specific Swift toolchain release. We declare types under the ABI namespace that conform to this protocol. For example, ABI.v6_3 is a type that conforms to ABI.Version and represents the Swift Testing ABI used in the Swift 6.3 toolchain.

Note

Unless otherwise stated, Swift toolchain patch releases share an ABI version with their previous minor Swift toolchain release. For instance, Swift Testing in the Swift 6.3.1 toolchain uses the same ABI as does Swift Testing in the Swift 6.3.0 toolchain.

Each type that conforms to ABI.Version has a static versionNumber property of type ABI.VersionNumber, which is a Comparable and Codable value that represents that version. You can use this property to, for example, check if an ABI.Version-conforming type is newer than another:

let abi: (some ABI.Version).Type 
let isNewerThan6_3 = abi.versionNumber > ABI.v6_3.versionNumber

Swift Testing also defines ABI.ExperimentalVersion to represent experimental (and unsupported!) ABI variants.

The JSON event stream

The JSON event stream Swift Testing produces at runtime is defined in JSON.md. An instance of the Swift type JSON.Record represents an <output-record> value as defined in that file. JSON.Record itself is generic over some type conforming to ABI.Version; that type provides JSON.Record with the information it needs to correctly encode and decode JSON objects.

Note

If you are writing your tools using a language other than Swift, you can decode the JSON in this stream using your language's JSON decoder and access its fields directly. Using languages other than Swift is beyond the scope of this document.

Decoding a JSON event record

To decode an instance of ABI.Record, you'll first need to know what ABI version it was encoded with. We sometimes refer to this version as the JSON event stream's schema version.

To get the appropriate ABI.Version-conforming type for a known version number, you can use ABI.version(forVersionNumber:). If you have a JSON object from the JSON event stream and do not know what version number to use to decode it, you can use ABI.VersionNumber.init(fromRecordJSON:) to find out.

You can use the following template to decode a JSON object from the JSON event stream:

func handleJSONRecord(_ json: Data) throws {
  // Get the ABI version number from the JSON object. Note that this initializer
  // takes an UnsafeRawBufferPointer rather than a Data.
  let versionNumber = try json.withUnsafeBytes { try ABI.VersionNumber(fromRecordJSON: \$0) }

  guard let abi = ABI.version(forVersionNumber: versionNumber) else {
    // There is no Swift Testing ABI version associated with the version number
    // provided. Either the JSON object is malformed or the version number is
    // too new for the current version of Swift Testing to support.
    throw ...
  }
  try handleJSONRecord(json, using: abi)
}

func handleJSONRecord<V: ABI.Version>(_ json: Data, using _: V.Type) throws {
  let record = try JSONDecoder().decode(ABI.Record<V>.self, from: json)
  switch record.kind {
  case let .test(test):
    // This record declares the existence of a test.
    try cacheTest(test)
  case let .event(event):
    // This record represents some event that has occurred.
    try handleEvent(event)
  }
}

The associated values test and event in the template above are instances of the Swift Testing types ABI.EncodedTest and ABI.EncodedEvent, respectively. These types represent the JSON-codable subset of information contained in Swift Testing's Test API type and Event SPI type. If needed, you can convert them back to instances of Test and Event using the init?(decoding:) initializer on either type:

private let cachedTests = Mutex<[Test.ID: Test]>([:])

func cacheTest<V: ABI.Version>(_ test: ABI.EncodedTest<V>) throws {
  guard let test = Test(decoding: test) else {
    // Swift Testing could not recreate a copy of the original `Test` instance.
    throw ...
  }
  cachedTests.withLock { \$0[test.id] = test }
  print("Discovered test '\(test.displayName ?? test.name)' at \(test.sourceLocation)")
}

Important

When you convert an instance of ABI.EncodedTest to an instance of Test (or ABI.EncodedEvent to Event, etc.), the conversion is lossy. Information that was originally available in these values at runtime may not be available in the copy you derive from the JSON event stream. In particular, you cannot run a Swift Testing test function created in this manner as the body of the test is not representable as JSON data.