test(hytte-ui): pin the WarnLatch FIFO bound and the refusal arms' state contract (#1046) - #1048
Conversation
Adversarial review — PR #1048 @ 632c552FIX-FIRST — one MEDIUM (a claim in the mutation table that does not reproduce, plus the shipped doc built on it) and three LOW. Nothing here is a correctness risk: the production diff is an exactly behaviour-preserving extraction and every gate is green on the branch, so landing it and fixing in a follow-up regresses nothing. But this PR is the "be precise about what is enforced" PR, and its own row 4 is the imprecise part. Verified first (the two structural questions)
MEDIUM 1 — mutation row 4 does not reproduce, and the corrected doc names the one spelling CI already catches
Row 4 claims that dropping the
So the flake's let _ = upload_data(&gl, data, data_shape, data_source, &state, &self.warned_data);Measured: Why either one compiles — the part the doc does not say, and the whole trap: the argument binding Failure scenario: a plugin moves its grid 8×8 → 16×16, the driver refuses the reallocation, Fix (either; one line each):
LOW 1 — the FIFO test pins that eviction happens, not the bound
The loop is
So "does Fix — two-sided and literal-anchored (red at 0, 7 and 9): const BOUND: usize = 8;
assert_eq!(WARNED_LENGTHS, BOUND, "this test's arithmetic is written for a bound of 8");
let a = u64::from(100_000_u32);
assert!(latch.claim(a));
for n in 0..BOUND - 1 { // one short of the bound: a must survive
assert!(latch.claim(200_000 + n as u64));
}
assert!(!latch.claim(a), "still remembered one short of the bound");
assert!(latch.claim(300_000)); // the claim that fills it
assert!(latch.claim(a), "…and now evicted, so it is reported again");Same family as the "pin external wire units as literals" rule: an assertion derived from the constant under test cannot see that constant move. LOW 2 — what is pinned is the helper, not "the refusal arm's state contract"
Leaving both helpers intact and adding the residue in the arms, immediately after the call: // gl_surface.rs:1004, after refuse_data_strip(…)
self.data_len = len;
// shader_surface.rs:1002, after refuse_data_strip(…)
*data_shape = shape;→ That is inherent to the boundary #1046 chose (the GL-backed half is #1036's), not a defect introduced here — but the body's "the bookkeeping … is a pure, hermetically testable function" is fair while "the refusal arms' state half" is broader than what is pinned. Worth narrowing to "the helper's half" in the body and in the two test docs. Sub-point, LOW 3 — the pre-correction sentence is still at the call site, where the mistake gets made
There is something to bind — the outer Related: neither the corrected doc (328-335) nor either INFO
Mutation table (re-run independently; both files
|
| # | Mutation | Expected | Measured |
|---|---|---|---|
| 1 | gl_surface: WARNED_LENGTHS 8 → 0 |
RED | RED (101) — a_length_evicted_by_the_bound_is_reported_again, alone ✅ |
| 1b | gl_surface: WARNED_LENGTHS 8 → 9 |
? | GREEN (0) — bound value unpinned (LOW 1) |
| 1c | gl_surface: WARNED_LENGTHS 8 → 3 |
? | GREEN (0) — ditto |
| 1d | gl_surface: WARNED_LENGTHS 8 → 1 |
? | RED (101) — but via two pre-existing tests; the new test passes |
| 2 | gl_surface: delete *data_len = 0; from refuse_data_strip |
RED | RED (101) — refusing_a_data_strip_zeros_its_length_and_forgets_the_source ✅ |
| 3 | shader_surface: advance _data_shape inside refuse_data_strip |
RED | RED (101) — refusing_a_data_strip_does_not_advance_its_shape ✅ |
| 4 | shader_surface: let … else → if upload_data(…).is_none() {} |
compiles; test + clippy green | test 0, clippy 101 (needless_ifs) — row 4 only half reproduces (MEDIUM 1) |
| 5 | both: no-op the refuse_data_strip bodies, arms untouched |
RED if wired | RED (101) — both new tests fail ✅ extraction is not decorative |
| 6 | both: helpers intact, residue written in the arms | ? | GREEN — cargo test 0 (87) and system-tests 0 (186+1) (LOW 2) |
| 7 | shader_surface: let _ = upload_data(…); |
? | GREEN — clippy 0 warnings, test 0 (87): the silent hole (MEDIUM 1) |
| 8 | shader_surface: rename to let Some(sampled) + sampled.bind_unit |
clean | clippy 0, test 0 (87) — and #4 / #7 then both error[E0425]: the proposed fix |
Gates — pristine 632c552, foreground, RUSTC_WRAPPER unset, exit codes captured directly (never through tail/head)
| Gate | Exit |
|---|---|
cargo clippy --workspace --all-targets --features system-tests -- -D warnings |
0 |
cargo test -p hytte-ui |
0 — 87 passed, 0 failed |
xvfb-run -a cargo test --features system-tests -p hytte-ui |
0 — 186 + 1 passed, 3 GL-gated ignored (no GL in this sandbox) |
nix fmt -- --no-cache |
0 — 456 files, 0 changed |
Read-only review from a detached worktree at 632c552; nothing pushed to the branch.
🤖 Generated with Claude Code
…ate contract (#1046) PR #1031's third-pass review (comment #5617800572) found three residual gaps, all test-side: gl_surface's new WarnLatch had no eviction test (its shader_surface twin did), the refusal arms' bookkeeping half — data_len reset in gl_surface, data_shape NOT advancing in shader_surface — was pinned by nothing, and one doc comment overclaimed "the compiler enforces the skip" when only one spelling of it does. Extracts refuse_data_strip in both files so the state half of each refusal arm is a pure, hermetically testable function (no GL context needed), adds the reviewer-supplied tests plus a port of two_broken_sources_alternating_cost_two_lines for gl_surface's latch, and corrects the doc to say the let-else shape is the contract rather than the Option return type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157rpXf1ks2hZEfdB47UJ6v
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157rpXf1ks2hZEfdB47UJ6v
…ract claim, and compiler-enforce the shape/rename fixes (#1046 fix round) Adversarial review at 632c552 (PR #1048, issue #1046) returned FIX-FIRST: one MEDIUM and three LOW findings, all in gl_surface.rs/shader_surface.rs, none a correctness risk in the shipped production code. - MEDIUM 1 (shader_surface.rs): the PR body's row 4 claimed clippy stayed green for `if upload_data(...).is_none() {}` — measured, that spelling is `clippy::needless_ifs` (exit 101) under the workspace gate. The actually silent spelling is the shorter `let _ = upload_data(...);`, which compiles clean because the call site's own `data: &mut hgl::Texture` argument stays in scope once the shadowing `let Some(data) = ... else` is dropped, so `data.bind_unit(...)` keeps binding the *previous* texture while `u_data_size` describes the new one (#968 review L7, restored). Fixed by renaming the shadowed binding to `sampled`, which the outer scope has no other binding for: both drop spellings now fail with `E0425`, which is real compiler enforcement rather than an unenforced spelling convention. Corrected the doc and the stale call-site comment (LOW 3) to match. - LOW 1 (gl_surface.rs): `a_length_evicted_by_the_bound_is_reported_again` looped `0..WARNED_LENGTHS`, so it moved with the constant and stayed green at WARNED_LENGTHS shrunk to 3 or grown to 9 — it pinned only that eviction happens, not the bound's value. Rewritten with a literal `BOUND` anchor (asserted equal to WARNED_LENGTHS) and a two-sided fill/evict sequence, so 0, 7, and 9 are all red. - LOW 2 (both files): the new hermetic tests pin the refusal *helper*, not the *arm* that calls it — writing the residue in the arm around the call stays green either way. `shader_surface::refuse_data_grid`'s `_data_shape` never needed `&mut` (it never writes it), so it is now `&`: advancing the shape inside the helper is `E0594`, moving that half of the contract from a test into the type system. `gl_surface::refuse_data_strip`'s `data_len` genuinely has to be `&mut` (it is the one place that zeros it), so that half stays doc-narrowed to "pins the helper" rather than claimed as "the arm's state half" — closing the gap for real needs #1036's GL-backed test. - INFO: `refuse_data_strip` renamed to `refuse_data_grid` in shader_surface.rs to match that file's 2-D vocabulary (data_shape/u_data_size/grid) rather than gl_surface's 1-D one; `WarnLatch::claim`'s `len() == WARNED_LENGTHS` changed to `>=` in both files, which is what let a bound of 0 pass as unbounded — `>=` makes that class of mistake unreachable rather than merely tested for. No production behaviour changes beyond the LOW 2 reference-kind narrowing (a pure widening from `&mut` to `&` in a parameter that was never read or written) and the MEDIUM 1 rename (no runtime effect, same texture). Refs #1046 #1031 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157rpXf1ks2hZEfdB47UJ6v
632c552 to
02e6172
Compare
Fix round — PR #1048 @
|
| Finding | File:line | What changed |
|---|---|---|
| MEDIUM 1 | shader_surface.rs warn_on_data_upload_failure doc (~line 328) + draw's call site (~line 857) |
Corrected the doc: if upload_data(…).is_none() { } is clippy::needless_ifs under -D warnings, not green — the real silent spelling is let _ = upload_data(…);. Took fix (b): renamed the shadowed binding at the call site from data to sampled. The outer data: &mut hgl::Texture argument no longer offers a same-named fallback, so both drop spellings now fail with E0425 instead of silently binding the stale texture (#968 review L7). |
| LOW 1 | gl_surface.rs a_length_evicted_by_the_bound_is_reported_again (~line 1936) |
Rewritten two-sided and literal-anchored: a local const BOUND: usize = 8 asserted equal to WARNED_LENGTHS, filled to BOUND - 1 (one short — a must survive), then the claim that fills the bound, then the eviction claim. No longer moves with the constant. |
| LOW 2 | gl_surface.rs refuse_data_strip (~line 560) / shader_surface.rs refuse_data_grid (~line 355, renamed — see INFO) |
shader_surface's _data_shape param changed &mut (u32, u32, ShaderFormat) → &(u32, u32, ShaderFormat): it was never written, so "does not advance" is now compiler-enforced (E0594 on a stray write) rather than test-asserted. gl_surface's data_len genuinely needs &mut (the helper is the one place that zeros it), so that half is doc-narrowed instead: both docs now say plainly that this pins the helper, not the arm — closing the arm's half needs #1036's GL-backed test. |
| LOW 3 | shader_surface.rs draw's call site (~line 852) |
The stale "there is nothing to bind" comment corrected in place: the outer data argument is something to bind, which is exactly what MEDIUM 1's bug was. Both refusal-helper docs and the call-site comment now name the tests that drive them. |
| INFO | shader_surface.rs (rename), both files (WarnLatch::claim) |
refuse_data_strip → refuse_data_grid in shader_surface.rs (2-D vocabulary: data_shape/u_data_size/grid). WarnLatch::claim's self.said.len() == WARNED_LENGTHS → >= WARNED_LENGTHS in both files — == is exactly what let a bound of 0 pass as unbounded pre-#1046. |
Mutation table (re-run independently, md5sum before/after every mutation, reverted with git checkout -- and re-verified against the pristine digest)
Pristine: c75933b6cf253738a369bdc83375a6d2 (gl_surface.rs),
ff072e578b4d3a8b264fa48c2992d771 (shader_surface.rs).
| # | Mutation | Expected | Measured | Kind |
|---|---|---|---|---|
| 1 | gl_surface: WARNED_LENGTHS 8 → 0 |
RED | RED | test failure (exit 101) — const-equality assertion fails immediately |
| 2 | gl_surface: WARNED_LENGTHS 8 → 9 |
RED | RED | test failure (exit 101) — was GREEN before this round (LOW 1) |
| 3 | gl_surface: WARNED_LENGTHS 8 → 3 |
RED | RED | test failure (exit 101) — was GREEN before this round (LOW 1) |
| 4 | gl_surface: WARNED_LENGTHS 8 → 1 |
RED | RED | test failure (exit 101) — now via the new test itself, not only the two pre-existing ones |
| 5 | gl_surface: delete *data_len = 0; from refuse_data_strip |
RED | RED | test failure (exit 101) — refusing_a_data_strip_zeros_its_length_and_forgets_the_source |
| 6 | shader_surface: add *_data_shape = (state.data_size.0.max(1), state.data_size.1.max(1), state.format); inside refuse_data_grid |
RED | compile error | E0594: cannot assign to *_data_shape, which is behind a & reference — caught before cargo test runs at all |
| 7 | shader_surface: draw's call site → if upload_data(…).is_none() { } |
compile error | compile error | E0425: cannot find value 'sampled' in this scope |
| 8 | shader_surface: draw's call site → let _ = upload_data(…); (the spelling MEDIUM 1 actually names) |
compile error | compile error | E0425: cannot find value 'sampled' in this scope — same fix closes both |
| 9 | both: no-op both refusal-helper bodies (let _ = (…);), arms untouched |
RED | RED | test failure (exit 101) — both refusing_a_data_strip_zeros_its_length_and_forgets_the_source and refusing_a_data_grid_does_not_advance_its_shape fail; extraction still load-bearing after the rename |
| 10 | shader_surface: helper intact, *data_shape = shape; added in the arm immediately after the call |
? | GREEN | cargo test -p hytte-ui exit 0 (87 passed); xvfb-run -a cargo test --features system-tests -p hytte-ui exit 0 (186 + 1 passed) — inherent to the arm/helper boundary (documented in LOW 2's doc narrowing, not closeable without restructuring the call site) |
| 11 | gl_surface: helper intact, self.data_len = len; added in the arm immediately after the call |
? | GREEN | cargo test -p hytte-ui exit 0 (87 passed); xvfb-run -a cargo test --features system-tests -p hytte-ui exit 0 (186 + 1 passed) — data_len needs &mut in the helper, so no compiler fix exists here; doc-narrowed instead |
Gates (02e6172, rebased onto origin/main @ 26f0709; all foreground, RUSTC_WRAPPER unset, exit codes captured directly — never piped through tail/head)
| Gate | Exit |
|---|---|
nix fmt -- --no-cache |
0 (458 files, 0 changed) |
cargo clippy -p hytte-ui --all-targets --features system-tests -- -D warnings |
0 |
cargo clippy --workspace --all-targets --features system-tests -- -D warnings |
0 |
cargo test -p hytte-ui |
0 — 87 passed, 0 failed |
xvfb-run -a cargo test --features system-tests -p hytte-ui |
0 — 186 + 1 passed, 3 GL-gated tests skip (no mesa in this sandbox, same as prior rounds) |
Nothing deferred beyond what #1046 itself scoped out (#1036's GL-backed half
of each refusal arm) — LOW 2's remaining gap (mutations 10/11, both GREEN)
is exactly that boundary, now stated precisely in both files' docs rather
than implied by the tests' names.
🤖 Generated with Claude Code
What
Closes the three test-side gaps PR #1031's third-pass review found (comment
#5617800572),
filed as #1046. All in
crates/hytte-ui/src/{gl_surface.rs,shader_surface.rs};no production behaviour changes apart from one small extraction and (since
the fix round below) one rename and one reference-kind narrowing.
gl_surface'sWarnLatchFIFO bound had no eviction test. Itsshader_surfacetwin does(
two_broken_sources_alternating_cost_two_lines); ported it asa_length_evicted_by_the_bound_is_reported_again.WARNED_LENGTHS = 0(unbounded) used to pass both
cargo testand workspace clippy.Extracted
refuse_data_strip(gl_surface) /refuse_data_grid(
shader_surface, renamed in the fix round below — that file'svocabulary is 2-D) so the bookkeeping —
data_len = 0ingl_surface,not advancing
data_shapeinshader_surface— is a pure,hermetically testable function, and added the reviewer-supplied tests
(
refusing_a_data_strip_zeros_its_length_and_forgets_the_source,refusing_a_data_grid_does_not_advance_its_shape). The GL-backed half ofeach arm (that
Texture::newreally produces theErrin the firstplace) stays with build(ci): the three GL-context tests in hytte-ui skip everywhere — give the system-tests check a software GL stack (llvmpipe) so they run (#1031 review H1) #1036 — see the fix round's LOW 2 for exactly what
this does and does not pin.
warn_on_data_upload_failuredoc inshader_surface.rs: thelet … elsespelling stops one specific mistake (areturn-droppingone-liner) from compiling; it does not make the skip compiler-enforced
in general.
— thisif upload_data(…).is_none() { }still compilesclaim was wrong, corrected in the fix round below: that spelling is
clippy::needless_ifsunder this repo's-D warningsgate. Thegenuinely silent spelling is
let _ = upload_data(…);, and the fixround makes both spellings a compile error instead of documenting the
gap.
Fix round — PR #1048 @
02e6172The adversarial review at
632c552(comment)
returned FIX-FIRST: one MEDIUM (a mutation-table claim that did not
reproduce, plus the doc built on it) and three LOW, none a correctness risk.
Full per-finding writeup and mutation table posted as a
PR comment —
see that comment for the complete record; summary:
if upload_data(…).is_none() { }does not reach a green pipeline (
clippy::needless_ifs, exit 101 under-D warnings). The real silent spelling islet _ = upload_data(…);,which compiles and passes clippy clean because
draw's owndata: &mut hgl::Textureargument stays in scope once the shadowinglet Some(data) = … elseis dropped —data.bind_unit(…)then bindsthe previous texture while
u_data_sizedescribes the new shape (feat(plugin): the shader widget — Node::Shader, a plugin-supplied fragment + data buffer on the GL path (#893) #968review L7, restored). Fixed by renaming the shadowed binding from
datato
sampled: both drop spellings now fail withE0425, real compilerenforcement rather than an unenforced spelling convention.
a_length_evicted_by_the_bound_is_reported_againlooped0..WARNED_LENGTHS, so it pinned only that eviction happens, not thebound's value (stayed green at
WARNED_LENGTHSshrunk to 3 or grown to9). Rewritten two-sided and literal-anchored: a local
BOUNDconstantasserted equal to
WARNED_LENGTHS, filled to one short of the bound,then the claim that fills it, then the eviction.
that calls it; writing the residue in the arm around the call stays
green either way.
shader_surface's_data_shapenever needed&mut(never written), so it is now
&: advancing it inside the helper isE0594, a compile error instead of a test.gl_surface'sdata_lengenuinely needs
&mut(the helper is the one place that zeros it), sothat half is doc-narrowed to "pins the helper" — closing it for real
needs build(ci): the three GL-context tests in hytte-ui skip everywhere — give the system-tests check a software GL stack (llvmpipe) so they run (#1031 review H1) #1036's GL-backed test.
corrected in place, and both refusal-helper docs now name the tests that
drive them.
refuse_data_strip→refuse_data_gridinshader_surface.rs(matches that file's 2-D vocabulary);
WarnLatch::claim'slen() == WARNED_LENGTHS→>=in both files (the exact hole that letbound
0pass as unbounded).Mutation table
Every mutation applied to the committed tree,
md5sum'd before and after toconfirm it actually landed, gate run in the foreground, then reverted with
git checkout --and re-verified against the pristinemd5sum.Original round (
632c552):gl_surface:WARNED_LENGTHS: usize = 8→0a_length_evicted_by_the_bound_is_reported_againfailsgl_surface: delete*data_len = 0;fromrefuse_data_striprefusing_a_data_strip_zeros_its_length_and_forgets_the_sourcefailsshader_surface: add*_data_shape = (state.data_size…, state.format);to the refusal helperrefusing_a_data_grid_does_not_advance_its_shapefailsshader_surface:draw's call site →if upload_data(…).is_none() { }(drop thelet … else)needless_ifs); superseded belowFix round (
02e6172), re-run independently,md5sumbefore/after every mutation:gl_surface:WARNED_LENGTHS8→0gl_surface:WARNED_LENGTHS8→9gl_surface:WARNED_LENGTHS8→3gl_surface:WARNED_LENGTHS8→1gl_surface: delete*data_len = 0;fromrefuse_data_stripshader_surface: write*_data_shape = (…)insiderefuse_data_gridE0594(cannot assign, behind&) — was a test failure pre-fix, now caught earliershader_surface:draw's call site →if upload_data(…).is_none() { }(drop thelet … else)E0425(samplednot found)shader_surface:draw's call site →let _ = upload_data(…);(the actually-silent spelling)E0425(samplednot found) — this is the spelling MEDIUM 1 namedlet _ = (…);), arms untouchedshader_surface: helper intact,*data_shape = shape;written in the arm right after the callcargo testexit 0, 87 passed;xvfb-runsystem-tests exit 0, 186+1 passed) — inherent to the arm/helper boundary, documented in LOW 2gl_surface: helper intact,self.data_len = len;written in the arm right after the callcargo testexit 0, 87 passed;xvfb-runsystem-tests exit 0, 186+1 passed) —data_lenneeds&mut, so this one has no compiler fix; doc-narrowed insteadGates
Original round (
632c552, base7cda7e2):nix fmt -- --no-cachecargo clippy -p hytte-ui --all-targets --features system-tests -- -D warningscargo clippy --workspace --all-targets --features system-tests -- -D warningscargo test -p hytte-uixvfb-run -a cargo test --features system-tests -p hytte-uiFix round (
02e6172, rebased ontoorigin/main@26f0709), all foreground,RUSTC_WRAPPERunset:nix fmt -- --no-cachecargo clippy -p hytte-ui --all-targets --features system-tests -- -D warningscargo clippy --workspace --all-targets --features system-tests -- -D warningscargo test -p hytte-uixvfb-run -a cargo test --features system-tests -p hytte-uiNothing deferred beyond what #1046 itself scoped out (#1036's GL-backed half
of each refusal arm — see the fix round's LOW 2 for the precise boundary).
🤖 Generated with Claude Code
https://claude.ai/code/session_0157rpXf1ks2hZEfdB47UJ6v