Skip to content

Commit 1cedf5d

Browse files
authored
Ensure xUnit/JUnit XML output correctly counts test-less issues. (#597)
This PR ensures that issues that occur on detached tasks are recorded in the XML output provided by Swift Testing. I also took a moment to improve test coverage a bit for the XML recorder. ### 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 ec2bbcb commit 1cedf5d

File tree

2 files changed

+40
-10
lines changed

2 files changed

+40
-10
lines changed

Sources/Testing/Events/Recorder/Event.JUnitXMLRecorder.swift

+14-8
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ extension Event {
3333
/// This value does not include test suites.
3434
var testCount = 0
3535

36+
/// Any recorded issues where the test was not known.
37+
var issuesForUnknownTests = [Issue]()
38+
3639
/// A type describing data tracked on a per-test basis.
3740
struct TestData: Sendable {
3841
/// The ID of the test.
@@ -122,19 +125,22 @@ extension Event.JUnitXMLRecorder {
122125
if issue.isKnown {
123126
return nil
124127
}
125-
guard let id = test?.id else {
126-
return nil // FIXME: handle issues without known tests
127-
}
128-
let keyPath = id.keyPathRepresentation
129-
_context.withLock { context in
130-
context.testData[keyPath]?.issues.append(issue)
128+
if let id = test?.id {
129+
let keyPath = id.keyPathRepresentation
130+
_context.withLock { context in
131+
context.testData[keyPath]?.issues.append(issue)
132+
}
133+
} else {
134+
_context.withLock { context in
135+
context.issuesForUnknownTests.append(issue)
136+
}
131137
}
132138
return nil
133139
case .runEnded:
134140
return _context.withLock { context in
135141
let issueCount = context.testData
136142
.compactMap(\.value?.issues.count)
137-
.reduce(into: 0, +=)
143+
.reduce(into: 0, +=) + context.issuesForUnknownTests.count
138144
let skipCount = context.testData
139145
.compactMap(\.value?.skipInfo)
140146
.count
@@ -218,7 +224,7 @@ extension Event.JUnitXMLRecorder {
218224
">"
219225
case "&":
220226
"&"
221-
case _ where !character.isASCII:
227+
case _ where !character.isASCII || character.isNewline:
222228
character.unicodeScalars.lazy
223229
.map(\.value)
224230
.map { "&#\($0);" }

Tests/TestingTests/EventRecorderTests.swift

+26-2
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,32 @@ struct EventRecorderTests {
360360
}
361361
#endif
362362

363-
@Test("Recorded issues may not have associated tests")
364-
func issueWithoutTest() {
363+
@Test("HumanReadableOutputRecorder counts issues without associated tests")
364+
func humanReadableRecorderCountsIssuesWithoutTests() {
365365
let issue = Issue(kind: .unconditional, comments: [], sourceContext: .init())
366366
let event = Event(.issueRecorded(issue), testID: nil, testCaseID: nil)
367367
let context = Event.Context(test: nil, testCase: nil)
368368

369369
let recorder = Event.HumanReadableOutputRecorder()
370+
let messages = recorder.record(event, in: context)
371+
#expect(
372+
messages.map(\.stringValue).contains { message in
373+
message.contains("unknown")
374+
}
375+
)
376+
}
377+
378+
@Test("JUnitXMLRecorder counts issues without associated tests")
379+
func junitRecorderCountsIssuesWithoutTests() async throws {
380+
let issue = Issue(kind: .unconditional, comments: [], sourceContext: .init())
381+
let event = Event(.issueRecorded(issue), testID: nil, testCaseID: nil)
382+
let context = Event.Context(test: nil, testCase: nil)
383+
384+
let recorder = Event.JUnitXMLRecorder { string in
385+
if string.contains("<testsuite") {
386+
#expect(string.contains(#"failures=1"#))
387+
}
388+
}
370389
_ = recorder.record(event, in: context)
371390
}
372391
}
@@ -379,6 +398,7 @@ struct EventRecorderTests {
379398
await { () async in
380399
_ = Issue.record("Whales fail asynchronously.")
381400
}()
401+
Issue.record("Whales\nalso\nfall.")
382402
}
383403
@Test(.hidden) func expectantKangaroo() {
384404
#expect("abc" == "xyz")
@@ -449,6 +469,10 @@ struct EventRecorderTests {
449469
@Test(.hidden) func cornyUnicorn🦄() throws {
450470
throw MyDescriptiveError(description: #"🦄"#)
451471
}
472+
473+
@Test(.hidden) func burdgeoningBudgerigar() {
474+
Issue.record(#"</>& "Down\#nwe\#ngo!""#)
475+
}
452476
}
453477

454478
@Suite(.hidden) struct PredictablyFailingTests {

0 commit comments

Comments
 (0)