Skip to content

Per-test known issues: green jobs for known failures, honest badges - #14

Open
yarikoptic wants to merge 7 commits into
masterfrom
claude/confident-gates-b1e6lv
Open

yarikoptic wants to merge 7 commits into
masterfrom
claude/confident-gates-b1e6lv

Conversation

@yarikoptic

@yarikoptic yarikoptic commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Most of the matrix is red for reasons we already understand. That hides new failures inside an already-red cell, and nothing notices when a known failure goes away (e.g. a git-annex daily build that fixes something). This PR records known failures per test and judges each cell against that list.

How a cell is judged

Cell outcome CI job Badge / status page
all tests pass green passing
every failure covered by a known issue green failing (known), still red
any failure no issue covers red N new failing
suite timed out or died, or its totals disagree with the parse red incomplete
a known issue's tests all pass green, with a notice on the run +N fixed? appended

What's in it

  • evals/ replaces .github/ as the home for these files. evals/matrix.yaml (moved from .github/) says what runs. The new evals/known-issues.yaml records what is expected to fail. Each issue has an id, backend globs × targets, test ids (with prove-style ranges like chown/00.t#107-108,118-120, or "*" for a whole cell not yet narrowed down), expect: fail|flaky, tags from a validated list (fs-limitation, fs-divergence, test-assumption, build-config, harness, fixed-upstream, needs-triage, …), an optional fixed-in, and links. The initial list comes from run 36050917994.

  • bin/ci/collect-results.py turns each suite's own output into per-test results:

    • TAP for git: the exact stream prove parses, kept per script as test-results/<script>.tap by a small prove --exec hook (bin/ci/git-prove-exec.sh) that wraps git's own t/run-test.sh;
    • TAP for pjdfstest (prove -v, now the default) and stress-ng (the driver now prints TAP lines);
    • tasty's console output for git annex test. Upstream TODO for a TAP log: provide TAP protocol logging for 'annex test'.

    Each parse is cross-checked against the suite's own totals and exit code. A mismatch makes the cell incomplete (red), so a parser that drifts after a format change cannot silently drop failures.

  • bin/ci/known_issues.py has four subcommands:

    • validate checks the YAML against the matrix;
    • check judges one cell and writes verdict.json, a step summary, and annotations;
    • gotchas regenerates GOTCHAS.md's "Known issues" section, which replaces the hand-kept KNOWN_RED list;
    • draft prints issue stubs for new failures.
  • Workflow (test.yaml):

    • The suite step is continue-on-error, and bin/ci/check-cell.sh decides the job result.
    • It is skipped on cancelled runs.
    • Validation runs as its own job.
    • results.tsv and verdict.json go into the per-cell artifacts.
    • update-status.py and render-report.py show states, which issues matched, new failures, and a Known-issues section.
  • Shellcheck: bin/ci/shellcheck.sh finds scripts by shebang, the same discovery as improveit's shellcheckit, instead of a fixed glob. Master's checks workflow runs it via run-checks.sh shellcheck. All 25 scripts are clean.

Findings from building the initial list

  • BeeGFS git-annex: down to 8 tests (export/import and git-remote-annex exporttree) that fail with a busy-device error on rename, identical on 7.4.6 and 8.1.0. Tagged build-config, fixed-upstream and needs-triage: the OsPath build is the working hypothesis.
  • Loop git-annex cells: the loop image is 100 MB, the same as git-annex's default annex.diskreserve, so many tests log "not enough free space". This is recorded as a whole-cell harness + needs-triage issue and not fixed here. Until the image is enlarged or the reserve lowered, the ext4 control row can't catch a git-annex regression.

Review

Three independent reviews covered the Python parsers and classifier, the shell and workflow wiring, and the data and docs. All findings were addressed in 539c110, including:

  • prove's padded (Wstat: column, which meant no #exit rows were ever produced;
  • a repeated TAP number that could overwrite a not ok;
  • cancelled runs being recorded as incomplete;
  • root-cause hypotheses that had been stated as facts.

What real CI runs caught

The cross-checks did their job: each problem marked the affected cells incomplete (red) rather than hiding a failure.

  • git, attempt 1 (--verbose-log .out files): t0202 runs a Perl Test::More script, which adds a second TAP numbering to the .out file. Command output lacking a trailing newline also glues onto the next result line (Hello Worldok 12 - …).
  • git, attempt 2 (--write-junit-xml): git v2.55.0, and still master, calls an undefined write_junit_xml_testcase for scripts that skip everything. That garbles their TAP and fails make prove.
  • git, now: the per-script copy of prove's own TAP stream. On a local v2.55.0 build over the full t0*/t1* selection CI runs, all 10,359 test points reconcile with prove, t0202 included. It also handles synthetic scripts that fail, fix a known breakage, keep a known breakage, skip everything, or die midway.
  • git-annex: merging stderr into the logged pipe spliced git-annex's messages into tasty's result lines. Only stdout is logged now.

pjdfstest -v (including vfat's bail-out) and the stress-ng TAP worked on the first run. Any failures the initial list missed will show up as named "new failures" in the step summary; known_issues.py draft prints stubs for them.

Testing

  • The git-annex parser reproduces tasty's own counts on the real CI logs of the vfat and BeeGFS cells.
  • The TAP adapters and every classification path (known, flaky, new, not reproduced, stale, fixed-in, timeout, never ran, exit-code/parse disagreement) were exercised on synthetic input.
  • The status page render was dry-run locally, with a screenshot check.
  • bin/ci/run-checks.sh (shellcheck + bats), codespell, and known_issues.py validate --gotchas pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3

Most of the matrix is red for reasons we already understand, which both
hides new failures inside a red cell and leaves no way to notice when a
known one goes away. Replace the hand-kept KNOWN_RED list (and its copy
in GOTCHAS.md) with a single, validated source:

- .github/known-issues.yaml: issues keyed by id, each naming backend
  globs x targets, the failing test ids (prove-style ranges allowed; "*"
  for a not-yet-narrowed whole cell), expect: fail|flaky, tags from a
  declared vocabulary (fs-limitation, fs-divergence, test-assumption,
  build-config, harness, fixed-upstream, needs-triage, ...), optional
  fixed-in, and links.  Seeded from run 36050917994.
- bin/ci/collect-results.py: per-test results.tsv from what each suite
  already emits -- TAP for git (.out/.exit), pjdfstest (prove -v) and
  stress-ng (our driver now prints TAP), tasty's console tree for
  git-annex -- each cross-checked against the suite's own totals so a
  drifting parser reports "incomplete" instead of hiding failures.
- bin/ci/known_issues.py: validate; check one cell (job green iff every
  failure is covered, red on new failures or incomplete runs; notices
  for issues that no longer reproduce, warnings for stale patterns);
  regenerate GOTCHAS.md's "Known issues" section; draft stubs.
- The badge/report keep telling the truth: "failing (known)" is still
  red, "N new failing", "incomplete", "+N fixed?".

Seeding surfaced that both loop git-annex cells mostly fail on
annex.diskreserve (100 MB default) vs the 100 MB loop image -- a
harness issue, recorded as such rather than fixed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3
… fixes

evals/ now holds the data describing which filesystem evaluations run
(matrix.yaml) and how they are expected to come out (known-issues.yaml):
neither is GitHub-specific -- bin/ci reads both locally and in the VM,
and a future non-GitHub CI would share them.

Shellcheck in CI: bin/ci/shellcheck.sh checks every tracked file whose
first line is a sh/bash shebang (same discovery as improveit's
shellcheckit), so provision/*.sh and bin/eval-under* are covered too;
.github/workflows/shellcheck.yml runs it. CLAUDE.md and README point at
it instead of a hand-written glob.

Fixes from three independent reviews of the previous commit:

- collect-results: prove pads the "(Wstat:" column, so no #exit rows
  were ever produced for pjdfstest; a repeated TAP number could turn a
  "not ok" into "ok" -- now the failure is kept and the cell reported
  incomplete.
- known_issues: header lines must be "# key: value" (ids starting with
  '#' were dropped); reversed ranges are a validation error; draft
  emits ids validate accepts.
- workflow: skip the check on cancelled runs (they were recorded as
  "incomplete"); dump failure logs after install failures again;
  validation is its own job so a stale GOTCHAS.md cannot stop the
  matrix; the output dir is defined once, in the job env.
- run-under: chown the output dir up front (a killed run left it root-
  owned), and no recursive chown of an overridable path.
- report/badges: XML-escape badge text; issue anchors prefixed to avoid
  colliding with cell anchors; cells without a verdict count as
  unexpected in the overall badge.
- data: unconfirmed root causes (diskreserve on ext4, OsPath on BeeGFS)
  are tagged needs-triage and worded as hypotheses; the whole-cell loop
  issue names its two backends instead of loop-*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3
Master meanwhile gained .github/workflows/checks.yaml (shellcheck +
bats, via bin/ci/run-checks.sh), so a separate shellcheck workflow
would be a duplicate. Drop it; run-checks.sh now delegates to
bin/ci/shellcheck.sh, which finds scripts by shebang instead of a fixed
glob (24 today, including run-checks.sh and install-check-deps.sh), and
honours the same SHELLCHECK override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3
Every git cell came out "incomplete" (duplicate TAP numbers in
t0202-gettext-perl.sh), and both loop git-annex cells had tasty counts
that did not add up. Both were the new cross-checks doing their job:

- git: t0202 runs a Perl Test::More script inside a test, and
  --verbose-log records that script's own "ok 1 ..." lines in the .out
  file next to git's -- two numberings in one stream. Read per-test
  results from git's own --write-junit-xml output (t/out/TEST-*.xml),
  which test-lib writes from its bookkeeping, not from anything a test
  prints; keep the .out plan line and .exit only to report scripts that
  die. Verified against a local v2.55.0 build: 292/292 tests on a
  subset including t0202, plus failure, fixed-breakage, skip-all and
  died-midway scripts.
- git-annex: run-under.sh merged stderr into the logged pipe, so
  git-annex's "not enough free space ..." landed between tasty's
  "retrieveKeyFile:" and its "OK". Log stdout only (every suite reports
  there; prove's bail-out message included), leaving stderr on its own
  stream as before this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3
The JUnit switch (fb30f8c) turned every git cell "incomplete" again:
git v2.55.0 (and master) calls an undefined write_junit_xml_testcase
for scripts that skip everything, so --write-junit-xml garbles their
TAP and fails `make prove`. The --verbose-log .out files are no
alternative either: besides nested TAP (t0202), command output without
a trailing newline glues onto the next "ok N" line ("Hello Worldok
12 - ...").

bin/ci/git-prove-exec.sh is a prove --exec hook that runs each script
through git's own t/run-test.sh and tees its stdout -- exactly the TAP
prove parses -- to test-results/<script>.tap; the git adapter reads
those with the same TAP parser as the other suites. Verified on a local
v2.55.0 build over the full t0*/t1* selection CI runs: 10359/10359
test points reconcile with prove, t0202 included, plus synthetic
fail / fixed-breakage / open-breakage / skip-all / died scripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3
Code:
- Fix status.json "version": the verdict's tool version overwrote the
  backend version, but only on re-run cells.
- Drop features nobody uses yet: `fixed-in` (and the per-adapter version
  scraping behind it) and `expect: flaky`.
- known_issues.py is the one home of the matrix naming rules
  (matrix_cells: slug, label) and the cell states; update-status.py and
  render-report.py import them instead of re-deriving them.
- Issues are an `Issue` dataclass instead of dicts with private keys;
  one `gotchas` entry point, staleness checked by `validate --gotchas`.
- collect-results.py: adapters dispatched from a table, no unused
  arguments or outputs; named constants for magic numbers.
- git-prove-exec.sh writes <script>.exit itself, so git's #exit rows no
  longer depend on --verbose-log being passed.
- render-report.py owns the badge wording; render-badge.sh maps states
  to colours.
- New scripts use usage() heredocs (CLAUDE.md rule 4).

Checks:
- Unit tests for the parsers (trimmed real suite output in tests/data/)
  and the classifier, plus known-issues validation, now run from
  bin/ci/run-checks.sh in the fast `checks` workflow; the separate job
  in test.yaml is gone.

Docs: each fact kept in one place -- the job-vs-badge semantics in the
README table, the per-cell output files at cell_output_dir(), test id
formats in the known-issues.yaml header -- and comments cut to the
non-obvious why. Unconfirmed root causes are tagged needs-triage only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8dGtiFfZ6gRt2bWj39Qv3

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants