diff --git a/.tests/lab/scenarios/20-picker.sh b/.tests/lab/scenarios/20-picker.sh index d369ceb..5d149de 100644 --- a/.tests/lab/scenarios/20-picker.sh +++ b/.tests/lab/scenarios/20-picker.sh @@ -20,11 +20,18 @@ # the exit status is the number of failures. # # It exits non-zero today, and every failure it reports is the product's, not -# the harness's: BUG-1/2 (^t over a filter ticks invasive rows that are not on -# screen, and is not its own undo), BUG-3 (a deliberate empty selection is not -# restored), BUG-4/6 (no tty: fzf's raw error, and the defaults preset written -# by a picker that never drew), BUG-7 (an unreadable state file makes every -# keystroke a silent no-op). Fixing those turns this green. +# the harness's. BUG-3 (a deliberate empty selection is not restored), BUG-4/6 +# (no tty: fzf's raw error, and the defaults preset written by a picker that +# never drew) and BUG-7 (an unreadable state file makes every keystroke a +# silent no-op) were fixed, and the assertions below now hold the FIXED +# behaviour -- they fail again if it regresses. +# +# What is left is DEFERRED, not unknown, and there is no expected-fail +# mechanism here to hide it behind: BUG-1/2, in `promise 3`, three assertions. +# ^t over an --exact filter widens along @needs and ticks invasive rows that +# are not on screen, and ^t ^t is not its own undo because the reverse edges +# do not retract what the forward ones pulled in. Until those are fixed this +# scenario exits 3. set -u FAILS=0 @@ -909,14 +916,36 @@ export XDG_CACHE_HOME=/tmp/nocache; rm -rf /tmp/nocache # is modified BY THE TEST here; dotup is still the thing that must not modify it. mkdir -p /tmp/nonet printf '#!/bin/sh\nexit 6\n' > /tmp/nonet/curl; chmod +x /tmp/nonet/curl +# Two things are wrong at once here -- there is no terminal AND no fzf can be +# fetched -- and cmd_pick now answers the terminal first. The /dev/tty probe +# was moved AHEAD of ensure_fzf on purpose (a run in a pipe used to print +# fzf's raw error having already overwritten the selection with the defaults +# preset), so with no tty the fzf message is unreachable by definition. This +# arm asserts the ORDER; the pty arm below asserts the fzf message. out=$(PATH=/tmp/nonet:$PATH "$D" pick 2>&1); rc=$? -[ "$rc" -eq 2 ] || fail "an unfetchable fzf exits $rc, not 2" +[ "$rc" -eq 2 ] || fail "with neither a terminal nor a fetchable fzf, pick exits $rc, not 2" ok "dotup pick exits 2 when fzf cannot be had" printf '%s\n' "$out" | sed 's/^/ | /' case $out in +*"no terminal"*) ok "the /dev/tty probe answers before ensure_fzf is reached" ;; +*) fail "with no terminal the tty probe must answer first: got '$(printf '%s' "$out" | head -1)'" ;; +esac +[ -s "$ST/selected" ] && fail "a pick that failed the tty probe still wrote a selection" + +# The same missing fzf, with a terminal attached. That is now the only way to +# reach ensure_fzf's error at all, so the message is checked through a pty: +# `script -e` hands the child a real /dev/tty and returns the child's status. +rm -rf /tmp/nocache +: > /tmp/nofzf.log +PATH=/tmp/nonet:$PATH script -q -e -c "$D pick" /tmp/nofzf.log >/dev/null 2>&1; rc=$? +nofzf=$(tr -d '\r' < /tmp/nofzf.log) +[ "$rc" -eq 2 ] || fail "with a terminal but no fetchable fzf, pick exits $rc, not 2" +ok "with a terminal attached and no fzf to be had, dotup pick exits 2" +printf '%s\n' "$nofzf" | sed 's/^/ | /' +case $nofzf in *"numbered prompt"*) fail "the error still promises a numbered prompt that does not exist" ;; esac -case $out in +case $nofzf in *"dotup preset defaults && dotup install"*) ok "it names a fallback that exists" ;; *) fail "the error names no usable way forward" ;; esac @@ -933,6 +962,15 @@ head_ "hostile 4 — a hand-edited state file" fresh printf 'core/eza\r\ncore/ripgrep\n\n core/htop\ncore/bat\ncore/bat\ng:core\nnot/a-package\n' > "$ST/selected" printf 'core\nnosuchgroup\n' > "$ST/expanded" +# The hand-edited file is only under test if the picker actually reads it. +# Seeding the defaults preset is now decided by "has a pick ever completed" +# ($STATE/picked), not by "$SEL is empty" -- emptiness could not tell a +# deliberate `^x`-then-enter apart from a brand new machine. So a state +# directory with no `picked` in it gets the 43 default rows written over the +# top of the junk BEFORE anything draws, and the session below would then be +# testing the preset rather than the hand edit. Every machine that has ever +# pressed enter has this marker; the fixture needs it too. +: > "$ST/picked" cat > /tmp/steps.corrupt <<'EOS' ready snap c1 @@ -956,8 +994,12 @@ if drive corrupt 44 220 /tmp/steps.corrupt "$D pick"; then [ "$(markof c1 p:core/htop)" = "." ] || note "a leading-space line was honoured" [ "$(markof c1 p:core/bat)" = "x" ] || fail "the duplicated clean key was not honoured" ok "a duplicated key is read once, not twice" - selhas c2 core/tree || fail "space stopped working after junk in the state file" - ok "space still toggles with junk in the file (core/tree went on)" + if selhas c2 core/tree; then + ok "space still toggles with junk in the file (core/tree went on)" + else + fail "space stopped working after junk in the state file: core/tree is not in + $(seln c2) selected line(s) after the keystroke" + fi [ "$(seln c1)" -eq "$(seln c2)" ] && note "the first toggle silently rewrote the whole file — sort -u collapsed the duplicate, so the line count did not move even though a package was added" @@ -974,30 +1016,27 @@ fi fresh "$D" preset defaults chmod 000 "$ST/selected" -cat > /tmp/steps.perm <<'EOS' -ready -snap perm1 -at g:media -key space \x20 -sleep 2500 -snap perm2 -abort -end -EOS -if drive perm 44 220 /tmp/steps.perm "$D pick"; then - if [ "$(markof perm1 g:core)" = "." ]; then - note "an unreadable selected file draws as 'nothing is ticked' rather than as - an error — the awk that reads it cannot tell 'empty' from 'refused'" - fi - if diff -q /tmp/snap.perm1 /tmp/snap.perm2 >/dev/null; then - fail "with an unreadable state file, space does nothing at all and says nothing: - cmd_toggle's final \`sort -u \$tmp > \$SEL\` fails, execute-silent throws - the status away, and the row never moves. The picker looks alive and is - inert. (BUG-7)" - else - ok "a toggle still lands with an unreadable state file" - fi -fi +# There is no session to drive here any more. BUG-7 was that the picker DREW +# on an unreadable $SEL: every bind is execute-silent, which throws its child's +# status away, so each tick was discarded in silence and the picker looked +# alive while being completely inert. Readable-and-writable is now a +# precondition of drawing, so the assertion is that nothing draws at all -- +# and it has to be made through a pty, because the /dev/tty probe runs first +# and would otherwise be the thing that answers. +: > /tmp/permrefuse.log +script -q -e -c "$D pick" /tmp/permrefuse.log >/dev/null 2>&1; permrc=$? +permout=$(tr -d '\r' < /tmp/permrefuse.log) +[ "$permrc" -eq 2 ] || fail "with an unreadable state file, dotup pick exits $permrc, not 2" +ok "dotup pick refuses to draw on an unreadable state file (rc=$permrc)" +printf '%s\n' "$permout" | sed 's/^/ | /' +case $permout in +*"not readable and writable"*) ok "the refusal names the file, and says the ticks would be lost" ;; +*) fail "the refusal does not explain itself: '$(printf '%s' "$permout" | head -1)'" ;; +esac +case $permout in +*"delete it, and re-run"*) ok "and it names a way out (fix the ownership, or delete it)" ;; +*) fail "the refusal names no way out" ;; +esac chmod 644 "$ST/selected" 2>/dev/null || : head_ "hostile 5 — ^c in the middle of the picker"