Add diagnostics for issues recorded after their associated test has finished#1591
Add diagnostics for issues recorded after their associated test has finished#1591wrdowney wants to merge 4 commits intoswiftlang:mainfrom
Conversation
Adds a warning diagnostic message for issue that are recorded after their associated test has finished. This commonly occurs when asynchronous work completes after the test its associated test has ended. While responsibility falls on the user in this scenario, clear messaging can point them in the correct direction. Resolves swiftlang#1283
|
I'm not sure this is the right abstraction. A problem of this form should probably itself be surfaced as an issue (of kind Just adding it to the console output means that, for instance, somebody using Xcode or VS Code won't see anything about the problem unless they go digging in the log file. |
|
Thanks for the feedback! I agree about surfacing through issues, thanks for pointing that out. I'm working on implementing that now and looking for a little guidance on the following questions:
|
If an issue is recorded from within a detached task that was originated by a specific test, we would expect that issue to be associated with that test (if we can figure out that it came from that test, which is hard.) You shouldn't need to ever store a list of "current" tests anywhere.
The only immediate concern(s) I have are that: a. We would need to guard against accidentally recursing, i.e. an issue of this specific flavour, not necessarily all For a., an internal-only |
|
I have a working (albeit a little rough) solution for emitting a second recordIssue event when an issue is recorded after test completion. But I'm now running into the following issue in Xcode: recording an issue after test completion results in an internal inconsistency error . @Suite struct ExampleTests {
@Test func example() async throws {
Task {
try await Task.sleep(for: .seconds(1))
Issue.record("Late")
}
}
@Test func longRunning() async throws {
try await Task.sleep(for: .seconds(5))
}
}This is run on the current main using Xcode 26.3 Unfortunately the error is thrown after the initial issue is recorded, thus any subsequent issues are neither logged to the console or recorded in editor. I see #480 which fixes a similar issue for detached tasks. But those fixes don't seem to resolve this case. This seems to be an Xcode issue (I have confirmed running via CLI and VSCode extension does not throw an error). Although I do recognize this is quite the unique use case. Assuming I'm correct here, do we want to wait on a potential Xcode update prior to this work or am I able to continue here and for now the second issue(regarding the late issue recording) will only be logged when running in environments outside of Xcode? I wanted to validate I'm not missing anything here before filing a feedback. |
|
The crash you're seeing should be fixed in the Xcode 26.4 Beta. Our main branch de facto requires Xcode 26.4 (our release/6.3 branch works with earlier Xcode 26.x releases). |
… execution Removed previous changed to HumanReadableEventRecorder. Instead added a computed property on Test.Case indicating whether the test has finished. The computed property accesses modifies a property of a backing class on the Test.Case object. When a testCaseEnded event is posted for a given test case, the hasFinished property is set to true. When issueRecorded events are posted, they are checked to see if the corresponding test case has finished. If the test case has finished, a second late issue is emitted.
Issues that are recorded late directly call the _post function. This removes the need for guarding against recursing and also prevents us from re-computing expensive state (source location, configuration).
Now that we are directly posting the late issue recorded event, the isLate flag is redundant.
|
I ended up going with a slightly different approach by setting a flag on the test case. My main concern is that this approach might be too heavy but it's the simplest logic I could think of. I also call the private post method when emitting the second event so that should prevent any issues with accidental recursion or recomputing state. |
|
So far we've avoided adding mutable state to Something you might want to try instead… install an event handler somewhere under Something like: enum FinishedItem: Sendable, Equatable, Hashable {
case test(Test.ID)
case testCase(Test.Case.ID)
}
let finishedItems = Allocated(Mutex<Set<FinishedItem>>([]))
configuration.eventHandler = { [oldEventHandler = configuration.eventHandler] event, eventContext in
switch event.kind {
case .testEnded:
finishedItems.value.withLock { finishedItems in
_ = finishedItems.insert(.test(event.testID!))
}
case .testCaseEnded:
finishedItems.value.withLock { finishedItems in
_ = finishedItems.insert(.testCase(event.testCaseID!))
}
case .issueRecorded:
let finishedItems = finishedItems.value.rawValue
if let testCaseID = event.testCaseID, finishedItems.contains(.testCase(testCaseID)) {
// Bad test case
} else if let testID = event.testID, finishedItems.contains(.test(testID)) {
// Bad test
}
default:
break
}
oldEventHandler(event, eventContext)
}Note I haven't tried to optimize this code or clean up its structure, and I'm not saying you must implement exactly what I've typed here. This is just an idea about how to shoehorn this functionality into the existing architecture. |
Adds a warning diagnostic message for issues that are recorded after their associated test has finished.
Resolves #1283
Motivation:
Currently, Issues can be recorded after a test has finished. This commonly occurs when asynchronous work completes after the test its associated test has ended. While responsibility falls on the user in this scenario, clear messaging can point them in the correct direction.
From the issue:
Modifications:
hasFinishedtoTest.Casealong with a corresponding backing class that stores whether the test case has finished executing. This flag defaults to falsehasFinishedflag is set to trueChecklist: