Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 28 additions & 77 deletions Sources/Testing/ABI/ABI.Record+Streaming.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//

#if canImport(Foundation) && (!SWT_NO_FILE_IO || !SWT_NO_ABI_ENTRY_POINT)
extension ABI.Record {
extension ABI.Version {
/// Post-process encoded JSON and write it to a file.
///
/// - Parameters:
Expand Down Expand Up @@ -43,25 +43,6 @@ extension ABI.Record {
}
}

/// Create an event handler that encodes events as JSON and forwards them to
/// an ABI-friendly event handler.
///
/// - Parameters:
/// - encodeAsJSONLines: Whether or not to ensure JSON passed to
/// `eventHandler` is encoded as JSON Lines (i.e. that it does not contain
/// extra newlines.)
/// - eventHandler: The event handler to forward events to. See
/// ``ABIv0/EntryPoint-swift.typealias`` for more information.
///
/// - Returns: An event handler.
///
/// The resulting event handler outputs data as JSON. For each event handled
/// by the resulting event handler, a JSON object representing it and its
/// associated context is created and is passed to `eventHandler`.
///
/// Note that ``configurationForEntryPoint(from:)`` calls this function and
/// performs additional postprocessing before writing JSON data to ensure it
/// does not contain any newline characters.
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
Expand All @@ -75,12 +56,12 @@ extension ABI.Record {
let humanReadableOutputRecorder = Event.HumanReadableOutputRecorder()
return { [eventHandler = eventHandlerCopy] event, context in
if case .testDiscovered = event.kind, let test = context.test {
try? JSON.withEncoding(of: Self(encoding: test)) { testJSON in
try? JSON.withEncoding(of: ABI.Record<Self>(encoding: test)) { testJSON in
eventHandler(testJSON)
}
} else {
let messages = humanReadableOutputRecorder.record(event, in: context, verbosity: 0)
if let eventRecord = Self(encoding: event, in: context, messages: messages) {
if let eventRecord = ABI.Record<Self>(encoding: event, in: context, messages: messages) {
try? JSON.withEncoding(of: eventRecord, eventHandler)
}
}
Expand All @@ -89,63 +70,33 @@ extension ABI.Record {
}

#if !SWT_NO_SNAPSHOT_TYPES
// MARK: - Experimental event streaming
// MARK: - Xcode 16 Beta 1 compatibility

/// A type containing an event snapshot and snapshots of the contents of an
/// event context suitable for streaming over JSON.
///
/// This type is not part of the public interface of the testing library.
/// External adopters are not necessarily written in Swift and are expected to
/// decode the JSON produced for this type in implementation-specific ways.
///
/// - Warning: This type supports early Xcode 16 betas and will be removed in a
/// future update.
struct EventAndContextSnapshot {
/// A snapshot of the event.
var event: Event.Snapshot

/// A snapshot of the event context.
var eventContext: Event.Context.Snapshot
}

extension EventAndContextSnapshot: Codable {}
extension ABI.Xcode16Beta1 {
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
) -> Event.Handler {
return { event, context in
if case .testDiscovered = event.kind {
// Discard events of this kind rather than forwarding them to avoid a
// crash in Xcode 16 Beta 1 (which does not expect any events to occur
// before .runStarted.)
return
}

/// Create an event handler that encodes events as JSON and forwards them to an
/// ABI-friendly event handler.
///
/// - Parameters:
/// - eventHandler: The event handler to forward events to. See
/// ``ABIv0/EntryPoint-swift.typealias`` for more information.
///
/// - Returns: An event handler.
///
/// The resulting event handler outputs data as JSON. For each event handled by
/// the resulting event handler, a JSON object representing it and its
/// associated context is created and is passed to `eventHandler`.
///
/// Note that ``configurationForEntryPoint(from:)`` calls this function and
/// performs additional postprocessing before writing JSON data to ensure it
/// does not contain any newline characters.
///
/// - Warning: This function supports early Xcode 16 betas and will be removed
/// in a future update.
func eventHandlerForStreamingEventSnapshots(
to eventHandler: @escaping @Sendable (_ eventAndContextJSON: UnsafeRawBufferPointer) -> Void
) -> Event.Handler {
return { event, context in
if case .testDiscovered = event.kind {
// Discard events of this kind rather than forwarding them to avoid a
// crash in Xcode 16 Beta 1 (which does not expect any events to occur
// before .runStarted.)
return
}
let snapshot = EventAndContextSnapshot(
event: Event.Snapshot(snapshotting: event),
eventContext: Event.Context.Snapshot(snapshotting: context)
)
try? JSON.withEncoding(of: snapshot) { eventAndContextJSON in
eventAndContextJSON.withUnsafeBytes { eventAndContextJSON in
eventHandler(eventAndContextJSON)
struct EventAndContextSnapshot: Codable {
var event: Event.Snapshot
var eventContext: Event.Context.Snapshot
}
let snapshot = EventAndContextSnapshot(
event: Event.Snapshot(snapshotting: event),
eventContext: Event.Context.Snapshot(snapshotting: context)
)
try? JSON.withEncoding(of: snapshot) { eventAndContextJSON in
eventAndContextJSON.withUnsafeBytes { eventAndContextJSON in
eventHandler(eventAndContextJSON)
}
}
}
}
Expand Down
38 changes: 24 additions & 14 deletions Sources/Testing/ABI/ABI.Record.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,14 @@ extension ABI {
/// This type is not part of the public interface of the testing library. It
/// assists in converting values to JSON; clients that consume this JSON are
/// expected to write their own decoders.
struct Record: Sendable {
/// The version of this record.
///
/// The value of this property corresponds to the ABI version i.e. `0`.
var version = 0

struct Record<V>: Sendable where V: ABI.Version {
/// An enumeration describing the various kinds of record.
enum Kind: Sendable {
/// A test record.
case test(EncodedTest)
case test(EncodedTest<V>)

/// An event record.
case event(EncodedEvent)
case event(EncodedEvent<V>)
}

/// The kind of record.
Expand All @@ -38,7 +33,7 @@ extension ABI {
}

init?(encoding event: borrowing Event, in eventContext: borrowing Event.Context, messages: borrowing [Event.HumanReadableOutputRecorder.Message]) {
guard let event = EncodedEvent(encoding: event, in: eventContext, messages: messages) else {
guard let event = EncodedEvent<V>(encoding: event, in: eventContext, messages: messages) else {
return nil
}
kind = .event(event)
Expand All @@ -57,7 +52,7 @@ extension ABI.Record: Codable {

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(version, forKey: .version)
try container.encode(V.versionNumber, forKey: .version)
switch kind {
case let .test(test):
try container.encode("test", forKey: .kind)
Expand All @@ -70,16 +65,31 @@ extension ABI.Record: Codable {

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(Int.self, forKey: .version)

let versionNumber = try container.decode(Int.self, forKey: .version)
if versionNumber != V.versionNumber {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: decoder.codingPath + CollectionOfOne(CodingKeys.version as any CodingKey),
debugDescription: "Unexpected record version \(versionNumber) (expected \(V.versionNumber))."
)
)
}

switch try container.decode(String.self, forKey: .kind) {
case "test":
let test = try container.decode(ABI.EncodedTest.self, forKey: .payload)
let test = try container.decode(ABI.EncodedTest<V>.self, forKey: .payload)
kind = .test(test)
case "event":
let event = try container.decode(ABI.EncodedEvent.self, forKey: .payload)
let event = try container.decode(ABI.EncodedEvent<V>.self, forKey: .payload)
kind = .event(event)
case let kind:
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unrecognized record kind '\(kind)'"))
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: decoder.codingPath + CollectionOfOne(CodingKeys.kind as any CodingKey),
debugDescription: "Unrecognized record kind '\(kind)'"
)
)
}
}
}
66 changes: 60 additions & 6 deletions Sources/Testing/ABI/ABI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,70 @@
@_spi(ForToolsIntegrationOnly)
public enum ABI: Sendable {}

// MARK: -
// MARK: - ABI version abstraction

extension ABI {
/// A protocol describing the types that represent different ABI versions.
protocol Version: Sendable {
/// The numeric representation of this ABI version.
static var versionNumber: Int { get }

/// Create an event handler that encodes events as JSON and forwards them to
/// an ABI-friendly event handler.
///
/// - Parameters:
/// - encodeAsJSONLines: Whether or not to ensure JSON passed to
/// `eventHandler` is encoded as JSON Lines (i.e. that it does not
/// contain extra newlines.)
/// - eventHandler: The event handler to forward events to.
///
/// - Returns: An event handler.
///
/// The resulting event handler outputs data as JSON. For each event handled
/// by the resulting event handler, a JSON object representing it and its
/// associated context is created and is passed to `eventHandler`.
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
) -> Event.Handler
}

/// The current supported ABI version (ignoring any experimental versions.)
typealias CurrentVersion = v0
}

// MARK: - Concrete ABI versions

@_spi(ForToolsIntegrationOnly)
extension ABI {
/// A namespace for ABI version 0 symbols.
public enum v0: Sendable {}
#if !SWT_NO_SNAPSHOT_TYPES
/// A namespace and version type for Xcode 16 Beta 1 compatibility.
///
/// - Warning: This type will be removed in a future update.
enum Xcode16Beta1: Sendable, Version {
static var versionNumber: Int {
-1
}
}
#endif

/// A namespace and type for ABI version 0 symbols.
public enum v0: Sendable, Version {
static var versionNumber: Int {
0
}
}

/// A namespace for ABI version 1 symbols.
/// A namespace and type for ABI version 1 symbols.
///
/// @Metadata {
/// @Available("Swift Testing ABI", introduced: 1)
/// }
@_spi(Experimental)
public enum v1: Sendable {}
public enum v1: Sendable, Version {
static var versionNumber: Int {
1
}
}
}

/// A namespace for ABI version 0 symbols.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension ABI {
/// expected to write their own decoders.
///
/// - Warning: Attachments are not yet part of the JSON schema.
struct EncodedAttachment: Sendable {
struct EncodedAttachment<V>: Sendable where V: ABI.Version {
/// The path where the attachment was written.
var path: String?

Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/Encoded/ABI.EncodedBacktrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension ABI {
/// expected to write their own decoders.
///
/// - Warning: Backtraces are not yet part of the JSON schema.
struct EncodedBacktrace: Sendable {
struct EncodedBacktrace<V>: Sendable where V: ABI.Version {
/// The frames in the backtrace.
var symbolicatedAddresses: [Backtrace.SymbolicatedAddress]

Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/Encoded/ABI.EncodedError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension ABI {
/// expected to write their own decoders.
///
/// - Warning: Errors are not yet part of the JSON schema.
struct EncodedError: Sendable {
struct EncodedError<V>: Sendable where V: ABI.Version {
/// The error's description
var description: String

Expand Down
14 changes: 7 additions & 7 deletions Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension ABI {
/// This type is not part of the public interface of the testing library. It
/// assists in converting values to JSON; clients that consume this JSON are
/// expected to write their own decoders.
struct EncodedEvent: Sendable {
struct EncodedEvent<V>: Sendable where V: ABI.Version {
/// An enumeration describing the various kinds of event.
///
/// Note that the set of encodable events is a subset of all events
Expand All @@ -38,33 +38,33 @@ extension ABI {
var kind: Kind

/// The instant at which the event occurred.
var instant: EncodedInstant
var instant: EncodedInstant<V>

/// The issue that occurred, if any.
///
/// The value of this property is `nil` unless the value of the
/// ``kind-swift.property`` property is ``Kind-swift.enum/issueRecorded``.
var issue: EncodedIssue?
var issue: EncodedIssue<V>?

/// The value that was attached, if any.
///
/// The value of this property is `nil` unless the value of the
/// ``kind-swift.property`` property is ``Kind-swift.enum/valueAttached``.
///
/// - Warning: Attachments are not yet part of the JSON schema.
var _attachment: EncodedAttachment?
var _attachment: EncodedAttachment<V>?

/// Human-readable messages associated with this event that can be presented
/// to the user.
var messages: [EncodedMessage]
var messages: [EncodedMessage<V>]

/// The ID of the test associated with this event, if any.
var testID: EncodedTest.ID?
var testID: EncodedTest<V>.ID?

/// The ID of the test case associated with this event, if any.
///
/// - Warning: Test cases are not yet part of the JSON schema.
var _testCase: EncodedTestCase?
var _testCase: EncodedTestCase<V>?

init?(encoding event: borrowing Event, in eventContext: borrowing Event.Context, messages: borrowing [Event.HumanReadableOutputRecorder.Message]) {
switch event.kind {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/Encoded/ABI.EncodedInstant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension ABI {
/// This type is not part of the public interface of the testing library. It
/// assists in converting values to JSON; clients that consume this JSON are
/// expected to write their own decoders.
struct EncodedInstant: Sendable {
struct EncodedInstant<V>: Sendable where V: ABI.Version {
/// The number of seconds since the system-defined suspending epoch.
///
/// For more information, see [`SuspendingClock`](https://developer.apple.com/documentation/swift/suspendingclock).
Expand Down
Loading