Skip to content

Commit 81c537c

Browse files
authored
Distinguish when an error is part of a recorded issue in an exit test. (#718)
Hot on the heels of #697… If an exit test records an issue of kind `.errorCaught()`, we currently treat it the same as any other issue and translate it to a flat `.unconditional` issue in the parent process: ```swift await #expect(exitsWith: .failure) { Issue.record(NoSuchWeaselError()) // unconditional issue recorded } ``` This PR distinguishes the `.errorCaught` issue kind and records an `.errorCaught` issue in the parent process. Fidelity of errors across a process boundary is weak, but we encode a minimal representation of the error that should be good enough for most uses. If you need `withKnownIssue()`, or `#expect(throws:)`, you can call them _inside_ the exit test. ### 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 eed68e7 commit 81c537c

File tree

6 files changed

+109
-19
lines changed

6 files changed

+109
-19
lines changed

Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ extension ABIv0 {
1515
/// This type is not part of the public interface of the testing library. It
1616
/// assists in converting values to JSON; clients that consume this JSON are
1717
/// expected to write their own decoders.
18+
///
19+
/// - Warning: Backtraces are not yet part of the JSON schema.
1820
struct EncodedBacktrace: Sendable {
1921
/// The frames in the backtrace.
2022
var symbolicatedAddresses: [Backtrace.SymbolicatedAddress]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
extension ABIv0 {
12+
/// A type implementing the JSON encoding of ``Error`` for the ABI entry point
13+
/// and event stream output.
14+
///
15+
/// This type is not part of the public interface of the testing library. It
16+
/// assists in converting values to JSON; clients that consume this JSON are
17+
/// expected to write their own decoders.
18+
///
19+
/// - Warning: Errors are not yet part of the JSON schema.
20+
struct EncodedError: Sendable {
21+
/// The error's description
22+
var description: String
23+
24+
/// The domain of the error.
25+
var domain: String
26+
27+
/// The code of the error.
28+
var code: Int
29+
30+
// TODO: userInfo (partial) encoding
31+
32+
init(encoding error: some Error, in eventContext: borrowing Event.Context) {
33+
description = String(describingForTest: error)
34+
domain = error._domain
35+
code = error._code
36+
}
37+
}
38+
}
39+
40+
// MARK: - Error
41+
42+
extension ABIv0.EncodedError: Error {
43+
var _domain: String {
44+
domain
45+
}
46+
47+
var _code: Int {
48+
code
49+
}
50+
51+
var _userInfo: AnyObject? {
52+
// TODO: userInfo (partial) encoding
53+
nil
54+
}
55+
}
56+
57+
// MARK: - Codable
58+
59+
extension ABIv0.EncodedError: Codable {}
60+
61+
// MARK: - CustomTestStringConvertible
62+
63+
extension ABIv0.EncodedError: CustomTestStringConvertible {
64+
var testDescription: String {
65+
description
66+
}
67+
}

Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedIssue.swift

+10
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,24 @@ extension ABIv0 {
2323
var sourceLocation: SourceLocation?
2424

2525
/// The backtrace where this issue occurred, if available.
26+
///
27+
/// - Warning: Backtraces are not yet part of the JSON schema.
2628
var _backtrace: EncodedBacktrace?
2729

30+
/// The error associated with this issue, if applicable.
31+
///
32+
/// - Warning: Errors are not yet part of the JSON schema.
33+
var _error: EncodedError?
34+
2835
init(encoding issue: borrowing Issue, in eventContext: borrowing Event.Context) {
2936
isKnown = issue.isKnown
3037
sourceLocation = issue.sourceLocation
3138
if let backtrace = issue.sourceContext.backtrace {
3239
_backtrace = EncodedBacktrace(encoding: backtrace, in: eventContext)
3340
}
41+
if let error = issue.error {
42+
_error = EncodedError(encoding: error, in: eventContext)
43+
}
3444
}
3545
}
3646
}

Sources/Testing/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_library(Testing
1414
ABI/v0/ABIv0.Record+Streaming.swift
1515
ABI/v0/ABIv0.swift
1616
ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift
17+
ABI/v0/Encoded/ABIv0.EncodedError.swift
1718
ABI/v0/Encoded/ABIv0.EncodedEvent.swift
1819
ABI/v0/Encoded/ABIv0.EncodedInstant.swift
1920
ABI/v0/Encoded/ABIv0.EncodedIssue.swift

Sources/Testing/ExitTests/ExitTest.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -505,12 +505,17 @@ extension ExitTest {
505505
let comments: [Comment] = event.messages.compactMap { message in
506506
message.symbol == .details ? Comment(rawValue: message.text) : nil
507507
}
508+
let issueKind: Issue.Kind = if let error = issue._error {
509+
.errorCaught(error)
510+
} else {
511+
// TODO: improve fidelity of issue kind reporting (especially those without associated values)
512+
.unconditional
513+
}
508514
let sourceContext = SourceContext(
509515
backtrace: nil, // `issue._backtrace` will have the wrong address space.
510516
sourceLocation: issue.sourceLocation
511517
)
512-
// TODO: improve fidelity of issue kind reporting (especially those without associated values)
513-
var issueCopy = Issue(kind: .unconditional, comments: comments, sourceContext: sourceContext)
518+
var issueCopy = Issue(kind: issueKind, comments: comments, sourceContext: sourceContext)
514519
issueCopy.isKnown = issue.isKnown
515520
issueCopy.record()
516521
}

Tests/TestingTests/ExitTestTests.swift

+22-17
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ private import _TestingInternals
3232
await Task.yield()
3333
exit(123)
3434
}
35-
await #expect(exitsWith: .failure) {
36-
throw MyError()
37-
}
3835
#if !os(Windows)
3936
await #expect(exitsWith: .signal(SIGKILL)) {
4037
_ = kill(getpid(), SIGKILL)
@@ -203,22 +200,30 @@ private import _TestingInternals
203200

204201
@Test("Exit test forwards issues") func forwardsIssues() async {
205202
await confirmation("Issue recorded") { issueRecorded in
206-
var configuration = Configuration()
207-
configuration.eventHandler = { event, _ in
208-
if case let .issueRecorded(issue) = event.kind,
209-
case .unconditional = issue.kind,
210-
issue.comments.contains("Something went wrong!") {
211-
issueRecorded()
203+
await confirmation("Error caught") { errorCaught in
204+
var configuration = Configuration()
205+
configuration.eventHandler = { event, _ in
206+
guard case let .issueRecorded(issue) = event.kind else {
207+
return
208+
}
209+
if case .unconditional = issue.kind, issue.comments.contains("Something went wrong!") {
210+
issueRecorded()
211+
} else if issue.error != nil {
212+
errorCaught()
213+
}
212214
}
213-
}
214-
configuration.exitTestHandler = ExitTest.handlerForEntryPoint()
215+
configuration.exitTestHandler = ExitTest.handlerForEntryPoint()
215216

216-
await Test {
217-
await #expect(exitsWith: .success) {
218-
#expect(Bool(false), "Something went wrong!")
219-
exit(0)
220-
}
221-
}.run(configuration: configuration)
217+
await Test {
218+
await #expect(exitsWith: .success) {
219+
#expect(Bool(false), "Something went wrong!")
220+
exit(0)
221+
}
222+
await #expect(exitsWith: .failure) {
223+
Issue.record(MyError())
224+
}
225+
}.run(configuration: configuration)
226+
}
222227
}
223228
}
224229

0 commit comments

Comments
 (0)