1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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(ForToolsIntegrationOnly) import Testing
@Suite("IssueHandlingTrait Tests")
struct IssueHandlingTraitTests {
@Test("Transforming an issue by appending a comment")
func addComment() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
guard case let .issueRecorded(issue) = event.kind, case .unconditional = issue.kind else {
return
}
#expect(issue.comments == ["Foo", "Bar"])
}
let handler = IssueHandlingTrait.compactMapIssues { issue in
var issue = issue
issue.comments.append("Bar")
return issue
}
await Test(handler) {
Issue.record("Foo")
}.run(configuration: configuration)
}
@Test("Suppressing an issue by returning `nil` from the closure passed to compactMapIssues()")
func suppressIssueUsingCompactMapIssues() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
if case .issueRecorded = event.kind {
Issue.record("Unexpected issue recorded event: \(event)")
}
}
let handler = IssueHandlingTrait.compactMapIssues { _ in
// Return nil to suppress the issue.
nil
}
await Test(handler) {
Issue.record("Foo")
}.run(configuration: configuration)
}
@Test("Suppressing an issue by returning `false` from the filter closure")
func filterIssue() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
if case .issueRecorded = event.kind {
Issue.record("Unexpected issue recorded event: \(event)")
}
}
await Test(.filterIssues { _ in false }) {
Issue.record("Foo")
}.run(configuration: configuration)
}
#if !SWT_NO_UNSTRUCTURED_TASKS
@Test("Transforming an issue recorded from another trait on the test")
func skipIssue() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
guard case let .issueRecorded(issue) = event.kind, case .errorCaught = issue.kind else {
return
}
#expect(issue.comments == ["Transformed!"])
}
struct MyError: Error {}
try await confirmation("Issue handler closure is called") { issueHandlerCalled in
let transform: @Sendable (Issue) -> Issue? = { issue in
defer {
issueHandlerCalled()
}
#expect(Test.Case.current == nil)
var issue = issue
issue.comments = ["Transformed!"]
return issue
}
let test = Test(
.enabled(if: try { throw MyError() }()),
.compactMapIssues(transform)
) {}
// Use a detached task to intentionally clear task local values for the
// current test and test case, since this test validates their value.
await Task.detached { [configuration] in
await test.run(configuration: configuration)
}.value
}
}
#endif
@Test("Accessing the current Test and Test.Case from an issue handler closure")
func currentTestAndCase() async throws {
await confirmation("Issue handler closure is called") { issueHandlerCalled in
let handler = IssueHandlingTrait.compactMapIssues { issue in
defer {
issueHandlerCalled()
}
#expect(Test.current?.name == "fixture()")
#expect(Test.Case.current != nil)
return issue
}
var test = Test(handler) {
Issue.record("Foo")
}
test.name = "fixture()"
await test.run()
}
}
@Test("Validate the relative execution order of multiple issue handling traits")
func traitOrder() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
guard case let .issueRecorded(issue) = event.kind, case .unconditional = issue.kind else {
return
}
// Ordering is intentional
#expect(issue.comments == ["Foo", "Bar", "Baz"])
}
let outerHandler = IssueHandlingTrait.compactMapIssues { issue in
var issue = issue
issue.comments.append("Baz")
return issue
}
let innerHandler = IssueHandlingTrait.compactMapIssues { issue in
var issue = issue
issue.comments.append("Bar")
return issue
}
await Test(outerHandler, innerHandler) {
Issue.record("Foo")
}.run(configuration: configuration)
}
@Test("Secondary issue recorded from an issue handler closure")
func issueRecordedFromClosure() async throws {
await confirmation("Original issue recorded") { originalIssueRecorded in
await confirmation("Secondary issue recorded") { secondaryIssueRecorded in
var configuration = Configuration()
configuration.eventHandler = { event, context in
guard case let .issueRecorded(issue) = event.kind, case .unconditional = issue.kind else {
return
}
if issue.comments.contains("Foo") {
originalIssueRecorded()
} else if issue.comments.contains("Something else") {
secondaryIssueRecorded()
} else {
Issue.record("Unexpected issue recorded: \(issue)")
}
}
let handler1 = IssueHandlingTrait.compactMapIssues { issue in
return issue
}
let handler2 = IssueHandlingTrait.compactMapIssues { issue in
Issue.record("Something else")
return issue
}
let handler3 = IssueHandlingTrait.compactMapIssues { issue in
// The "Something else" issue should not be passed to this closure.
#expect(issue.comments.contains("Foo"))
return issue
}
await Test(handler1, handler2, handler3) {
Issue.record("Foo")
}.run(configuration: configuration)
}
}
}
@Test("System issues are not passed to issue handler closures")
func ignoresSystemIssues() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
if case let .issueRecorded(issue) = event.kind, case .unconditional = issue.kind {
issue.record()
}
}
let handler = IssueHandlingTrait.compactMapIssues { issue in
if case .system = issue.kind {
Issue.record("Unexpectedly received a system issue")
}
return nil
}
await Test(handler) {
Issue(kind: .system).record()
}.run(configuration: configuration)
}
@Test("An API misused issue can be returned by issue handler closure when the original issue had that kind")
func returningAPIMisusedIssue() async throws {
var configuration = Configuration()
configuration.eventHandler = { event, context in
if case let .issueRecorded(issue) = event.kind, case .unconditional = issue.kind {
issue.record()
}
}
let handler = IssueHandlingTrait.compactMapIssues { issue in
guard case .apiMisused = issue.kind else {
return Issue.record("Expected an issue of kind 'apiMisused': \(issue)")
}
return issue
}
await Test(handler) {
Issue(kind: .apiMisused).record()
}.run(configuration: configuration)
}
#if !SWT_NO_EXIT_TESTS
@Test("Disallow assigning kind to .system")
func disallowAssigningSystemKind() async throws {
await #expect(processExitsWith: .failure) {
await Test(.compactMapIssues { issue in
var issue = issue
issue.kind = .system
return issue
}) {
Issue.record("A non-system issue")
}.run()
}
}
@Test("Disallow assigning kind to .apiMisused")
func disallowAssigningAPIMisusedKind() async throws {
await #expect(processExitsWith: .failure) {
await Test(.compactMapIssues { issue in
var issue = issue
issue.kind = .apiMisused
return issue
}) {
Issue.record("A non-system issue")
}.run()
}
}
#endif
}
|