Skip to content

Commit 40c1b67

Browse files
authored
Encode/decode .apiMisused and .system issue kinds. (#1632)
This PR takes advantage of the fact that we (experimentally) encode errors for issues and encodes instances of `APIMisuseError` and `SystemError` to represent `.apiMisused` and `.system` issues, respectively. This improves the fidelity of issues reported via XCTest interop and exit test backchannels. ### 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 b946b36 commit 40c1b67

File tree

4 files changed

+81
-6
lines changed

4 files changed

+81
-6
lines changed

Sources/Testing/ABI/Encoded/ABI.EncodedError.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ extension ABI {
1818
///
1919
/// - Warning: Errors are not yet part of the JSON schema.
2020
struct EncodedError<V>: Sendable where V: ABI.Version {
21-
/// The error's description
21+
/// The error's description.
2222
var description: String
2323

2424
/// The domain of the error.
25-
var domain: String
25+
///
26+
/// The value of this property may be `nil` if the error originated in a
27+
/// context other than Swift or Objective-C (where errors may not have
28+
/// associated domain strings).
29+
var domain: String?
2630

2731
/// The code of the error.
2832
var code: Int
@@ -32,6 +36,9 @@ extension ABI {
3236
init(encoding error: some Error) {
3337
description = String(describingForTest: error)
3438
domain = error._domain
39+
if domain == Self.unknownDomain {
40+
domain = nil
41+
}
3542
code = error._code
3643
}
3744
}
@@ -40,8 +47,13 @@ extension ABI {
4047
// MARK: - Error
4148

4249
extension ABI.EncodedError: Error {
50+
/// The domain of decoded errors that did not specify a domain of their own.
51+
static var unknownDomain: String {
52+
"<unknown>"
53+
}
54+
4355
var _domain: String {
44-
domain
56+
domain ?? Self.unknownDomain
4557
}
4658

4759
var _code: Int {

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,17 @@ extension ABI {
9292
if let backtrace = issue.sourceContext.backtrace {
9393
_backtrace = EncodedBacktrace(encoding: backtrace, in: eventContext)
9494
}
95-
if let error = issue.error {
96-
_error = EncodedError(encoding: error)
95+
_error = if let error = issue.error {
96+
EncodedError(encoding: error)
97+
} else {
98+
switch issue.kind {
99+
case .apiMisused:
100+
EncodedError(encoding: APIMisuseError(description: ""))
101+
case .system:
102+
EncodedError(encoding: SystemError(description: ""))
103+
default:
104+
nil
105+
}
97106
}
98107
if case let .expectationFailed(expectation) = issue.kind {
99108
_expression = EncodedExpression(encoding: expectation.evaluatedExpression)
@@ -138,7 +147,14 @@ extension Issue {
138147
init?<V>(decoding issue: ABI.EncodedIssue<V>) {
139148
let issueKind: Issue.Kind
140149
if let error = issue._error {
141-
issueKind = .errorCaught(error)
150+
switch error.domain {
151+
case APIMisuseError.domain:
152+
issueKind = .apiMisused
153+
case SystemError.domain:
154+
issueKind = .system
155+
default:
156+
issueKind = .errorCaught(error)
157+
}
142158
} else if let expression = issue._expression.flatMap(__Expression.init(decoding:)),
143159
let sourceLocation = issue.sourceLocation.flatMap(SourceLocation.init) {
144160
let expectation = Expectation(

Sources/Testing/Support/CustomIssueRepresentable.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ protocol CustomIssueRepresentable: Error {
4747
struct SystemError: Error, CustomStringConvertible, CustomIssueRepresentable {
4848
var description: String
4949

50+
static var domain: String {
51+
"org.swift.testing.SystemError"
52+
}
53+
54+
var _domain: String {
55+
Self.domain
56+
}
57+
5058
func customize(_ issue: consuming Issue) -> Issue {
5159
issue.kind = .system
5260
issue.comments.append("\(self)")
@@ -66,6 +74,14 @@ struct SystemError: Error, CustomStringConvertible, CustomIssueRepresentable {
6674
struct APIMisuseError: Error, CustomStringConvertible, CustomIssueRepresentable {
6775
var description: String
6876

77+
static var domain: String {
78+
"org.swift.testing.APIMisuseError"
79+
}
80+
81+
var _domain: String {
82+
Self.domain
83+
}
84+
6985
func customize(_ issue: consuming Issue) -> Issue {
7086
issue.kind = .apiMisused
7187
issue.comments.append("\(self)")

Tests/TestingTests/ExitTestTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,37 @@ private import _TestingInternals
225225
}
226226
}
227227

228+
@Test("Exit test forwards .apiMisused and .system issues") func forwardsAPIMisusedAndSystemIssues() async {
229+
await confirmation(".apiMisused recorded") { apiMisusedRecorded in
230+
await confirmation(".system recorded") { systemRecorded in
231+
var configuration = Configuration()
232+
configuration.eventHandler = { event, _ in
233+
guard case let .issueRecorded(issue) = event.kind else {
234+
return
235+
}
236+
switch issue.kind {
237+
case .apiMisused:
238+
apiMisusedRecorded()
239+
case .system:
240+
systemRecorded()
241+
default:
242+
break
243+
}
244+
}
245+
configuration.exitTestHandler = ExitTest.handlerForEntryPoint()
246+
247+
await Test {
248+
await #expect(processExitsWith: .success) {
249+
Issue(kind: .apiMisused).record()
250+
}
251+
await #expect(processExitsWith: .failure) {
252+
Issue(kind: .system).record()
253+
}
254+
}.run(configuration: configuration)
255+
}
256+
}
257+
}
258+
228259
@Test("Exit test issues contain expression trees") func expressionsInIssues() async {
229260
await confirmation("Expectation failed") { expectationFailed in
230261
var configuration = Configuration()

0 commit comments

Comments
 (0)