Skip to content

test(parity): bidirectional snapshot ratchet for the gap suite (#797)#6755

Open
TheHypnoo wants to merge 6 commits into
mainfrom
feat/gap-snapshot-ratchet
Open

test(parity): bidirectional snapshot ratchet for the gap suite (#797)#6755
TheHypnoo wants to merge 6 commits into
mainfrom
feat/gap-snapshot-ratchet

Conversation

@TheHypnoo

@TheHypnoo TheHypnoo commented Jul 22, 2026

Copy link
Copy Markdown
Member

Second of the base-hygiene changes drawn from oxc's workspace (after #6754). Adopts their conformance ratchet: a generated snapshot, committed, diffed by CI in both directions.

The problem

run_gap_tests.sh gated with comm -23 <failures> known_failures.json — "fail on any failure not already triaged". That is one-directional. A listed test that starts passing produces no signal at all, so its entry survives the bug it was tracking, and the file decays into orphans nobody can revalidate. That decay is exactly what #797 asks someone to clean up by hand.

The change

scripts/gap_snapshot.py generates and checks test-parity/gap_snapshot.json, listing every gap test that is not passing. A test absent from the file is expected to pass. CI fails on any divergence in either direction — a new failure, a listed test that now passes, or a status change. Accept the current state with:

UPDATE_SNAPSHOT=1 ./scripts/run_gap_tests.sh

and commit the diff. Because an improvement is a red build until it is recorded, an orphaned entry becomes impossible rather than something to audit periodically.

Design points worth reviewing:

  • Only non-passing tests are stored. "Fails and is not listed" already means regression, so listing the ~370 passing tests would quadruple the file without adding a single detection. The snapshot stays small enough to read in a PR diff.
  • node_fail / skipped are recorded explicitly instead of being dropped from the denominator. A test the oracle stopped covering becomes a visible diff — that hole is how a Node 22 pin hid 14 tests for months (runtime: DisposableStack / Symbol.dispose surface incomplete (.disposed returns undefined) #6364).
  • Statuses only, never program output. The harness truncation marker embeds an exact line count (TRUNCATED … (total: N)) that is not stable across runs for a flapping test (CI: stop truncating gap-suite output, add per-test summary #796).
  • Shard-safe. Check and update act only on tests present in the report, so a --shard N/8 run never judges or rewrites its siblings' entries. An entry is pruned only when its test file is gone from disk, which is decidable from any shard.
  • Triage metadata carries across a status change (parity_fail -> crash is the same open bug); a fixed test drops out of the file entirely. New failures land as category: untriaged with a null issue — that placeholder is the gate asking for triage.

Also adds test-parity/README.md: the category reference that known_failures.json's schema has always pointed at and which never existed (a #797 finding).

Scope

known_failures.json still gates the tag-gated parity job (the full test-files/*.ts suite); its scope is now documented in-file. Migrating that job needs a full-suite baseline from a tag run and is a follow-up.

Bootstrap — why this starts red

The snapshot is evidence about a specific platform and oracle, so it cannot honestly be generated on a laptop: CI runs Ubuntu with Node pinned by .node-version (26.5.0), and several failure classes are Linux-only or oracle-version-dependent. Generating it on macOS/Node 24 would commit wrong statuses.

So the first conformance-smoke run here is expected to fail, listing the real failures as regressions against an absent snapshot. Its 8 shards still upload their reports (if: always()), and the snapshot is seeded by folding those 8 reports in — the merge-only update semantics exist precisely so 8 partial reports compose into one file. Once seeded and committed, the run goes green and this leaves draft.

Validation

gap_snapshot.py --self-test covers both diff directions, the sharded-update case, triage carry-over, and pruning; it runs in the lint job so the logic gating conformance-smoke is checked on the cheap job rather than only 8 shards deep. Additionally exercised end-to-end against synthetic reports: seed → green, improvement → red, regression → red, status change → red, and a one-test sharded update leaving a sibling entry intact.

Per contributor convention, no version bump — maintainer bumps at merge time.


Update: snapshot seeded — and it immediately found a hole

Seeded from this PR's first (intentionally red) run: 401 tests, 384 passing, 17 not. All 8 shards now check green against it.

The 8 real failures were already in known_failures.json, and their triage is carried over rather than discarded. The other 9 were invisible: node_fail tests that no skip-list entry covered, because the old gate dropped node_fail from the denominator. They break down as:

Tests Why node exits non-zero Verdict
6 npm-import (exponential-backoff, cron, dayjs, moment, rate-limiter-flexible, slugify) Import cannot resolve without node_modules Same class as test_ramda_sum; needs a CI fixture (#1634)
test_gap_4510_enum_forward_ref, test_gap_derived_param_props ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX — enums and parameter properties cannot run in strip-only mode These have never verified the feature they were written for. Needs an expected-output fixture
test_gap_prop_plan_cache_invalidation TypeError: Cannot assign to read only property 'q' (ESM strict mode) Needs triage

The two ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX cases are the sharpest argument for the ratchet: both were written to catch real Perry bugs (#4510 enum forward-references, #1758/#321 derived parameter properties), and both have been silently no-ops against the oracle ever since. The harness already supports expected-output fixtures (test-parity/expected/<name>.txt), so the fix is mechanical — worth its own issue rather than bundling here.

Summary by CodeRabbit

  • CI Improvements

    • Updated the gap-suite gate to enforce a bidirectional snapshot check against test-parity/gap_snapshot.json.
    • CI now reports divergences both for newly appearing failures and for tests that start passing after being tracked.
    • Added snapshot update support via UPDATE_SNAPSHOT=1 ./scripts/run_gap_tests.sh.
    • Improved snapshot status accounting by explicitly tracking all non-passing outcomes (including skipped/crash cases).
    • Added a workflow validation step to self-test snapshot behavior.
  • Documentation

    • Documented how test-parity gating works, including how to regenerate the gap snapshot and the meaning of tracked statuses/categories.

…napshot ratchet

run_gap_tests.sh gated with `comm -23 <failures> known_failures.json` — fail on
any failure not already triaged. That only catches regressions: a listed test
that starts PASSING produces no signal, so its entry outlives the bug it tracked
and the file decays into orphans nobody can revalidate (#797).

- New scripts/gap_snapshot.py generates and checks test-parity/gap_snapshot.json,
  a committed list of every non-passing gap test. Checked BOTH ways — a new
  failure is red, and so is a listed test that now passes, which makes an
  orphaned entry impossible instead of something to audit by hand. Accept the
  current state with UPDATE_SNAPSHOT=1 ./scripts/run_gap_tests.sh.
- Only non-passing tests are stored: "fails and is not listed" already means
  regression, so listing the ~370 passing tests would quadruple the file
  without adding a detection.
- node_fail/skipped are recorded explicitly rather than dropped from the
  denominator, so an oracle-coverage change is a visible diff, not a silent
  hole (a Node 22 pin hid 14 tests that way, #6364).
- Statuses only, never program output: the harness truncation marker embeds an
  exact line count that is not stable across runs (#796).
- Shard-safe: check and update act only on tests present in the report, so a
  --shard N/8 run never judges or rewrites its siblings' entries. Entries are
  pruned only when the test file is gone, decidable from any shard.
- Triage metadata carries across a status change (parity_fail -> crash is the
  same open bug); a fixed test drops out of the file entirely.
- Add test-parity/README.md — the category reference known_failures.json's
  schema has always pointed at but which never existed (#797 finding).
- known_failures.json keeps gating the tag-gated full parity job; its scope is
  now documented in-file. Migrating that job is a follow-up.
- The checker's self-test runs in the lint job, so the logic that decides
  whether conformance-smoke goes red is verified on the cheap job.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 827ea260-32e1-4571-88ba-fda51c51286c

📥 Commits

Reviewing files that changed from the base of the PR and between 8d24b93 and a1815ef.

📒 Files selected for processing (1)
  • scripts/run_gap_tests.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/run_gap_tests.sh

📝 Walkthrough

Walkthrough

The gap test gate now compares reports against a committed snapshot, detects regressions and improvements, supports explicit snapshot updates, and validates its comparison logic in CI. Documentation distinguishes the snapshot gate from the legacy parity allowlist.

Changes

Gap snapshot ratchet

Layer / File(s) Summary
Snapshot comparison and merge tool
scripts/gap_snapshot.py
Loads reports and snapshots, detects status divergences, merges non-passing results, preserves triage metadata, prunes removed tests, and provides self-test and CLI modes.
Gap test gate integration
scripts/run_gap_tests.sh, .github/workflows/test.yml
Runs snapshot checks or updates, forwards shard arguments, retains crash reporting, and executes the snapshot self-test in lint CI.
Committed snapshot and gate documentation
test-parity/gap_snapshot.json, test-parity/README.md, changelog.d/6755-gap-snapshot-ratchet.md, test-parity/known_failures.json
Adds the generated gap snapshot, documents both conformance gates, records the ratchet change, and updates legacy allowlist metadata and reasons.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant RunGapTests
  participant ParityRunner
  participant GapSnapshot
  Developer->>RunGapTests: invoke gap suite with optional shard arguments
  RunGapTests->>ParityRunner: run filtered gap tests
  ParityRunner-->>RunGapTests: produce test report
  RunGapTests->>GapSnapshot: check report against committed snapshot
  GapSnapshot-->>RunGapTests: return divergence status
  Developer->>RunGapTests: set UPDATE_SNAPSHOT=1 when accepting changes
  RunGapTests->>GapSnapshot: update committed snapshot
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: adding a bidirectional snapshot ratchet for the gap suite.
Description check ✅ Passed The description is detailed and covers summary, changes, validation, and rollout context, though it omits some template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gap-snapshot-ratchet

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Folded from the 8 shard reports of this PR's first conformance-smoke run —
Ubuntu, Node 26.5.0 per .node-version. Generating it on a macOS/Node 24
laptop would have committed statuses that are wrong for the gate's platform
and oracle, so the run had to happen first (that is why the first run was red).

Seeding it immediately surfaced 9 node_fail tests that NO existing skip-list
entry covered, because the old gate dropped node_fail from the denominator
entirely — the #6364 hole, still open:

- 6 npm-import tests (exponential-backoff, cron, dayjs, moment,
  rate-limiter-flexible, slugify): node cannot resolve the import without
  node_modules, so the oracle never runs. Same class as test_ramda_sum;
  tracked to #1634 pending a CI-side fixture.
- test_gap_4510_enum_forward_ref and test_gap_derived_param_props: node
  refuses the file outright with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX — enums
  and parameter properties cannot run in strip-only mode. These tests have
  therefore never verified the very features they were written for. The fix
  is an expected-output fixture, not an edit to the test.
- test_gap_prop_plan_cache_invalidation: node throws TypeError "Cannot assign
  to read only property 'q'" under ESM strict mode; needs triage.

Triage for the 8 failures already in known_failures.json is carried over
rather than discarded, so no provenance is lost. Every entry has a category
and a reason; none are left untriaged.
@TheHypnoo
TheHypnoo marked this pull request as ready for review July 22, 2026 14:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@scripts/run_gap_tests.sh`:
- Around line 46-52: Update the run_parity_tests.sh invocation in
run_gap_tests.sh to remove any existing latest.json before execution, capture
its exit status, and only continue for the documented aggregate-parity failure;
propagate every other non-zero status so setup or runtime failures cannot use
stale or partial results.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e8c4ebd5-068e-4ff9-9acb-64366b999301

📥 Commits

Reviewing files that changed from the base of the PR and between f5ab0c0 and 8d24b93.

📒 Files selected for processing (7)
  • .github/workflows/test.yml
  • changelog.d/6755-gap-snapshot-ratchet.md
  • scripts/gap_snapshot.py
  • scripts/run_gap_tests.sh
  • test-parity/README.md
  • test-parity/gap_snapshot.json
  • test-parity/known_failures.json

Comment thread scripts/run_gap_tests.sh Outdated
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.

1 participant