a73x

cb1e4946

test: the mutation notes name what the mutants actually did

a73x   2026-08-20 07:55

Commit message
test: the mutation notes name what the mutants actually did

Both mutations had to be re-aimed — each one killed the suite at a leg
several before the one it was pointed at. The single-tile exit mutant also
came back 0 rather than the 124 the leg's failure text predicted: the wall
it wrongly dropped to reads a piped script's closed stdin as a wall nobody
is left to steer, and leaves. Zero is not seven so the assertion fires
either way, but the sentence it prints now names both shapes.

docs/decisions.md
Old New
@@ -4702,12 +4702,22 @@ while `\x1c w` is a chord — so the abort is judged after `feed`, not before it
4702 Secondary, from the same review: the sentence was wrong for half the states it 4702 Secondary, from the same review: the sentence was wrong for half the states it
4703 covered. Nothing has been "detached while reconnecting" from a first attach 4703 covered. Nothing has been "detached while reconnecting" from a first attach
4704 that never came up, so `.connecting` says `aborted before attaching` instead — 4704 that never came up, so `.connecting` says `aborted before attaching` instead —
4705 which is also what `openFailure` says for an abort one layer down. 4705 which is also what `openFailure` says for an abort one layer down. — and both had to be re-aimed,
4706 4706 which is the finding worth recording rather than the kills.
4707 Two mutations, to check the new legs have teeth. Making a ring step re-dial a 4707
4708 sibling that already has a tile fails `ring grow` at `the daemon accepted 3 4708 Making a ring step re-dial a sibling that already has a tile kills the SUITE at
4709 attaches (want 2)` — the flat-count assertion, and nothing else in the suite 4709 `zoom skip`, four legs before `ring grow` ever runs: the same flat-count
4710 notices. Making a single-tile session end drop to the wall instead of exiting 4710 assertion, on the phase-1 path, and it gets there first. Run against the
4711 fails `exit semantics` at `the only tile's shell exited 7 and mux exited 124`, 4711 `ring grow` leg standalone the mutant gives `delta=3 want=2`, which is the
4712 which is the timeout: a wall nobody can leave is exactly what that half of the 4712 assertion that leg was built for. Making a single-tile session end drop to the
4713 rule exists to refuse, and the 124 says so in the failure's own words. 4713 wall instead of exiting is worse: it wedges an early piped leg and the suite
4714 stops having proved nothing about the rule at all. Standalone, `exit 7` under
4715 the mutant comes back `0` rather than `7` — not the timeout's 124 that the
4716 obvious reading predicts, because the wall it dropped to reads the script's
4717 closed stdin as a wall nobody is left to steer and leaves. Zero is not seven,
4718 so the leg fires either way, but the failure text had been written for the 124
4719 and now names both shapes.
4720
4721 The rule this cost re-learning is the one already in these notes: a mutation
4722 that fails the suite somewhere is not a mutation that fails the leg you aimed
4723 it at. When the two differ, go get the targeted answer, and quote THAT.
test/e2e.sh
Old New
@@ -1224,7 +1224,8 @@ cleanup() {
1224 "$OUT.rgcap" "$OUT.rgcap.err" "$OUT.rgpc" "$OUT.rgcapt" "$OUT.rgcap0" \ 1224 "$OUT.rgcap" "$OUT.rgcap.err" "$OUT.rgpc" "$OUT.rgcapt" "$OUT.rgcap0" \
1225 "$OUT.rgstop" 1225 "$OUT.rgstop"
1226 rm -f "$SOCK47" "$OUT.xe.d" "$OUT.xe1" "$OUT.xe1.err" \ 1226 rm -f "$SOCK47" "$OUT.xe.d" "$OUT.xe1" "$OUT.xe1.err" \
1227 "$OUT.xecap" "$OUT.xecap.err" "$OUT.xepc" "$OUT.xestop" 1227 "$OUT.xecap" "$OUT.xecap.err" "$OUT.xepc" "$OUT.xe2" "$OUT.xe2.err" \
1228 "$OUT.xestop"
1228 rm -rf "$CVSTATE" "$RGSTATE" "$RGOTHER" "$XESTATE" 1229 rm -rf "$CVSTATE" "$RGSTATE" "$RGOTHER" "$XESTATE"
1229 # The convergence files a FAILING assert_converged leaves behind 1230 # The convergence files a FAILING assert_converged leaves behind
1230 # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not 1231 # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not
@@ -6747,14 +6748,19 @@ ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one tha
6747 # Both halves in one block, because either alone is satisfied by a bug: 6748 # Both halves in one block, because either alone is satisfied by a bug:
6748 # 6749 #
6749 # * one tile. `exit 7` in a piped `mux` — the shape every script in the 6750 # * one tile. `exit 7` in a piped `mux` — the shape every script in the
6750 # world uses — and the code comes back 7. A wall that always dropped to 6751 # world uses — and the code comes back 7. Graded against the mutation
6751 # itself would hang here forever, which is why the timeout's 124 is a 6752 # (`endAction` dropping to the wall whatever the tile count): `mux`
6752 # failure this leg names. 6753 # comes back 0, because the wall it dropped to reads this script's
6754 # closed stdin as a wall nobody is left to steer. Zero is not seven and
6755 # the leg says so; 124 is the other shape the same bug can take.
6753 # * two tiles. `\x1cc` adds one and zooms it, the shell in it exits, and 6756 # * two tiles. `\x1cc` adds one and zooms it, the shell in it exits, and
6754 # mux is still running, on the wall, with the dead tile narrating 6757 # mux is still running, on the wall, with the dead tile narrating
6755 # `[exited]` beside the live one. Then `q` leaves — exit 0, not 7 and 6758 # `[exited]` beside the live one. Then `q` leaves — exit 0, not 7 and
6756 # not the shell's anything, because leaving the wall is the user's act 6759 # not the shell's anything, because leaving the wall is the user's act
6757 # and not the session's. 6760 # and not the session's.
6761 # * two tiles and a PIPED client whose stdin has already gone. The case
6762 # review reproduced as a hang, and the reason the rule's second half is
6763 # stated in `endAction` rather than at the read that notices EOF.
6758 "$MUXD" run --sock "$SOCK47" --shell /bin/sh > "$OUT.xe.d" 2>&1 & 6764 "$MUXD" run --sock "$SOCK47" --shell /bin/sh > "$OUT.xe.d" 2>&1 &
6759 D47PID=$! 6765 D47PID=$!
6760 wait_sock "$SOCK47" "$OUT.xe.d" "exit-semantics daemon never bound" 6766 wait_sock "$SOCK47" "$OUT.xe.d" "exit-semantics daemon never bound"
@@ -6766,7 +6772,8 @@ RC=$?
6766 set -e 6772 set -e
6767 [ "$RC" -eq 7 ] || { 6773 [ "$RC" -eq 7 ] || {
6768 echo "e2e FAIL: exit semantics: the only tile's shell exited 7 and mux exited $RC" 6774 echo "e2e FAIL: exit semantics: the only tile's shell exited 7 and mux exited $RC"
6769 echo " (124 means it dropped to a wall nobody could leave):" 6775 echo " — a wall it dropped to instead reads as 0 once this script's"
6776 echo " stdin closes under it, or 124 if it never gets that far:"
6770 cat "$OUT.xe1.err"; exit 1; } 6777 cat "$OUT.xe1.err"; exit 1; }
6771 6778
6772 set +e 6779 set +e
@@ -6806,6 +6813,38 @@ grep -q -- "--sock $SOCK47#0 \[up\]" "$OUT.xecap" || {
6806 # The session that did NOT end is still there: a shell exiting under the 6813 # The session that did NOT end is still there: a shell exiting under the
6807 # zoom ends that session and nothing else. 6814 # zoom ends that session and nothing else.
6808 wait_grid "$SOCK47" "xehome-pin" "exit semantics: session 0 outlived its neighbour" 6815 wait_grid "$SOCK47" "xehome-pin" "exit semantics: session 0 outlived its neighbour"
6816
6817 # The third case, and the one review REPRODUCED as a hang: two tiles, but
6818 # a PIPED client whose stdin has already closed when the zoomed session
6819 # ends. "Drop to the wall" is not an answer there — the wall it drops to
6820 # has no keyboard, so mux waited in poll forever with the exit code in
6821 # hand (RC=124, measured). The rule is the same rule with its second half
6822 # stated: an ending nobody can leave is mux's ending.
6823 #
6824 # The gap is built rather than raced: the shell reads `sleep 4` and
6825 # `exit 3` from a stdin that closes IMMEDIATELY after, so the pipe is long
6826 # gone by the time the session ends four seconds later. `timeout` is what
6827 # turns a regression into a failure instead of a wedged suite.
6828 XETIME_START=$(date +%s)
6829 set +e
6830 { sleep 1; printf '\034c'; sleep 3; printf 'sleep 4\n'; printf 'exit 3\n'; } | \
6831 XDG_STATE_HOME="$XESTATE" timeout 40 "$MUX" --sock "$SOCK47" \
6832 > "$OUT.xe2" 2> "$OUT.xe2.err"
6833 RC=$?
6834 set -e
6835 XETIME=$(( $(date +%s) - XETIME_START ))
6836 [ "$RC" -eq 3 ] || {
6837 echo "e2e FAIL: exit semantics: a piped two-tile client whose session ended"
6838 echo " exited $RC after ${XETIME}s, want 3 — 124 is the wall it"
6839 echo " dropped to with nobody left to leave it:"
6840 cat "$OUT.xe2.err"; exit 1; }
6841 # Promptly, not eventually: the code is right in both readings, but a
6842 # client that only exits because the timeout reaped it is the bug wearing
6843 # the right answer. The session ends ~8s in; 20 is generous and still far
6844 # under the 40s timeout that would otherwise be the only bound.
6845 [ "$XETIME" -lt 20 ] || {
6846 echo "e2e FAIL: exit semantics: the piped client took ${XETIME}s to notice its"
6847 echo " own session had ended — it exited, but it did not notice"; exit 1; }
6809 assert_stopped "$SOCK47" "$D47PID" "exit semantics" "$OUT.xestop" 6848 assert_stopped "$SOCK47" "$D47PID" "exit semantics" "$OUT.xestop"
6810 D47PID="" 6849 D47PID=""
6811 ok "a session ending under the zoom exits mux alone, and drops to the wall beside company" 6850 ok "a session ending under the zoom exits mux alone, and drops to the wall beside company"