Report a delegated test run as finished exactly once - #1900
Open
wenyt (wenytang-ms) wants to merge 1 commit into
Open
Report a delegated test run as finished exactly once#1900wenyt (wenytang-ms) wants to merge 1 commit into
wenyt (wenytang-ms) wants to merge 1 commit into
Conversation
The delegate command dispatched the test request and returned, leaving completion to arrive later as a build server notification. The build server only reports a test run as finished once it has actually started one, so a Gradle version it cannot drive, a connection that dropped and a target with nothing to run all ended the request with no report at all, and the caller waited for one forever. Wait for the result instead, and treat the end of the request as the end of the run. Reporting is idempotent, so the build server's own report still wins whenever it arrives, and a lost report now degrades to a finished run rather than a run that never ends. Waiting also keeps a per-run init script alive until Gradle has read it, which the old dispatch-and-delete raced with; the debugger is now attached while the request is in flight, since the test JVM is waiting on the debug port for the duration of the run. Runs are queued rather than interleaved. Results arrive through extension-wide commands that carry no run identity and are resolved against the single run this class tracks, so two overlapping runs reported into each other's test items. The client now attaches an origin id that the build server echoes back on the test report, so a report belonging to a run that has already ended can no longer close the run that replaced it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8b7a64f2-3b9f-4328-a173-f4728a1fa95c
wenyt (wenytang-ms)
requested review from
Changyong Gong (chagong) and
Jinbo Wang (testforstephen)
as code owners
July 27, 2026 05:59
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
Author
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A delegated test run could never end.
GradleDelegateCommandHandlerdispatchedbuildTarget/testand returned immediately, so completion could only reach the client as aTEST_REPORTnotification. The build server sends that report only once it has actually started a test run, and several paths end the request before that ever happens:< 2.6, or< 3.5with environment variables) — it logs an error and returns without constructing aTestReportReporterGradleConnectionException/IllegalStateExceptionfrom the outer connectionIn all of these the client is left waiting for a report that never comes: the spinner never stops and the test run stays pending until the window is reloaded.
Two more problems sit next to it:
java.gradle.buildServer.onDidChangeTestItemStatus/onDidFinishTestRun) that carry no run identity, and are resolved against the singlebspContextthe runner tracks. A second run started while the first is in flight reports into the first one's test items.launchWithBspwrites a per-run init script and removes it in afinallyaround the delegate command. Because the command returned on dispatch, the removal raced Gradle actually reading the script.What
Wait for the result.
GradleDelegateCommandHandlernow joinsbuildTargetTestand returns the BSP status code. The end of the request becomes the authoritative end of the run, so a missing report degrades to a finished run instead of one that never ends. This also keeps the init script alive for the whole run — the debugger is now attached while the request is in flight, since the test JVM waits on the debug port for the duration of the run.Report exactly once.
reportFinishedis idempotent, so the build server's own report still wins whenever it arrives, and the request completing is only a backstop.Queue runs.
launchchains onto the previous run and resolves on completion rather than dispatch. The chain survives a failed run, so one failure cannot block everything after it. A run cancelled while queued is reported without being launched, so the queue still advances.Correlate reports with an origin id. The client attaches one to the test request; the build server already echoes it back on
TestReport, soGradleBuildClientforwards it with the notification. A report belonging to a run that has already ended can no longer close the run that replaced it. Reports without an origin id are still accepted, so nothing regresses against an older build server.Notes
IRunTestContext.cancellationTokenis added to the mirroredjava-test-runner.api.ts. It is optional: hosts released before it exists do not set it.StatusCodevalues, matching what the notification path already sends. The XML fallback keeps its existing codes.Testing
extensionunit suite: 117 passing, including 6 new cases covering a missing report, report precedence, a stale report arriving during the next run, serialization, cancel-while-queued, and queue recovery after a failure.jdtls.extbuilds clean (mvn clean package).tsc,eslintandprettierclean.