Repository navigation
ci(java): retry a failed merge-queue IT once, report it to #ci-cleanup, retry dependency downloads - #35505
ci(java): retry a failed merge-queue IT once, report it to #ci-cleanup, retry dependency downloads#35505sonika-shah wants to merge 7 commits into
Conversation
… download Merge-queue IT lanes now re-run a failed test method once after the lane finishes (failsafe rerunFailingTestsCount=1, CI only; default stays 0). A test that passes on the re-run is reported as a flake and no longer ejects the entry; one that fails twice still blocks. Failsafe closes the JUnit launcher session between the two passes, and TestSuiteBootstrap tore the whole stack down on session close, so the re-run pass hit a dead server. Teardown now runs in a JVM shutdown hook. A lane also re-invokes mvn verify (up to 3 attempts, 30s apart) when it failed on a dependency download before writing any test report. ADR:2026-10-15-merge-queue-its-retry-a-failed-test-once
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically. Maintainers can bypass this check by adding the |
Each IT lane annotates the tests failsafe re-ran and passed. After a green merge-queue run, the workflow's final job collects those annotations and posts one message to #ci-cleanup naming the tests, the PRs in the group and the run, the same way Playwright reports its merge-queue flakes. Failed runs post nothing; their failures already reach the dequeue report.
…s failed attempt TestNamespace.prefix is deterministic per JVM, class and method, and the failsafe re-run happens in the same JVM, so it reused the failed attempt's names. Entities that attempt created without tracking them as roots were still there, and the re-run failed with 409 "Entity already exists" instead of passing. TestNamespaceExtension now suffixes the method id with __retry the second time it sees a test's unique id; first attempts are unchanged.
The previous commit only renamed method-level namespaces. 94 call sites build a TestNamespace by hand (class-level @BeforeAll fixtures, shared statics), and JUnit re-runs the failed test's @BeforeAll in the re-run pass, so those still reused the failed attempt's names and hit 409. The bootstrap now marks the second launcher session in the JVM as the re-run pass, and TestNamespace.prefix changes for every namespace from then on. Replaces the extension-level __retry suffix.
…splits its report Re-running a parallel JUnit lane, failsafe can write a retried test's pass into another class's report file while the first attempt's <failure> stays in the test's own (seen locally on 2 of 10 retried tests). Maven's verdict is right, but classify_test_outcome.py counted the stale <failure>, so the lane job would still fail. It now treats a test that passed in any report as passed, and the flaky annotation reports a failure-plus-pass as a flake.
…download retry When Central answers "not found", Maven writes a *.lastUpdated marker and won't ask again until the update interval passes, so attempts 2 and 3 failed instantly with the same error (the #35042 ejection's log shows Maven honouring those markers). Delete them before each retry. Also: retry only an attempt that failed within 10 minutes (a long attempt with no report was a hang, and must stay inside the job timeout), judge each attempt by its own output, and append every attempt to maven.log instead of overwriting the first failure.
…ive a retry The parallel lane shares one server, database and search index, and the merge queue now re-runs a failed test once. Write down the three rules the recent flakes broke, in the IT README (for people) and in the Java rules file (for coding agents): assert only on your own data; write only to your own data or run alone with the GLOBAL resource lock / global-state lane; and build every name with TestNamespace and keep no cross-method state, so the retry can re-run the test.
Code Review ✅ Approved🟡 Medium risk · The merge-queue retry changes could let intermittent integration-test failures pass, allowing a real regression to merge despite flake reporting. Adds a CI safety net for merge-queue IT lanes: failed tests get one re-run against the same server and flakes are reported to #ci-cleanup, while dependency-download failures get up to three retries after clearing Maven's cached not-found markers. No issues found. VerificationRules No rules evaluated Functional validation Not enabledAuto-approval Not enabledFor more verification details, comment OptionsDisplay: compact → Showing compact verification results. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
Summary
Since #34892 the Java ITs run only in the merge queue, so one flaky test or one network blip ejects a PR and restarts everything queued behind it. In the 12 hours after #35469, 6 PRs were ejected and none of them by their own change: 4 by flaky tests, 2 by Maven Central dropping the connection before any test ran.
This adds a safety net for the merge-queue IT lanes. It does not replace fixing the flaky tests.
<flakyFailure>,Flakes: N) and the lane stays green. If it fails twice the lane still fails. Green runs cost nothing extra. CI-only via-DintegrationTests.rerunFailingTestsCount=1; the pom default stays0, so local runs are unchanged.TestSuiteBootstraptore the whole stack down on session close, so the retry hit a dead server. Teardown now runs in a JVM shutdown hook.TestNamespace.prefixis deterministic per JVM/class/method, so the retry reused the failed attempt's names and hit409 Entity already existson whatever that attempt created.TestNamespaceExtensionnow adds__retryto the method id the second time it sees a test's unique id; first attempts are unchanged.mvn verify(up to 3 attempts, 30s apart) only when the attempt failed on a dependency download within 10 minutes and wrote no test report. Before each retry it deletes~/.m2/**/*.lastUpdated: Maven caches a "not found" there and would otherwise never ask Central again, which is exactly what the Fixes #30361: Show failed rows sample when a test passes within its threshold #35042 ejection's log shows. Test failures and hangs are never retried this way, and every attempt is kept inmaven.log.Decision record:
ADR:2026-10-15-merge-queue-its-retry-a-failed-test-once.Verified locally (postgres-opensearch, parallel lane)
/v1/system/version; this PRFlakes: 1,<flakyFailure>in the XML, BUILD SUCCESSns.prefix, then fails its first attempt__retrysuffix: retry fails409 Entity already exists→ BUILD FAILURE. With it: retry creates…__retry, passes →Flakes: 1, BUILD SUCCESSConnection refused→ BUILD FAILURE, so the harness change is requiredmvnthat models Maven's not-found cache (attempt 1 writes*.lastUpdated; a later attempt that finds one fails without touching the network)maven.log✅ · download error then compile error → no second retry ✅ · attempt over 10 min with no report (hang) → not retried ✅. (The first version of this test used a stub that cached nothing, which is why it missed the not-found case.)Details
Why the retry needs the harness change. Surefire 3.1.2's
JUnitPlatformProvider.invokeAllTestsruns all tests, callscloseLauncher()(which closes theLauncherSession), then opens a new session and re-executes only the failed test IDs.TestSuiteBootstrap.launcherSessionClosed()calledcleanup(), which stops the app and every container. On the second sessionSTARTEDis alreadytrue, so it skipped boot and the retried test ran against nothing. The bootstrap now registerscleanupas a shutdown hook once boot succeeds, and no longer overrideslauncherSessionClosed.forkedProcessExitTimeoutInSecondsis 180 so the fork can finish that teardown before Surefire halts it. External mode (JPW_MODE=external) is unchanged: it never boots, so it registers no hook.Download retry condition. Retry only if
exit != 0, there are nofailsafe-reports/TEST-*.xmlfiles, and the maven log matchesCould not (resolve dependencies|transfer artifact|find artifact). This retries the wholemvn verifyrather than a separate pre-fetch step, because some of the artifacts that failed (e.g.junit-platform-launcher) are resolved by Surefire at test start-up, which a pre-fetch wouldn't cover.Ejections this would have prevented (all after #35469):
MetricResourceIT: approval workflow ran 6s after the 5-minute waitTableCertificationPropagationIT: async cascade update dropped on a version conflictOntologyStructureIT: MySQL deadlock in edit-lock acquireTaskResourceIT: count vs list over shared fixturesCould not transfer artifact … Connection reset/Could not find artifact … in centralThe flaky tests are being fixed separately.