TRT-2846: payload-snapshot: classify flakes and informing tests separately from failures - #645
Conversation
|
Hi @not-stbenjam. Thanks for your PR. I'm waiting for a openshift-eng member to verify that this patch is reasonable to test. If it is, they should reply with Tip We noticed you've done this a few times! Consider joining the org to skip this step and gain Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe payload snapshot now classifies JUnit outcomes as gating failures, flakes, or informing results. Regression and summary generation use gating results only, while collection errors, incomplete data, and non-gating visibility are recorded separately. ChangesGating-aware payload snapshots
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant JUnitArtifacts
participant PayloadSnapshot
participant RegressionTracker
participant SummaryGenerator
participant SummaryJSON
JUnitArtifacts->>PayloadSnapshot: provide JUnit XML
PayloadSnapshot->>PayloadSnapshot: parse lifecycle and classify outcomes
PayloadSnapshot->>RegressionTracker: provide parsed results
RegressionTracker->>RegressionTracker: track gating regressions only
PayloadSnapshot->>SummaryGenerator: provide gating and non-gating results
SummaryGenerator->>SummaryJSON: write failure lists and collection status
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 1 warning)
✅ Passed checks (8 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py (2)
954-973: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffFallback probing is serial: 3+
gcloud storage lscalls per step dir.This path triggers exactly on jobs with huge artifact trees (many step dirs), so worst case is dozens of sequential 60s-timeout invocations. Consider probing steps through a small
ThreadPoolExecutor(the error ledger is already lock-protected and scopes are thread-local, so recording stays safe — note that errors raised in worker threads won't land in the caller's scope, so mark recovery explicitly).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py` around lines 954 - 973, The artifact probing loop in the payload snapshot recovery path performs multiple gcloud listings serially, causing slow recovery for many step directories. Update the probing around the step_dirs loop to use a small ThreadPoolExecutor, with each worker probing one step and returning its discovered paths; merge results in the caller while preserving the existing patterns and aggregator-specific recursive probe. Mark recovery explicitly and handle worker failures in the caller because thread-raised errors do not populate the caller’s scope.
1551-1569: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlso extend the generated schema list with the new fields.
The incomplete-snapshot banner is good, but the "summary.json Schema" block emitted a few lines below still lists only
test_failures.blocking[]and never mentionstest_failures.informing[],test_failures.flakes[],data_complete, orcollection_errors[]. AGENTS.md is the first thing an agent reads, so the categories this PR introduces stay invisible there.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py` around lines 1551 - 1569, Update the summary.json Schema block generated by the surrounding payload snapshot reporting code to include test_failures.informing[], test_failures.flakes[], data_complete, and collection_errors[] alongside the existing test_failures.blocking[] entry. Keep the schema output consistent with the new summary fields introduced by the incomplete-snapshot handling.plugins/ci/skills/payload-snapshot/scripts/test_test_classification.py (1)
41-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNo coverage for
<error>testcases.
_is_gatingand_mark_flakesboth treaterroralongsidefailed, but_casecan only emit<failure>. Anerrored=Truevariant would cover the error→flake relabel and error-with-lifecycle="informing"paths.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/ci/skills/payload-snapshot/scripts/test_test_classification.py` around lines 41 - 50, Extend the test helper _case with an errored option that emits an <error> testcase body, alongside the existing failed and skipped variants. Use this helper option in tests covering _is_gating and _mark_flakes so error cases exercise both error-to-flake relabeling and error cases with lifecycle="informing".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py`:
- Around line 1943-1952: When collect_junit is enabled in the payload processing
flow, also invalidate the corresponding regressions.json for each payload tag
whose JUnit output is removed by _invalidate_suspect_junit. Update the logic
around _invalidate_suspect_junit so _track_regressions recomputes regressions
after re-collection, while preserving the existing carry-forward behavior when
collect_junit is disabled.
- Around line 1746-1751: The _build_failed_job_details flow must mark
junit_collection_failed when _job_junit_state reports junit_unavailable,
junit_missing, or junit_unparseable, while continuing to omit
test_failure_count. In
plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py lines 1746-1751,
extend the condition accordingly. In plugins/ci/skills/payload-snapshot/SKILL.md
lines 228-258, retain the existing “Missing data is absent, never empty” bullets
because the implementation will satisfy the documented junit_collection_failed:
true contract.
- Around line 1023-1035: Replace the global before/after error-count check in
the build-log collection flow with the existing _error_scope() mechanism, so
only errors raised while _run_gcloud_bytes for this job executes determine
whether _record_collection_error("build_log_unavailable", ...) is called. Follow
the established usage in JUnitCollector.collect and preserve the existing
command, detail, stage, and job metadata.
- Around line 2515-2520: Update the no_match classification logic using
_GCLOUD_NO_MATCH_PATTERNS so only the specific “URLs matched no objects” outcome
is allowlisted; remove broad absence tokens such as “not found” and “not exist”.
Evaluate authentication, permission, and HTTP 4xx/status tokens before applying
the no-match check so those failures remain in collection_errors.
In `@plugins/ci/skills/payload-snapshot/SKILL.md`:
- Around line 211-216: Update the later “results.json (in junit/ subdirectory)”
section to state that results include failed, error, and flake categories, and
document the status and test_lifecycle fields with their supported values. Keep
its existing description of the file’s purpose and location intact.
---
Nitpick comments:
In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py`:
- Around line 954-973: The artifact probing loop in the payload snapshot
recovery path performs multiple gcloud listings serially, causing slow recovery
for many step directories. Update the probing around the step_dirs loop to use a
small ThreadPoolExecutor, with each worker probing one step and returning its
discovered paths; merge results in the caller while preserving the existing
patterns and aggregator-specific recursive probe. Mark recovery explicitly and
handle worker failures in the caller because thread-raised errors do not
populate the caller’s scope.
- Around line 1551-1569: Update the summary.json Schema block generated by the
surrounding payload snapshot reporting code to include
test_failures.informing[], test_failures.flakes[], data_complete, and
collection_errors[] alongside the existing test_failures.blocking[] entry. Keep
the schema output consistent with the new summary fields introduced by the
incomplete-snapshot handling.
In `@plugins/ci/skills/payload-snapshot/scripts/test_test_classification.py`:
- Around line 41-50: Extend the test helper _case with an errored option that
emits an <error> testcase body, alongside the existing failed and skipped
variants. Use this helper option in tests covering _is_gating and _mark_flakes
so error cases exercise both error-to-flake relabeling and error cases with
lifecycle="informing".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 058cf7eb-af8e-4e54-b773-52a1055bb196
📒 Files selected for processing (8)
.claude-plugin/marketplace.jsondocs/index.htmlplugins/ci/.claude-plugin/plugin.jsonplugins/ci/skills/payload-analysis/SKILL.mdplugins/ci/skills/payload-snapshot/SKILL.mdplugins/ci/skills/payload-snapshot/scripts/payload_snapshot.pyplugins/ci/skills/payload-snapshot/scripts/test_collection_completeness.pyplugins/ci/skills/payload-snapshot/scripts/test_test_classification.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py (1)
1767-1796: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winPreserve suite identity when grouping non-gating tests.
foundis keyed only byname, so same-named tests from different suites collapse into one informing/flake summary entry. Key by suite plus test name and emit the suite identity with the entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py` around lines 1767 - 1796, Update the grouping logic in the lifecycle test-processing block to key entries by both suite identity and test name, preventing same-named tests from different suites from merging. Preserve the existing job aggregation, and include the suite identity in each emitted found entry alongside test_name and jobs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py`:
- Around line 1767-1796: Update the grouping logic in the lifecycle
test-processing block to key entries by both suite identity and test name,
preventing same-named tests from different suites from merging. Preserve the
existing job aggregation, and include the suite identity in each emitted found
entry alongside test_name and jobs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e3d5e168-3b8a-4a24-a3e3-293bdfcfc872
📒 Files selected for processing (1)
plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py
|
/test payload-agent |
|
/ok-to-test |
|
/retitle TRT-2846: payload-snapshot: classify flakes and informing tests separately from failures |
|
@not-stbenjam: This pull request references TRT-2846 which is a valid jira issue. Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
|
The agent is getting confused by the difference between informing jobs and tests, example from the payload-agent presubmit: Please clarify the skill to include the difference. |
… failures A test result falls into one of three categories, and only one can fail a job and therefore reject a payload: flake - same test, same suite, both failed and passed -> does not gate informing - testcase carries lifecycle="informing" -> does not gate failure - failed everywhere, no informing lifecycle -> gates The parser recognised neither. `grep -ci flake` was 0, and the testcase's `lifecycle` attribute was read and discarded, so `_test_results_to_json` emitted every `<failure>` element as a failure. That overstated what could have rejected a payload and, worse, let a non-cause drive regression onset: `first_failed_in` could be set by an informing test, sending analysis to hunt for a culprit PR behind a test that never gated anything. Real example from the payload chain analysed in openshift-eng#644: one conformance run's JUnit contains 11 failures, *all* of them informing UDN tests being stabilized — 0 gating. It previously reported test_failure_count: 11. Changes: - `_TestResult` carries `test_lifecycle` from the testcase attribute. A missing attribute means the test gates; the attribute exists only to opt out. - `_mark_flakes()` relabels a failure as a flake when the same test also passed in the same suite. Grouping is per (suite, name): the same monitor evaluated in `openshift-tests-upgrade` and `openshift-tests` covers two different phases, and a pass in one does not clear a failure in the other. - `results.json` records all three categories with `status` and `test_lifecycle`, so nothing is hidden. - `test_failure_count` counts gating results only; `test_flake_count` and `test_informing_failure_count` are reported separately. - `summary.json` gains `test_failures.informing[]` and `test_failures.flakes[]`. No onset is tracked for either — an onset implies a culprit to find. - Regression tracking considers gating failures only, and logs what it excluded. - payload-analysis: never score informing failures or flakes as candidates, never derive an originating payload from them, never revert for them; report them in their own section stating they cannot cause a rejection, with the one exception worth investigating — a test that harms the cluster it runs on. Tests: 13 new covering flake detection (including the cross-suite case that must NOT be treated as a flake), informing classification, absent-lifecycle gating, and mixed counting; validated against real aggregated and non-aggregated JUnit.
Per-job entries carry a single failure count again. test_flake_count and test_informing_failure_count are removed: neither is a failure count, and having three numbers next to each other invites summing them back into the inflated total this change exists to remove. test_failure_count remains the gating count. The non-gating results are not lost — every one is still recorded by name in the job's results.json with its status and test_lifecycle, and listed under test_failures.flakes[] and test_failures.informing[] in the summary.
…ressions - Use _error_scope() in BuildLogCollector to avoid racing the global error count across concurrent worker threads - Extend junit_collection_failed to cover junit_missing and junit_unparseable, not just junit_unavailable - Remove stale regressions.json when JUnit is invalidated so _track_regressions recomputes from fresh data - Narrow _GCLOUD_NO_MATCH_PATTERNS by removing overly broad "not found" and check auth patterns before no-match to prevent auth errors from being silently classified as benign Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86f08b4 to
51ce340
Compare
|
Rebased onto main (resolved version bump conflicts) and added explicit disambiguation between informing jobs and informing tests to both The agent was conflating the two — reporting informing job failure counts under a heading about informing tests. Both SKILL files now include a table and explanation making clear these are orthogonal concepts:
The payload-analysis SKILL.md also now explicitly says: "Never combine informing-job counts with informing-test lists." |
|
/ok-to-test |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/ci/skills/payload-snapshot/scripts/payload_snapshot.py`:
- Around line 1651-1661: Rename the mixed test_failures buckets in the payload
construction to distinguish scopes: use gating for rejectable test regressions,
and explicit test-lifecycle names such as informing_test_lifecycle and
flake_test_lifecycle for the non-gating buckets. Update all consumers, schema
definitions, and documentation referencing test_failures.blocking,
test_failures.informing, or test_failures.flakes to match the new keys while
preserving their existing data and behavior.
In `@plugins/ci/skills/payload-snapshot/SKILL.md`:
- Around line 202-215: Make the combined job/test gating predicate explicit in
the category table and instructions: only a failed test in a blocking job gates,
and it must not have lifecycle="informing"; informing tests never gate,
including those in blocking jobs, and missing lifecycle continues to mean the
test gates when its job is blocking. In
plugins/ci/skills/payload-snapshot/SKILL.md lines 202-215, align the categories
and missing-lifecycle rule with this predicate. In
plugins/ci/skills/payload-analysis/SKILL.md lines 159-180, exclude failed
informing tests and tests from informative failures from scoring, revert, and
rejection guidance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: df7a4eb8-1ce7-445d-874e-2a9246ac30c0
📒 Files selected for processing (4)
plugins/ci/skills/payload-analysis/SKILL.mdplugins/ci/skills/payload-snapshot/SKILL.mdplugins/ci/skills/payload-snapshot/scripts/payload_snapshot.pyplugins/ci/skills/payload-snapshot/scripts/test_test_classification.py
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/ci/skills/payload-snapshot/scripts/test_test_classification.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
/test payload-agent |
|
/test eval-payload-analysis |
…nforming-tests # Conflicts: # .claude-plugin/marketplace.json # docs/index.html # plugins/ci/.claude-plugin/plugin.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
/lgtm |
|
/approve |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: neisw, not-stbenjam, stbenjam The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Problem
A test result is one of three things, and only one of them can fail a job and therefore reject a payload:
lifecycle="informing"informinglifecycleThe parser recognised neither of the first two.
grep -ci flake payload_snapshot.pyreturned 0, and the testcase'slifecycleattribute was parsed and thrown away —_TestResulthad no field for it. So_test_results_to_jsonemitted every<failure>element as a failure.Two consequences:
test_failure_countoverstates what could have rejected the payload.first_failed_incould be set by an informing test, sending the analysis to hunt for a culprit PR behind a test that never gated anything. That is exactly what happened in the chain analysed in payload-snapshot: fix silent data loss on unauthenticated gcloud (supersedes #641) #644, where the previous analysis chased[Feature:NetworkSegmentation]UDN onset across payloads that contained zero PRs.Informing tests are run in the wild to stabilize them; they are not expected to gate. A missing
lifecycleattribute means the test does gate — the attribute exists only to opt a test out.Measured on real data
One conformance run's JUnit from that chain (run):
test_failure_counttest_informing_failure_countAll 11 were UDN tests being stabilized. None could have failed the job.
And the aggregated report for
5.0.0-0.ci-2026-07-25-162741— the payload whose misattribution started all of this:aggregated-aws-ovn-upgrade-5.0-majorThe entire blocking signal was 3 tests (the CVO invariants), sitting among 14 recorded failures.
Changes
_TestResultcarriestest_lifecycle; the parser reads the attribute._mark_flakes()relabels a failure as a flake when the same test also passed in the same suite. Grouping is per(suite, name)deliberately: the same monitor evaluated inopenshift-tests-upgradeandopenshift-testscovers two different phases, so a pass in one does not clear a failure in the other. Treating those as flakes would silently discard real upgrade-phase failures — includingalert/etcdNoLeaderand the CVO invariants.results.jsonrecords all three categories withstatusandtest_lifecycle; nothing is hidden.test_failure_countcounts gating results only.test_flake_countandtest_informing_failure_countare separate.summary.jsongainstest_failures.informing[]andtest_failures.flakes[]. No onset is tracked for either — an onset implies a culprit to find.payload-analysis: never score informing failures or flakes as candidates, never derive an originating payload from them, never revert for them. Report them in their own section stating they cannot cause a rejection — with the one exception worth investigating: a test that damages the cluster it runs on.Tests
13 new (38 total in this directory), including the cross-suite case that must not be classified as a flake, absent-lifecycle-gates, informing-flake, and mixed counting. Validated against real aggregated and non-aggregated JUnit.
Scope note: what is and isn't verified by real data
The flake convention is confirmed, not assumed.
openshift/originwrites a flake as two testcases with the same name in the same testsuite — one carrying a failure, one not (pkg/test/ginkgo/junit.go:118-138):_mark_flakes()matches that exactly, including the same-suite grouping —s.TestCasesis one suite, so a pass in a different suite is a different test.Note also
s.NumFailed++on the flake path: even the testsuite's ownfailures=attribute counts flakes as failures, so there is no pre-computed count to lean on instead.But I have not seen the flake path fire on real data. Across the JUnit I pulled while investigating this chain:
Eight of the nine are aggregated reports, which cannot contain a flake by construction — the aggregator emits one verdict per test, so a retry appears as
passed N times, failed M timesin the summary text rather than as duplicate testcases. That leaves exactly one file that could have exhibited a flake, and it had none.So: the flake code path is correct with respect to the writer that produces the input, and is covered by unit tests including the cross-suite case it must not match — but reviewers should treat it as unexercised in the wild. It changes no number in this PR's before/after tables. The informing split is what measurably changes results today.
🤖 Generated with Claude Code
Summary by CodeRabbit
--fail-on-incompleteto exit non-zero when unrecovered collection errors remain.