Skip to content

test(ts): split lang into a fixture per module and key reject's refusals by item - #474

Merged
kixelated merged 6 commits into
mainfrom
quest/m0/checks/fixture-layout
Nov 3, 2026
Merged

kixelated merged 6 commits into
mainfrom
quest/m0/checks/fixture-layout

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

lang was one crate of 31.7k lines with one case list, one caller.ts, and one snapshot of its cases, and reject keyed each expected error by line number in one lib.rs, with every root appended to one list and a census whose totals change with any case. Two PRs that each add a case appended to the same lines, so they conflicted: of 71 merge-queue conflict ejections in four days, 43 were in lang and 28 in reject, and gate failure #374 was reject's line numbers shifting within a batch. A targeted lang run also translated all of lang.

Approach

  • fixtures/lang/ is now a fixture per module: 66 crates, each with its own cases, ts.toml, expected/, and caller.ts when its cases name inputs. fmt, generics, and shapes share types, so they stay together in shapes. They share harness.rs (the case picker), lang.ts (the caller and its input helpers), the translation build dir, and the wasm target dir. fixtures.rs declares one test per fixture, so just ts test lang::ints translates and runs only ints.
  • differential.ts takes a caller's ops, the bytes that pick each case, and scales the random-input count by them, so each case still gets 151 random inputs. The full-width u64 inputs it used to make for the first 8 ops, which were ints' ops, move to ints/caller.ts as boundaries().
  • reject/src/ splits into a module per kind of construct. expected.txt keys each error by the item it points into and its offset from there (strs.rs:cow_matched+2:4), with closure spans keyed the same way, and keeps them sorted. The roots in ts.toml are module-qualified, and a .taplo.toml rule keeps them sorted (just check fails on unsorted roots, just fix sorts them). bind and crates, whose refusals share rejected(), get the same keys.
  • The census over reject is still tested. It lowers generic items that no root reaches, which finds refusals translation never reaches, such as in config::generic and rebound::generic_park_rebound. Its spans are now snapshotted in reject/census.txt, keyed and sorted like expected.txt. The census's totals and tables, which changed with every reject case, move to a new fixtures/census crate of a few refusals that should stay refusals, so they change only when the census report itself changes. sysroot uses that crate too, since it depends on bytes.

Demonstration: one script made two branches from each layout. Each branch adds one lang case (in ints on one branch, strs on the other) and one reject case (pointers on one, fmt on the other), with snapshots regenerated by the real driver.

Layout Files changed per branch Merging one branch into the other
main 8 conflicts in lang/src/harness.rs, lang/expected/harness.ts, reject/src/lib.rs, reject/ts.toml, reject/expected.txt, reject/census.md
this PR 8 merges cleanly, and the merged branch passes reject, reject_census, census and both lang snapshots unchanged

Translating one lang module in that script took 3-8s, against 3-4 min for all of lang on main.

Expected snapshot changes:

  • Translating each module apart changes a few names in the generated TS. For example, an import alias counts_Noisy is now Noisy, and one copied() of Bumped in iters is elided now that the iters crate alone decides how Bumped is copied. Each module's behavior is still checked against native Rust.
  • reject's messages name types by module path, for example pointers::Halves, because those types moved into modules. Apart from location and module prefixes, the errors match main's one for one (checked with a script).

Impact

Alternatives

  • Keep one lang crate and split only its case list and caller into per-module files: this removes the list conflicts, but each case still changes the one shared snapshot of its cases, and a targeted run still translates everything.
  • Snapshot the census over reject as before with item keys: its totals and per-construct counts still change with every case, so any two reject PRs would still conflict. Dropping it would lose coverage of generic lowering.
  • Sorting reject's roots by hand instead of through taplo: nothing would enforce it, and appends at the end would come back.

Follow-ups

  • The go and ffi reject fixtures key errors by line number the same way. No merge conflicts have been counted there, so no quest was planned.

Review

Round 2: Codex, gpt-6.1-sol, medium effort, quest review --base origin/main on head ba6d201091d01ba0361a1b994e8136ca580b1c05: no findings. Round 1 on cb659c55 also had none.

🤖 Generated with Claude Code

kixelated and others added 4 commits November 2, 2026 18:52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reject's expected errors, and its census's spans in census.txt, now key each refusal
by the item it points into and its offset from there, sorted, so cases added to
different items merge cleanly. Its source splits into a module per kind of construct
and taplo keeps its roots sorted. The census's totals, which every reject case
changed, move to a small census fixture of refusals translation keeps for good.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#471's removed reject cases leave strs.rs, with its roots and expected lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Fixtures that merge test(ts): split lang into a fixture per module and key reject's refusals by item Nov 3, 2026
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 04:01
kixelated and others added 2 commits November 2, 2026 20:19
#475's `iters::lent` case moves to the `iters` fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#478's `dyns` cases regenerate in the `dyns` fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated enabled auto-merge November 3, 2026 04:26
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit ab62412 Nov 3, 2026
5 checks passed
@kixelated
kixelated deleted the quest/m0/checks/fixture-layout branch November 3, 2026 04:31
kixelated added a commit that referenced this pull request Nov 3, 2026
A harness that names its cases (`lang`'s fixtures, `stock`, `codec`, `task`'s steps)
now draws each case's random inputs from a stream seeded by its name, so adding,
removing, or reordering a case changes no other case's inputs. `stable.ts` checks it.

Also deletes quest/m0/checks/fixture-layout.md, which #474 delivered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant