CAMEL-24290: ci - report tests that only passed after a retry - #25598
CAMEL-24290: ci - report tests that only passed after a retry#25598ammachado wants to merge 5 commits into
Conversation
Surefire retries failing tests: surefire.rerunFailingTestsCount defaults to
2 in the `full` profile of parent/pom.xml, and both CI systems pass it again
explicitly. A test that fails and then passes within those attempts leaves
the build green and leaves nothing in the console output, so today the retry
is invisible.
parse_errors.sh cannot cover this for two independent reasons: it parses the
.txt reports, which carry no flake data at all, and it only runs inside the
build-failure branch, while a recovered flake exits 0.
Add collect-flakes.py, invoked on the always-path of incremental-build.sh.
It walks **/target/{surefire,failsafe}-reports/TEST-*.xml and reports every
<testcase> carrying <flakyFailure>/<flakyError>. Tests with <rerunFailure>
failed every attempt and already fail the build, so they are excluded.
Two outputs: a section appended to the PR comment and job summary, and
flakes.json uploaded as a workflow artifact. Develocity publishes build
scans only when authenticated, so fork PRs produce none; that artifact is
the only per-PR record of flakiness available.
No time figure is reported. Surefire records no per-attempt timing and
<testcase time> reflects only the final successful attempt, so a
minutes-lost number would understate timeout-driven flakes specifically.
XML is parsed with defusedxml, declared inline via PEP 723 and resolved by
uv. A pre-parse byte scan for <!DOCTYPE is not sufficient: it misses a
UTF-16 document, where the marker is interleaved with NUL bytes.
testdata/TEST-utf16-doctype-rejected.xml pins that case.
Failures are logged and skipped; this step must never fail a job whose
verdict is already decided.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
Correctness: - Pass forbid_dtd=True explicitly. defusedxml only forbids entity *declarations* by default, so a bare <!DOCTYPE .. SYSTEM ..> parsed cleanly even though the docstring, the test class name and the docs all claimed such a report is refused. - Read flaky attempts in document order. Collecting per tag returned the first flakyFailure even when a flakyError came first, so the "First failure" column named the wrong attempt, hiding the timeout that is typically the real cause. - HTML-escape failure messages. GitHub treats "expected: <true> but was: <false>" as raw HTML and strips it, emptying the column for exactly the assertion messages that produce most flake reports. JDK matrix attribution: - Name the JDK in the section and record it in flakes.json (flake-label action input, --label on the script). The PR-comment artifact uploads with overwrite: true on the grounds that content is identical across the matrix; flake data is the one part that is not, so an unattributed section left the reader unable to tell which JDK a flake came from. - Upload flakes.json from main-build.yml as flakes-main-java-<version>. The action already produced it there and it was being discarded, even though main-branch flakes are the least noisy signal available. Other: - Replace the **/target glob with a pruned os.walk. After a full build every module's target/ holds thousands of class and generated-source directories that cannot contain a report. - Write the "Tested modules" header before anything else appends to the job summary, so the reader gets what was built before what happened while building it. - Fix "1 retried attempts" and a test docstring naming a command that fails with ModuleNotFoundError. Adds three tests (18 total) plus fixtures for attempt ordering and for a DOCTYPE that declares no entities of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apupier
left a comment
There was a problem hiding this comment.
I think it would be easier to maintain and easier to leverage reported flaky tests by reusing the same tool than for Jenkins. Which is develocity.
davsclaus
left a comment
There was a problem hiding this comment.
This is a rules-and-conventions review from OSS Helper — it does not replace CodeRabbit, Sourcery, SonarCloud, or a dedicated static analyzer.
Overall this is a well-scoped, well-tested CI visibility change. To verify rather than just read it, I checked out the branch, ran all 18 unit tests locally (all pass), confirmed the forbid_dtd=True XXE defense actually rejects the UTF-16-hidden-DOCTYPE fixture, confirmed reportRecoveredFlakes genuinely runs on the always-path of incremental-build.sh (after the build's exit code is captured, before the final exit $ret), confirmed the new astral-sh/setup-uv SHA pin matches the v10.0.1 tag, and confirmed both commits carry the Co-Authored-By trailer per project convention. JIRA CAMEL-24290 is Open/Unassigned and the PR's "implements one bullet, doesn't claim the umbrella ticket" framing matches that.
One correctness issue below (inline), plus two non-blocking notes:
Recommend confirming before merge: this is a fork PR, and the only checks that ran are pr-ci-scripts-validation.yml (isolated unit tests) plus the PR-id uploader/dependency-review. pr-build-main.yml — the workflow that actually exercises the new flake-label input, the new install-uv composite-action step, and the new "Upload recovered-flake report" step — has no run at all for this branch yet (checked via gh run list --workflow=pr-build-main.yml), most likely GitHub's approval gate for fork PRs touching .github/workflows/** / .github/actions/**. The unit tests validate collect-flakes.py's logic in isolation but can't prove the composite-action wiring (uv landing on PATH, FLAKE_LABEL threading through, the hashFiles('flakes.json') upload gate) works on a live runner. Worth a maintainer approving/triggering that run before merging.
Minor/optional: in pr-build-main.yml, the new "Upload recovered-flake report" step isn't gated on !matrix.experimental the way the "Save PR number and test comment" step is. Harmless today (the matrix has no experimental: true entry), but worth a thought if an experimental JDK entry is ever added.
Claude Code on behalf of Claus Ibsen (@davsclaus)
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
- Escape module, class, and test name through _cell() in the markdown table row, not just the failure message. A test name containing '|' (a JUnit 5 @ParameterizedTest display name, or a Camel URI/DSL parameterized test) otherwise splits the row into the wrong columns. - Gate the "Upload recovered-flake report" step on !matrix.experimental for consistency with the other post-build steps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Thanks for the suggestion, @apupier. I checked whether Develocity could stand in for this instead of the custom flake report, and it can't cover the case this feature is actually for.
So for a fork PR (the majority of external Camel contributions), and the exact audience that can't see Jenkins internals and needs inline visibility most - no Develocity scan ever gets published. There's nothing to leverage there. Where I think you're right: for merged builds on trusted ASF Jenkins infra, Develocity's cross-build flaky-test view is a better long-term signal than one job's retry report. That's an argument for wiring Develocity on the trusted side too, not a substitute for PR-time visibility on the fork side. I can file a follow-up for that if it's useful. Claude Code on behalf of Adriano Machado (@ammachado) This reply was generated by an AI agent and may contain inaccuracies. Please verify before relying on it. |
There was a problem hiding this comment.
I'm a bit sceptical to the fact that contributors will benefit to the "flaky but still passing" report, especially given that there are a lot of them today. Either they will look and can be confused because it is an existing flaky one, or they won't care as it is passing anyway.
The "flaky but failing" are already looked by almost nobody.
Anyway, if i'm alone to think that that's fine and we can go ahead after other pints clarified/tackled.
Can you try to have these jobs triggered before we merge the PR to ensure that it is not breaking the main branch please?
| # honours that block. Installed here rather than in the caller so the action | ||
| # stays self-contained for downstream repos that reuse it. | ||
| - id: install-uv | ||
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 |
There was a problem hiding this comment.
What is the advantage/requirement to use uv compared to the "official" Python setup action https://github.com/actions/setup-python ?
There was a problem hiding this comment.
actions/setup-python only installs a Python interpreter — it has no notion of a script's own dependencies. collect-flakes.py declares its one runtime dependency (defusedxml>=0.7.1) inline via PEP 723:
# /// script
# requires-python = ">=3.9"
# dependencies = ["defusedxml>=0.7.1"]
# ///uv run --quiet collect-flakes.py (see incremental-build.sh:447) reads that block and materializes an ephemeral, cached venv with exactly that dependency for the single invocation — no separate pip install step, no committed requirements.txt, and no risk of the dependency drifting from what the script actually declares it needs. With setup-python we'd still need a manual pip install defusedxml step to keep in sync by hand. That's the tradeoff captured in the comment on lines 55-57.
Claude Code on behalf of Adriano Machado (@ammachado)
This reply was generated by an AI agent and may contain inaccuracies. Please verify before relying on it.
Pins `version`/`python-version` on the setup-uv step so the recovered-flake collector runs against a fixed uv (0.12.5) and Python (3.11) rather than whatever "latest" resolves to at run time, even though the setup-uv action itself is already SHA-pinned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the two points from this review: CI not triggered: Root cause is Value of the "flaky but still passing" report: Fair concern, and I don't have a strong counter beyond what's in the original commit message — for a first-time or occasional contributor whose PR fails, then passes on retry, seeing why inline (rather than having to know Jenkins exists, find the right job, and dig through console logs) lowers the bar to understanding "was this me or was this the test." I agree the "flaky but failing" case is the one nobody reads today; I'm not trying to fix that habit with this PR, just make the passing-after-retry case visible where a PR author will actually see it. Open to gating this behind something narrower (e.g. only show it if the flake count is small, or only on first-time-contributor PRs) if that addresses the noise concern — let me know if you'd like that adjustment before merge. Claude Code on behalf of Adriano Machado (@ammachado) This reply was generated by an AI agent and may contain inaccuracies. Please verify before relying on it. |
…patch The workflow_dispatch checkout step only set `ref`, so it defaulted to checking out `apache/camel` itself. That works for a maintainer's own branch but not for a fork PR branch, which is the norm for external contributions per the project's fork-only push policy. Add a `pr_repo` input (defaulting to `github.repository`, i.e. unchanged behavior) and pass it through to `actions/checkout`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Description
Part of CAMEL-24290 (improve CI to support larger workload). This is the visibility step: it makes an existing, currently invisible cost measurable before anything is changed about how tests run.
The problem
Surefire retries failing tests.
surefire.rerunFailingTestsCountdefaults to2in thefullprofile ofparent/pom.xml(activated by!quickly), and both CI systems pass it again explicitly (Jenkinsfile:17,install-mvnd/action.yml:71).A test that fails and then passes within those attempts is a recovered flake. The build goes green, nothing appears in the console output, and the retry leaves no trace. We are already paying up to 3x the runtime of every flaky test, and the retry is precisely the thing that stops anyone noticing.
parse_errors.shcannot cover this, for two independent reasons:.txtreports, which contain no flake data at all (verified:grep -c "Flake"on a surefire 3.5.6.txtreport with a known flake returns0).if [[ ${ret} -ne 0 ]]branch, and a recovered flake exits0.The change
collect-flakes.pyruns on the always-path ofincremental-build.sh. It walks the reactor fortarget/{surefire,failsafe}-reports/TEST-*.xmland reports every<testcase>carrying<flakyFailure>/<flakyError>. Tests with<rerunFailure>/<rerunError>failed every attempt and already fail the build, so they are deliberately excluded.The walk prunes rather than globbing: after a full build every module's
target/holds thousands of class and generated-source directories, none of which can hold a report, so**/target/...would descend into all of them.Two outputs:
flakes.json, uploaded asflakes-java-<version>on PRs andflakes-main-java-<version>onmain..mvn/develocity.xmlpublishes build scans only when authenticated, so fork PRs produce no Develocity data. This artifact is the only per-PR record of flakiness we can accumulate.The section names its JDK
The PR-comment artifact uploads with
overwrite: trueacross the JDK matrix, justified by the existing comment inpr-build-main.yml: the content is identical across entries because the same modules are tested. Flake data is the one part where that does not hold. If JDK 17 flakes and JDK 25 does not, whichever entry finishes last decides what the comment shows.Rather than restructure the comment/artifact strategy in this PR, the section names the entry it came from (
flake-labelon the action,--labelon the script, also recorded inflakes.json):Last writer still wins, so the comment shows one JDK's flakes. What the label buys is that a shown section is attributable, and that an empty section can no longer be silently confused between "no flakes" and "the other JDK overwrote it". The per-JDK
flakes-*artifacts remain the complete record, and the label inflakes.jsonlets aggregation distinguish a test that only flakes on one JDK from one that flakes everywhere.Deliberate non-goals
No time figure is reported. Surefire records no per-attempt timing, and
<testcase time>reflects only the final successful attempt. Estimating cost from it would systematically understate timeout-driven flakes, which are the common kind. Counts and test names are honest; a fabricated minutes-lost number would poison the evidence this is meant to gather.Nothing about test execution changes. No test is skipped, quarantined, or retried differently. This PR only reports.
Notes for reviewers
uv run(PEP 723 describes inline dependency declaration similar tojbang).uvis installed by the action (astral-sh/setup-uv, pinned by SHA) so the action stays self-contained for downstream repos that reuse it. Both theuvversion and the Python version it resolves against are pinned explicitly (version: "0.12.5",python-version: "3.11") — the action's own SHA pin does not fix whatuvitself installs at run time, sinceversiondefaults to "latest" absent apyproject.toml/uv.tomlpin.defusedxmlrather than the stdlib, withforbid_dtd=Truepassed explicitly.defusedxmlforbids entity declarations by default but not a bare<!DOCTYPE .. SYSTEM ..>, so the default alone does not enforce "Surefire never emits a DOCTYPE, therefore any report carrying one did not come from the build".testdata/TEST-doctype-no-entities-rejected.xmlpins this.<!DOCTYPEis not sufficient either: it misses a UTF-16 document, where the marker is interleaved with NUL bytes, and the entity then expands normally.testdata/TEST-utf16-doctype-rejected.xmlpins that case. (The stdlib-only alternative of installing pyexpat entity handlers is unavailable:ET.XMLParser.parserno longer exists on current CPython.)findall("flakyFailure") + findall("flakyError")) returns them grouped by tag, so the "First failure" column named the wrong attempt whenever the two kinds mixed, hiding the timeout that is typically the real cause.expected: <true> but was: <false>as raw HTML and strips it, which emptied the column for exactly the assertion messages that produce most flake reports.main()always returns0, because by that point the build verdict is already decided.TEST-utf16-doctype-rejected.xmlshows as binary in the diff. That is expected for UTF-16 with a BOM.Verification
pr-ci-scripts-validation.yml(path filter extended to.github/actions/incremental-build/**).GITHUB_STEP_SUMMARYset and unset states.Path,strand relative.roots, at root, one-level and three-level module depths.Target
mainbranch)Tracking
CAMEL-24290. Note the JIRA issue is currently Unassigned and this PR does not claim it; it implements one bullet from the ticket's list.
Apache Camel coding standards and style
I checked that each commit in the pull request has a meaningful subject line and body.
I have run
mvn clean install -DskipTestslocally from root folder and I have committed all auto-generated changes.Not run, and not applicable: this PR touches only
.github/**(Python, YAML, shell, Markdown). No Java sources, POMs, or generated files are involved, so no auto-generated content can change.pr-build-main.ymlitself carriespaths-ignore: .github/**for exactly this reason.AI-assisted contributions
Co-authored-bytrailers) and the PR description identifies the AI tool used.Written with Claude Code (Claude Opus 5 and Claude Sonnet 5, across commits). Every commit carries a
Co-Authored-Bytrailer.Claude Code on behalf of @ammachado