Skip to content

[Experimental] Implement JSON coding without using Foundation or Codable. #1024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
public import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
private import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
public import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
public import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
public import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) import Testing
import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
#if !SWT_NO_FOUNDATION && canImport(Foundation)
@_spi(Experimental) public import Testing
public import Foundation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation) && !SWT_NO_UTC_CLOCK
#if !SWT_NO_FOUNDATION && canImport(Foundation) && !SWT_NO_UTC_CLOCK
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) public import Testing
public import Foundation

Expand Down
12 changes: 3 additions & 9 deletions Sources/Testing/ABI/ABI.Record+Streaming.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation) && (!SWT_NO_FILE_IO || !SWT_NO_ABI_ENTRY_POINT)
private import Foundation

extension ABI.Version {
/// Post-process encoded JSON and write it to a file.
///
Expand Down Expand Up @@ -58,13 +55,13 @@ extension ABI.Version {
let humanReadableOutputRecorder = Event.HumanReadableOutputRecorder()
return { [eventHandler = eventHandlerCopy] event, context in
if case .testDiscovered = event.kind, let test = context.test {
try? JSON.withEncoding(of: ABI.Record<Self>(encoding: test)) { testJSON in
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 = ABI.Record<Self>(encoding: event, in: context, messages: messages) {
try? JSON.withEncoding(of: eventRecord, eventHandler)
JSON.withEncoding(of: eventRecord, eventHandler)
}
}
}
Expand Down Expand Up @@ -96,12 +93,9 @@ extension ABI.Xcode16 {
eventContext: Event.Context.Snapshot(snapshotting: context)
)
try? JSON.withEncoding(of: snapshot) { eventAndContextJSON in
eventAndContextJSON.withUnsafeBytes { eventAndContextJSON in
eventHandler(eventAndContextJSON)
}
eventHandler(eventAndContextJSON)
}
}
}
}
#endif
#endif
36 changes: 21 additions & 15 deletions Sources/Testing/ABI/ABI.Record.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,15 @@ extension ABI {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.Record: Codable {
extension ABI.Record: Decodable {
private enum CodingKeys: String, CodingKey {
case version
case kind
case payload
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(V.versionNumber, forKey: .version)
switch kind {
case let .test(test):
try container.encode("test", forKey: .kind)
try container.encode(test, forKey: .payload)
case let .event(event):
try container.encode("event", forKey: .kind)
try container.encode(event, forKey: .payload)
}
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

Expand Down Expand Up @@ -93,3 +80,22 @@ extension ABI.Record: Codable {
}
}
}

extension ABI.Record: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
var dict = [
"version": V.versionNumber.makeJSONValue()
]

switch kind {
case let .test(test):
dict["kind"] = "test".makeJSONValue()
dict["payload"] = test.makeJSONValue()
case let .event(event):
dict["kind"] = "event".makeJSONValue()
dict["payload"] = event.makeJSONValue()
}

return dict.makeJSONValue()
}
}
6 changes: 4 additions & 2 deletions Sources/Testing/ABI/ABI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ extension ABI {
/// The numeric representation of this ABI version.
static var versionNumber: Int { get }

#if canImport(Foundation) && (!SWT_NO_FILE_IO || !SWT_NO_ABI_ENTRY_POINT)
/// Create an event handler that encodes events as JSON and forwards them to
/// an ABI-friendly event handler.
///
Expand All @@ -39,7 +38,6 @@ extension ABI {
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
) -> Event.Handler
#endif
}

/// The current supported ABI version (ignoring any experimental versions.)
Expand All @@ -50,6 +48,10 @@ extension ABI {

extension ABI {
#if !SWT_NO_SNAPSHOT_TYPES
#if SWT_NO_FOUNDATION || !canImport(Foundation)
#error("Platform-specific misconfiguration: Foundation is required for snapshot type support")
#endif

/// A namespace and version type for Xcode&nbsp;16 compatibility.
///
/// - Warning: This type will be removed in a future update.
Expand Down
16 changes: 14 additions & 2 deletions Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ extension ABI {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.EncodedAttachment: Codable {}
extension ABI.EncodedAttachment: Decodable {}

// MARK: - JSON.Serializable

extension ABI.EncodedAttachment: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
var dict = [String: JSON.Value]()
if let path {
dict["path"] = path.makeJSONValue()
}
return dict.makeJSONValue()
}
}
33 changes: 27 additions & 6 deletions Sources/Testing/ABI/Encoded/ABI.EncodedBacktrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,35 @@ extension ABI {
}
}

// MARK: - Codable

