Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
62 changes: 62 additions & 0 deletions Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,65 @@ extension ABI {

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

// MARK: - Conversion to/from library types

@_spi(ForToolsIntegrationOnly)
extension Event {
/// Initialize an instance of this type from the given value.
///
/// - Parameters:
/// - event: The encoded event to initialize this instance from.
///
/// ``testID`` and ``testCaseID`` are always `nil` because we need information
/// from the associated `ABI.EncodedTest` to properly decode those values.
Comment on lines +193 to +194
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it'll just be follow-on work to fill in these missing property values, I take it?

public init?<V>(decoding event: ABI.EncodedEvent<V>) {
// SkipInfo will only be decoded for skip/cancel event kinds
lazy var skipInfo = SkipInfo(decoding: event)

let kind: Kind
switch event.kind {
case .runStarted:
kind = .runStarted
case .testStarted:
kind = .testStarted
case .testCaseStarted:
kind = .testCaseStarted
case .issueRecorded:
guard let issue = Issue(decoding: event) else {
return nil
}
kind = .issueRecorded(issue)
case .valueAttached:
guard let attachment = Attachment<AnyAttachable>(decoding: event) else {
return nil
}
kind = .valueAttached(attachment)
case .testCaseEnded:
kind = .testCaseEnded
case .testCaseCancelled:
guard let skipInfo else {
return nil
}
kind = .testCaseCancelled(skipInfo)
case .testEnded:
kind = .testEnded
case .testSkipped:
guard let skipInfo else {
return nil
}
kind = .testSkipped(skipInfo)
case .testCancelled:
guard let skipInfo else {
return nil
}
kind = .testCancelled(skipInfo)
case .runEnded:
kind = .runEnded
}

guard let instant = Test.Clock.Instant(decoding: event.instant) else { return nil }

self.init(kind, testID: nil, testCaseID: nil, instant: instant)
}
}
122 changes: 122 additions & 0 deletions Tests/TestingTests/ABI.EncodedEventTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@testable @_spi(Experimental) @_spi(ForToolsIntegrationOnly) import Testing

@Suite struct `ABI.EncodedEventTests` {
#if canImport(Foundation)
/// Creates an EncodedEvent from a JSON string.
///
/// - Throws: If the JSON doesn't represent a valid EncodedEvent.
private func encodedEvent(
_ json: String,
) throws -> ABI.EncodedEvent<ABI.CurrentVersion> {
var json = json
return try json.withUTF8 { json in
try JSON.decode(ABI.EncodedEvent<ABI.CurrentVersion>.self, from: UnsafeRawBufferPointer(json))
}
}

@Test func `Decoded event always has nil testID and testCaseID`() throws {
let event = try encodedEvent(
"""
{
"kind": "testStarted",
"instant": {"absolute": 123, "since1970": 456},
"messages": [],
"testID": "SomeValidTestID/testFunc()"
}
""")
let decoded = try #require(Event(decoding: event))

#expect(decoded.testID == nil)
#expect(decoded.testCaseID == nil)
}

@Test(arguments: [
"runStarted",
"runEnded",
"testStarted",
"testEnded",
"testCaseStarted",
"testCaseEnded",
// Following `kind`s need SkipInfo which nominally requires _sourceLocation.
// However, an empty placeholder SkipInfo can be provided when decoding.
// vvv
"testCaseCancelled",
"testSkipped",
"testCancelled",
]) func `Successfully decode events which don't require associated info`(kind: String) throws {
let event = try encodedEvent(
"""
{
"kind": "\(kind)",
"instant": {"absolute": 123, "since1970": 456},
"messages": [],
}
""")

#expect(Event(decoding: event) != nil)
}

@Test(arguments: [
"issueRecorded", // Needs issue details
"valueAttached", // Needs attachment details
]) func `Events without required associated info fail to decode`(kind: String) throws {
let event = try encodedEvent(
"""
{
"kind": "\(kind)",
"instant": {"absolute": 123, "since1970": 456},
"messages": [],
}
""")

#expect(Event(decoding: event) == nil)
}

@Test func `Decode issueRecorded`() throws {
let event = try encodedEvent(
"""
{
"kind": "issueRecorded",
"instant": {"absolute": 0, "since1970": 0},
"messages": [],
"issue": {"isKnown": true}
}
""")
let decoded = try #require(Event(decoding: event))

guard case .issueRecorded(let issue) = decoded.kind else {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if there was a nicer way of checking the enum case for kind since this enum has associated values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we do it for the moment.

Issue.record("Expected issueRecorded but got wrong kind \(decoded.kind)")
return
}
#expect(issue.isKnown)
}

@Test func `Decode valueAttached`() throws {
let event = try encodedEvent(
"""
{
"kind": "valueAttached",
"instant": {"absolute": 0, "since1970": 0},
"messages": [],
"attachment": {"path": "/tmp/important-cheese.txt"}
}
""")
let decoded = try #require(Event(decoding: event))

guard case .valueAttached = decoded.kind else {
Issue.record("Expected valueAttached but got wrong kind \(decoded.kind)")
return
}
}
#endif
}
3 changes: 0 additions & 3 deletions Tests/TestingTests/SkipInfoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
//

@testable @_spi(ForToolsIntegrationOnly) import Testing
#if canImport(Foundation)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, realised I didn't need the import in my earlier change after writing the tests in this one. I can leave it out though

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine, just helps to explain this sort of thing in the PR. :)

import Foundation
#endif

@Suite("SkipInfo Tests")
struct SkipInfoTests {
Expand Down
Loading