a3d0585e
test: the heal legs read the wall a gone pane leaves standing
a73x 2026-09-01 10:49
Commit message
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -522,7 +522,12 @@ ok "x on a wall ends the focused tile's session and no other, and that tile leav | |||
| 522 | # * back: its own default session and nothing else. `c`, which was live | 522 | # * back: its own default session and nothing else. `c`, which was live |
| 523 | # when the daemon died, is not there — asked of `mux d stats` on the | 523 | # when the daemon died, is not there — asked of `mux d stats` on the |
| 524 | # real daemon, not of the wall that would be reporting on its own | 524 | # real daemon, not of the wall that would be reporting on its own |
| 525 | # resurrection. | 525 | # resurrection. The PANE for `c` is still on the wall, and that is not |
| 526 | # a contradiction: it stands wearing [gone] because the host answered | ||
| 527 | # and did not name the session (gone panes, 2026-09-01), which is a | ||
| 528 | # rect the user saved and an offer to start it anew, not a session. | ||
| 529 | # `x` closes it and Enter creates it; until then the daemon holds one | ||
| 530 | # session and the wall says so. | ||
| 526 | assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop" | 531 | assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop" |
| 527 | DH2PID="" | 532 | DH2PID="" |
| 528 | set +e | 533 | set +e |
| @@ -576,9 +581,17 @@ grep -q -- "--sock $SOCKH2#0 \[up\]" "$OUT.hrcap" || { | |||
| 576 | echo "e2e FAIL: hosts restart: the reborn daemon's own session never became a tile:" | 581 | echo "e2e FAIL: hosts restart: the reborn daemon's own session never became a tile:" |
| 577 | cat "$OUT.hrpc"; exit 1; } | 582 | cat "$OUT.hrpc"; exit 1; } |
| 578 | "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final" | 583 | "$RENDER" --cols 80 --rows 44 < "$OUT.hrcap" > "$OUT.hrcap.final" |
| 579 | if grep -q -- "--sock $SOCKH2#c" "$OUT.hrcap.final"; then | 584 | # The pane the dead session left behind stands, and says why: the host |
| 585 | # ANSWERED and did not name `c`, which is [gone] and not [unreachable]. | ||
| 586 | grep -q -- "--sock $SOCKH2#c \[gone\]" "$OUT.hrcap.final" || { | ||
| 587 | echo "e2e FAIL: hosts restart: the pane for the session the dead daemon held did" | ||
| 588 | echo " not stand wearing [gone] on the reborn daemon:" | ||
| 589 | cat "$OUT.hrcap.final"; exit 1; } | ||
| 590 | # ...and it is a rect, not a session: nothing attached to a `c` that does | ||
| 591 | # not exist. A pane the wall had resurrected would read [up]. | ||
| 592 | if grep -- "--sock $SOCKH2#c" "$OUT.hrcap.final" | grep -q '\[up\]'; then | ||
| 580 | echo "e2e FAIL: hosts restart: the wall resurrected the session the dead daemon held:" | 593 | echo "e2e FAIL: hosts restart: the wall resurrected the session the dead daemon held:" |
| 581 | cat "$OUT.hrpc"; exit 1 | 594 | cat "$OUT.hrcap.final"; exit 1 |
| 582 | fi | 595 | fi |
| 583 | # The daemon's own account of it, which is the one that matters: a session | 596 | # The daemon's own account of it, which is the one that matters: a session |
| 584 | # the wall re-created would be here whether or not it ever got a tile. | 597 | # the wall re-created would be here whether or not it ever got a tile. |
test/e2e_12_panes.sh
| Old | New | ||
|---|---|---|---|
| @@ -697,9 +697,15 @@ ok "a resized layout survives a detach/reattach round trip via the sidecar" | |||
| 697 | # The drift a wall of daemons can actually suffer, and it is no longer a | 697 | # The drift a wall of daemons can actually suffer, and it is no longer a |
| 698 | # file being edited: a 2-pane sidecar is saved, and then one of those | 698 | # file being edited: a 2-pane sidecar is saved, and then one of those |
| 699 | # sessions ENDS while another is born. On reattach, restoreLayout heals — | 699 | # sessions ENDS while another is born. On reattach, restoreLayout heals — |
| 700 | # the survivor leaf keeps its session, the newcomer inserts beside it, and | 700 | # the survivor leaf keeps its session and the newcomer inserts beside it — |
| 701 | # the unmatched saved leaf collapses out. Two tiles, not three, and the | 701 | # and the unmatched saved leaf STANDS, wearing `gone`. Three tiles, and the |
| 702 | # wall did not refuse. | 702 | # wall did not refuse. |
| 703 | # | ||
| 704 | # It used to be two: the unmatched leaf collapsed out on the first poll | ||
| 705 | # answer. Gone panes are the change (2026-09-01) — a reachable host that | ||
| 706 | # no longer names your saved session dresses that pane instead of taking | ||
| 707 | # it, so a reboot cannot silently re-cut a wall. The heal is the same | ||
| 708 | # code; only the fate of the leaf the list disowns is different. | ||
| 703 | LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" | 709 | LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" |
| 704 | defer_rm "$LPHSTATE" | 710 | defer_rm "$LPHSTATE" |
| 705 | LPHHOSTS="$LPHSTATE/mux/hosts" | 711 | LPHHOSTS="$LPHSTATE/mux/hosts" |
| @@ -789,73 +795,51 @@ set -e | |||
| 789 | cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } | 795 | cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } |
| 790 | # The wall must not have refused: the ptyclient exited 0 (checked above), | 796 | # The wall must not have refused: the ptyclient exited 0 (checked above), |
| 791 | # and stderr mentions no layout error. | 797 | # and stderr mentions no layout error. |
| 792 | # | ||
| 793 | # The money assertion is that run 2's cut came from the SIDECAR and not | ||
| 794 | # from the aspect rule. At 80x24 the aspect rule cuts BESIDE — a vertical | ||
| 795 | # rail, a reverse-video column somewhere near 40, which run 1's capture | ||
| 796 | # has and this one must not. A restore that reported failure falls through | ||
| 797 | # to `setRootOrient(rootOrient(size))` and puts that rail back, so its | ||
| 798 | # absence is the heal having run AND won. | ||
| 799 | # | ||
| 800 | # What this leg does NOT claim, because the product does not do it — | ||
| 801 | # measured, with the sidecar read either side of the second run: | ||
| 802 | # | ||
| 803 | # saved by run 1: beside 0 / leaf 43 #0 / leaf 36 #b / focus 0 | ||
| 804 | # after the heal: stacked 0 / leaf 1 #0 / leaf 1 #c / focus 0 | ||
| 805 | # | ||
| 806 | # * the survivor's WEIGHT does not survive (43 -> 1) and neither does the | ||
| 807 | # container's ORIENTATION (beside -> stacked). Dropping the unmatched | ||
| 808 | # leaf collapses the two-child container to a bare leaf, and the | ||
| 809 | # newcomer's `insert` then re-wraps that leaf in a fresh STACKED | ||
| 810 | # container with default weights (`layout.insert`'s root-is-a-leaf | ||
| 811 | # arm). A heal keeps WHICH tiles, never HOW they were cut. | ||
| 812 | # * the focus lands on the newcomer rather than the leaf the record | ||
| 813 | # names, which is why the marker below is typed at a tile this leg | ||
| 814 | # names with `\x1c1` instead of inheriting one. | ||
| 815 | # | ||
| 816 | # Both are defects, not design: a sidecar exists to remember a cut. They | ||
| 817 | # are reported, not fixed here. | ||
| 818 | grep -qi "layout" "$OUT.lphcap2.err" && { | 798 | grep -qi "layout" "$OUT.lphcap2.err" && { |
| 819 | echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" | 799 | echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" |
| 820 | cat "$OUT.lphcap2.err"; exit 1; } | 800 | cat "$OUT.lphcap2.err"; exit 1; } |
| 821 | # Two tiles: the survivor and the newcomer. The daemon has 0 and c, and | ||
| 822 | # the tile the sidecar's surviving leaf carries is the one the marker | ||
| 823 | # landed in. | ||
| 824 | # A BESIDE cut puts a rail in a column of its own; a stacked cut has one | ||
| 825 | # only at column 1, which is a label bar and what `rail_cols`' callers | ||
| 826 | # filter out. | ||
| 827 | # Run 1 is the anchor: it really was cut beside and `lll` really moved the | 801 | # Run 1 is the anchor: it really was cut beside and `lll` really moved the |
| 828 | # rail, so run 2's silence below is a difference and not an empty capture. | 802 | # rail, so run 2's tree below is a comparison and not an empty capture. A |
| 803 | # BESIDE cut puts a rail in a column of its own; a stacked cut has one only | ||
| 804 | # at column 1, which is a label bar and what `rail_cols`' callers filter | ||
| 805 | # out. | ||
| 829 | _lph_r1=$(rail_cols "$OUT.lphcap1" | awk '$1 > 1' | tail -1) | 806 | _lph_r1=$(rail_cols "$OUT.lphcap1" | awk '$1 > 1' | tail -1) |
| 830 | [ -n "$_lph_r1" ] && [ "$_lph_r1" -ge 43 ] || { | 807 | [ -n "$_lph_r1" ] && [ "$_lph_r1" -ge 43 ] || { |
| 831 | echo "e2e FAIL: layout-heal: run 1's rail ended at column ${_lph_r1:-none}, want >= 43" | 808 | echo "e2e FAIL: layout-heal: run 1's rail ended at column ${_lph_r1:-none}, want >= 43" |
| 832 | echo " — the resize this leg saves a tree for did not happen" | 809 | echo " — the resize this leg saves a tree for did not happen" |
| 833 | cat "$OUT.lphpc1"; exit 1; } | 810 | cat "$OUT.lphpc1"; exit 1; } |
| 834 | # What run 2 may NOT be is the aspect rule's own cut. Two shapes pass, and | 811 | # The money assertion is that run 2's cut came from the SIDECAR and not |
| 835 | # both of them are the sidecar's: | 812 | # from the aspect rule, and the tree run 2 SAVES is where that reads |
| 813 | # exactly. A restore that reported failure falls through to | ||
| 814 | # `setRootOrient(rootOrient(size))` and builds default weights of 1, so a | ||
| 815 | # root that is still `beside` over a survivor still weighing 43 is the heal | ||
| 816 | # having run AND won. Measured 2026-09-01, sidecar read either side: | ||
| 836 | # | 817 | # |
| 837 | # * no vertical rail — today's heal, which drops the unmatched leaf, | 818 | # saved by run 1: beside 0 / leaf 43 #0 / leaf 36 #b / focus 0 |
| 838 | # collapses the container and re-wraps STACKED (measured above). | 819 | # saved by run 2: beside 0 / leaf 43 #0 / leaf 43 #c / leaf 36 #b |
| 839 | # * a rail at the column run 1 dragged the boundary to (>= 43) — a heal | ||
| 840 | # that also kept the saved orientation and weights, which is the fix | ||
| 841 | # the note above says is owed. | ||
| 842 | # | 820 | # |
| 843 | # The aspect rule at 80x24 cuts beside down the middle, around column 40, | 821 | # Three leaves, in that order: `insert` puts the newcomer immediately after |
| 844 | # and that is the one answer neither of those can produce. Asserting "no | 822 | # its anchor (the focus, `0`) and copies the anchor's weight, and the leaf |
| 845 | # rail at all" instead would fail the improvement and blame the restore for | 823 | # the list disowned keeps its own 36 where it stood. Nothing collapsed, so |
| 846 | # not having run, which is the opposite of what would have happened. | 824 | # nothing re-wrapped — which is why the orientation and the weights now |
| 847 | _lph_r2=$(rail_cols "$OUT.lphcap2" | awk '$1 > 1' | tail -1) | 825 | # survive a heal that used to lose both. |
| 848 | [ -z "$_lph_r2" ] || [ "$_lph_r2" -ge 43 ] || { | 826 | _lph_root=$(sed -n '2p' "$LPHLAYOUT") |
| 849 | echo "e2e FAIL: layout-heal: run 2's vertical rail is at column $_lph_r2 — neither" | 827 | [ "$_lph_root" = "beside 0" ] || { |
| 850 | echo " the sidecar's own cut (>= 43, where run 1 left it) nor the bare" | 828 | echo "e2e FAIL: layout-heal: run 2 saved root '$_lph_root', want 'beside 0'" |
| 851 | echo " leaf a heal collapses to. The aspect rule cut this wall, so the" | 829 | echo " — the aspect rule cut this wall, so the saved tree never reached it" |
| 852 | echo " saved tree never reached it" | 830 | cat "$LPHLAYOUT"; exit 1; } |
| 853 | cat "$OUT.lphpc2"; exit 1; } | 831 | _lph_leaves=$(sed -n 's/^ *leaf [0-9][0-9]* .*#\(.*\)$/\1/p' "$LPHLAYOUT" | tr '\n' ' ') |
| 854 | # ...and only now the bars, which the cut above is what makes readable: a | 832 | [ "$_lph_leaves" = "0 c b " ] || { |
| 855 | # stacked stripe is the full 80 columns, and a beside one is 39 — half a | 833 | echo "e2e FAIL: layout-heal: run 2 saved leaves '$_lph_leaves', want '0 c b ':" |
| 856 | # socket path, which `labelText` truncates before it reaches the `#c`. | 834 | cat "$LPHLAYOUT"; exit 1; } |
| 857 | grep -q -- "--sock $SOCK60#c \[up\]" "$OUT.lphcap2" || { | 835 | grep -q -- "^ leaf 43 .*#0\$" "$LPHLAYOUT" || { |
| 858 | echo "e2e FAIL: layout-heal: the newcomer never became a tile:" | 836 | echo "e2e FAIL: layout-heal: the survivor's weight did not survive the heal:" |
| 837 | cat "$LPHLAYOUT"; exit 1; } | ||
| 838 | # ...and the leaf the list disowned says on screen why it is still there. | ||
| 839 | # The state word survives a bar's truncation where the socket path does | ||
| 840 | # not, so this is the needle that reads at any pane width. | ||
| 841 | grep -qa '\[gone\]' "$OUT.lphcap2" || { | ||
| 842 | echo "e2e FAIL: layout-heal: the ended session's pane wore no [gone]:" | ||
| 859 | cat "$OUT.lphpc2"; exit 1; } | 843 | cat "$OUT.lphpc2"; exit 1; } |
| 860 | timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 | 844 | timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 |
| 861 | grep -q "lph-heal-marker" "$OUT.lphfa" || { | 845 | grep -q "lph-heal-marker" "$OUT.lphfa" || { |
| @@ -864,7 +848,7 @@ grep -q "lph-heal-marker" "$OUT.lphfa" || { | |||
| 864 | assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" | 848 | assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" |
| 865 | D61PID="" | 849 | D61PID="" |
| 866 | rm -rf "$LPHSTATE" | 850 | rm -rf "$LPHSTATE" |
| 867 | ok "the layout heals on live drift: the survivor keeps its session, the newcomer joins" | 851 | ok "the layout heals on live drift: the survivor keeps its session, the newcomer joins, the ended one's pane stands gone" |
| 868 | 852 | ||
| 869 | # ---- layout sidecar: silent degrade on garbage ----------------------- | 853 | # ---- layout sidecar: silent degrade on garbage ----------------------- |
| 870 | # | 854 | # |
| @@ -922,3 +906,4 @@ assert_stopped "$SOCK61" "$D62PID" "layout-degrade" "$OUT.lpdstop" | |||
| 922 | D62PID="" | 906 | D62PID="" |
| 923 | rm -rf "$LPDSTATE" | 907 | rm -rf "$LPDSTATE" |
| 924 | ok "a corrupted sidecar degrades silently to the default layout" | 908 | ok "a corrupted sidecar degrades silently to the default layout" |
| 909 | |||