extension ABI.EncodedBacktrace: Codable {
func encode(to encoder: any Encoder) throws {
try symbolicatedAddresses.encode(to: encoder)
}
// MARK: - Decodable

extension ABI.EncodedBacktrace: Decodable {
init(from decoder: any Decoder) throws {
self.symbolicatedAddresses = try [Backtrace.SymbolicatedAddress](from: decoder)
}
}

// MARK: - JSON.Serializable

extension ABI.EncodedBacktrace: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
symbolicatedAddresses.makeJSONValue()
}
}

extension Backtrace.SymbolicatedAddress: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
var dict = [
"address": address.makeJSONValue()
]

if let offset {
dict["offset"] = offset.makeJSONValue()
}
if let symbolName {
dict["symbolName"] = symbolName.makeJSONValue()
}

return dict.makeJSONValue()
}
}
17 changes: 15 additions & 2 deletions Sources/Testing/ABI/Encoded/ABI.EncodedError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,22 @@ extension ABI.EncodedError: Error {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.EncodedError: Codable {}
extension ABI.EncodedError: Decodable {}

// MARK: - JSON.Serializable

extension ABI.EncodedError: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
let dict = [
"description": description.makeJSONValue(),
"domain": domain.makeJSONValue(),
"code": code.makeJSONValue()
]
return dict.makeJSONValue()
}
}

// MARK: - CustomTestStringConvertible

Expand Down
35 changes: 32 additions & 3 deletions Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,36 @@ extension ABI {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.EncodedEvent: Codable {}
extension ABI.EncodedEvent.Kind: Codable {}
extension ABI.EncodedEvent: Decodable {}
extension ABI.EncodedEvent.Kind: Decodable {}

// MARK: - JSON.Serializable

extension ABI.EncodedEvent: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
var dict = [
"kind": kind.makeJSONValue(),
"instant": instant.makeJSONValue(),
"messages": messages.makeJSONValue(),
]

if let issue {
dict["issue"] = issue.makeJSONValue()
}
if let _attachment {
dict["_attachment"] = _attachment.makeJSONValue()
}
if let testID {
dict["testID"] = testID.makeJSONValue()
}
if let _testCase {
dict["_testCase"] = _testCase.makeJSONValue()
}

return dict.makeJSONValue()
}
}

extension ABI.EncodedEvent.Kind: JSON.Serializable {}
16 changes: 14 additions & 2 deletions Sources/Testing/ABI/Encoded/ABI.EncodedInstant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ extension ABI {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.EncodedInstant: Codable {}
extension ABI.EncodedInstant: Decodable {}

// MARK: - JSON.Serializable

extension ABI.EncodedInstant: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
let dict = [
"absolute": absolute.makeJSONValue(),
"since1970": since1970.makeJSONValue()
]
return dict.makeJSONValue()
}
}
37 changes: 32 additions & 5 deletions Sources/Testing/ABI/Encoded/ABI.EncodedIssue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension ABI {
var isKnown: Bool

/// The location in source where this issue occurred, if available.
var sourceLocation: SourceLocation?
var sourceLocation: EncodedSourceLocation<V>?

/// The backtrace where this issue occurred, if available.
///
Expand All @@ -51,7 +51,9 @@ extension ABI {
case .error: .error
}
isKnown = issue.isKnown
sourceLocation = issue.sourceLocation
if let sourceLocation = issue.sourceLocation {
self.sourceLocation = EncodedSourceLocation<V>(encoding: sourceLocation)
}
if let backtrace = issue.sourceContext.backtrace {
_backtrace = EncodedBacktrace(encoding: backtrace, in: eventContext)
}
Expand All @@ -62,7 +64,32 @@ extension ABI {
}
}

// MARK: - Codable
// MARK: - Decodable

extension ABI.EncodedIssue: Decodable {}
extension ABI.EncodedIssue.Severity: Decodable {}

// MARK: - JSON.Serializable

extension ABI.EncodedIssue: JSON.Serializable {
func makeJSONValue() -> JSON.Value {
var dict = [
"_severity": _severity.makeJSONValue(),
"isKnown": isKnown.makeJSONValue()
]

if let sourceLocation {
dict["sourceLocation"] = sourceLocation.makeJSONValue()
}
if let _backtrace {
dict["_backtrace"] = _backtrace.makeJSONValue()
}
if let _error {
dict["_error"] = _error.makeJSONValue()
}

return dict.makeJSONValue()
}
}

extension ABI.EncodedIssue: Codable {}
extension ABI.EncodedIssue.Severity: Codable {}
extension ABI.EncodedIssue.Severity: JSON.Serializable {}
Loading