Add a test of tiled, floating, zoomed and hidden pane combinations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael Grant
2026-10-01 11:14:28 +01:00
parent 24d0ead2b2
commit 435e2f8efb

404
regress/pane-states.sh Normal file
View File

@@ -0,0 +1,404 @@
#!/bin/sh
# Combinations of tiled, floating, zoomed and hidden panes.
#
# Every pane is tiled or floating, zoomed or not, and hidden or not, and these
# are independent. For each combination of those states for two panes (and for
# three), each pane command that changes or depends on them is run and the
# result is compared with what the command should do, using awk to work out the
# expectation from the state before. This covers:
# - select-pane, including -Z and moving to the next, last or a directional pane;
# - resize-pane -Z (toggle a zoom), -H (hide or show), -a -Z (unzoom all) and
# -a -H (show desktop), which is run twice to check it restores the panes;
# - break-pane -W and join-pane to float and tile panes;
# - split-window, new-pane and kill-pane;
# - next-layout, select-layout, rotate-window and resize-window.
#
# It also checks properties that must hold after every command: the active pane
# is not hidden if any pane is not, there is one active pane, a window with one
# pane is not zoomed, window_zoomed_flag matches the panes and visible panes have
# a size. The status line clicks and the cursor are in other tests.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
SOCK=testA$$
RUN=0
tm()
{
$TEST_TMUX -L"$SOCK$RUN" -f/dev/null "$@"
}
cleanup()
{
tm kill-server >/dev/null 2>&1
}
trap cleanup 0
trap 'exit 1' 1 2 15
FMT='#{pane_id} #{pane_floating_flag} #{pane_zoomed_flag} #{pane_hidden_flag} #{pane_active} #{pane_width} #{pane_height} #{window_zoomed_flag}'
# The oracle. The state before is on lines starting B and the state after on
# lines starting A: id floating zoomed hidden active width height window_zoomed.
# op is the operation, tgt the target pane, rc the exit status of the command and
# mode is "setup" to check that the start state was made as wanted, with want
# holding floating, zoomed and hidden for each pane in order.
ORACLE='
function fail(msg)
{
printf "%s %s (exit %s): %s\n", op, tgt, rc, msg
bad = 1
exit 1
}
function sameF(skip, i, id)
{
for (i = 1; i <= nb; i++) {
id = bid[i]
if (id == skip || !(id in ina))
continue
if (aF[id] != bF[id])
fail("pane " id " floating " bF[id] " became " aF[id])
}
}
function sameZ(skip, i, id)
{
for (i = 1; i <= nb; i++) {
id = bid[i]
if (id == skip || !(id in ina))
continue
if (aZ[id] != bZ[id])
fail("pane " id " zoomed " bZ[id] " became " aZ[id])
}
}
function sameH(skip, i, id)
{
for (i = 1; i <= nb; i++) {
id = bid[i]
if (id == skip || !(id in ina))
continue
if (aH[id] != bH[id])
fail("pane " id " hidden " bH[id] " became " aH[id])
}
}
function noZoom( i)
{
for (i = 1; i <= na; i++) {
if (aZ[aid[i]])
fail("pane " aid[i] " is still zoomed")
}
}
function covered(t, i, q)
{
if (bF[t] || bZ[t])
return 0
for (i = 1; i <= nb; i++) {
q = bid[i]
if (q != t && bZ[q] && !bH[q])
return 1
}
return 0
}
function newPane( i, k, id)
{
k = 0
for (i = 1; i <= na; i++) {
id = aid[i]
if (!(id in inb)) {
k++
created = id
}
}
if (k != 1)
fail(k " new panes, expected 1")
if (na != nb + 1)
fail("pane count " nb " became " na)
}
$1 == "B" {
nb++; id = $2; bid[nb] = id; inb[id] = 1
bF[id] = $3; bZ[id] = $4; bH[id] = $5; bA[id] = $6
}
$1 == "A" {
na++; id = $2; aid[na] = id; ina[id] = 1
aF[id] = $3; aZ[id] = $4; aH[id] = $5; aA[id] = $6
aW[id] = $7; aT[id] = $8; aWZ[id] = $9
}
END {
if (mode == "setup") {
split(want, w, " ")
for (i = 1; i <= nb; i++) {
id = bid[i]; p = substr(id, 2) + 0
if (bF[id] != w[3 * p + 1] || bZ[id] != w[3 * p + 2] ||
bH[id] != w[3 * p + 3])
fail("pane " id " is " bF[id] bZ[id] bH[id] ", wanted " \
w[3 * p + 1] w[3 * p + 2] w[3 * p + 3])
}
exit 0
}
# Properties of any state.
if (na < 1)
fail("no panes left")
nact = 0; anyZ = 0; anyvis = 0
for (i = 1; i <= na; i++) {
id = aid[i]
if (aA[id] == 1) { nact++; act = id }
if (aZ[id]) anyZ = 1
if (!aH[id]) anyvis = 1
}
if (nact != 1)
fail(nact " active panes")
for (i = 1; i <= na; i++) {
id = aid[i]
if (aWZ[id] != anyZ)
fail("window_zoomed_flag " aWZ[id] " with zoomed panes " anyZ)
if (!aH[id] && (aW[id] < 1 || aT[id] < 1))
fail("pane " id " is " aW[id] "x" aT[id])
}
if (na == 1 && anyZ)
fail("the only pane is zoomed")
if (anyvis && aH[act])
fail("active pane " act " is hidden but others are not")
# A command that failed changed nothing.
if (rc != 0) {
if (na != nb)
fail("pane count " nb " became " na)
sameF(""); sameZ(""); sameH("")
exit 0
}
T = tgt
if (op == "select") {
if (aA[T] != 1) fail("not active")
if (aH[T]) fail("still hidden")
if (covered(T)) noZoom(); else sameZ("")
sameF(""); sameH(T)
} else if (op == "zoom") {
sameF("")
if (bZ[T]) {
if (aZ[T]) fail("not unzoomed")
sameZ(T); sameH("")
} else {
if (!aZ[T]) fail("not zoomed")
if (aH[T]) fail("zoomed pane is hidden")
if (aA[T] != 1) fail("zoomed pane is not active")
sameZ(T); sameH(T)
}
} else if (op == "hide") {
sameF("")
if (bH[T]) {
if (aH[T]) fail("not shown")
if (aA[T] != 1) fail("not active when shown")
if (covered(T)) noZoom(); else sameZ("")
} else {
if (!aH[T]) fail("not hidden")
sameZ("")
}
sameH(T)
} else if (op == "desktop") {
sameF(""); sameZ("")
for (i = 1; i <= nb; i++) {
id = bid[i]
want = (bH[id] + bF[id] + bZ[id] > 0) ? 1 : 0
if (aH[id] != want)
fail("pane " id " hidden " bH[id] " floating " bF[id] \
" zoomed " bZ[id] " became hidden " aH[id])
}
} else if (op == "desktop2") {
sameF(""); sameZ(""); sameH("")
} else if (op == "unzoomall") {
sameF(""); sameH(""); noZoom()
} else if (op == "break") {
if (bF[T] || !aF[T]) fail("not floated")
sameF(T); sameZ(""); sameH("")
} else if (op == "join") {
if (!bF[T] || aF[T]) fail("not tiled")
sameF(T); sameZ(""); sameH("")
} else if (op == "kill") {
if (T in ina) fail("not killed")
if (na != nb - 1) fail("pane count " nb " became " na)
sameF(T); sameH(T)
if (na == 1) noZoom(); else sameZ(T)
} else if (op == "split") {
newPane()
if (aF[created] != bF[T]) fail("new pane floating " aF[created])
if (aZ[created] || aH[created]) fail("new pane zoomed or hidden")
sameF(""); sameZ("")
if (bH[T] && !bF[T]) {
if (aH[T]) fail("tiled target not shown")
sameH(T)
} else
sameH("")
} else if (op == "newpane") {
newPane()
if (!aF[created]) fail("new pane is not floating")
if (aZ[created] || aH[created]) fail("new pane zoomed or hidden")
sameF(""); sameZ(""); sameH("")
} else if (op == "nextlayout" || op == "tiled") {
sameF(""); sameH(""); noZoom()
} else {
# Moving around or changing layout: floating panes stay floating
# and nothing is hidden.
sameF("")
for (i = 1; i <= nb; i++) {
id = bid[i]
if (id in ina && aH[id] > bH[id])
fail("pane " id " was hidden by this")
}
}
exit 0
}
'
# describe N STATE: print the start state in words.
describe()
{
n=$1
s=$2
i=0
out=
while [ $i -lt "$n" ]; do
bits=$(( (s >> (3 * i)) & 7 ))
k=T
[ $((bits & 1)) -ne 0 ] && k=F
z=
[ $((bits & 2)) -ne 0 ] && z=z
h=
[ $((bits & 4)) -ne 0 ] && h=h
out="$out %$i=$k$z$h"
i=$((i + 1))
done
echo "$out"
}
# run_one N STATE OP TARGET: set up the state, run the operation, check it.
run_one()
{
n=$1
s=$2
op=$3
tgt=$4
RUN=$((RUN + 1))
set -- new-session -d -x 80 -y 24 cat
i=1
while [ $i -lt "$n" ]; do
set -- "$@" ';' split-window -d ''
i=$((i + 1))
done
want=
for stage in 1 2 4; do
i=0
while [ $i -lt "$n" ]; do
bits=$(( (s >> (3 * i)) & 7 ))
if [ $((bits & stage)) -ne 0 ]; then
case $stage in
1) set -- "$@" ';' break-pane -d -W -s "%$i" ;;
2) set -- "$@" ';' resize-pane -Z -t "%$i" ;;
4) set -- "$@" ';' resize-pane -H -t "%$i" ;;
esac
fi
i=$((i + 1))
done
done
i=0
while [ $i -lt "$n" ]; do
bits=$(( (s >> (3 * i)) & 7 ))
want="$want $((bits & 1)) $(((bits >> 1) & 1)) $(((bits >> 2) & 1))"
i=$((i + 1))
done
set -- "$@" ';' list-panes -F "B $FMT"
before=$(tm "$@" 2>&1)
if ! msg=$(echo "$before" | awk -v mode=setup -v want="$want" \
-v op=setup -v tgt=- -v rc=0 "$ORACLE" 2>&1); then
echo "setup of $(describe "$n" "$s") failed: $msg"
echo "$before"
cleanup
exit 1
fi
p="-t $tgt"
rc=0
case $op in
select) tm select-pane -t "$tgt" || rc=$? ;;
selectZ) tm select-pane -Z -t "$tgt" || rc=$? ;;
zoom) tm resize-pane -Z -t "$tgt" || rc=$? ;;
hide) tm resize-pane -H -t "$tgt" || rc=$? ;;
break) tm break-pane -d -W -s "$tgt" || rc=$? ;;
join) tm join-pane -d -s "$tgt" -t "$tgt" || rc=$? ;;
kill) tm kill-pane -t "$tgt" || rc=$? ;;
split) tm split-window -d -t "$tgt" '' || rc=$? ;;
newpane) tm new-pane -d -t "$tgt" -x 20 -y 6 '' || rc=$? ;;
desktop) tm resize-pane -a -H -t %0 || rc=$? ;;
desktop2)
tm resize-pane -a -H -t %0 || rc=$?
tm resize-pane -a -H -t %0 || rc=$?
;;
unzoomall) tm resize-pane -a -Z -t %0 || rc=$? ;;
nextlayout) tm next-layout -t %0 || rc=$? ;;
tiled) tm select-layout -t %0 tiled || rc=$? ;;
rotate) tm rotate-window -t %0 || rc=$? ;;
nextpane) tm select-pane -t :.+ || rc=$? ;;
lastpane) tm last-pane -t %0 || rc=$? ;;
left) tm select-pane -L || rc=$? ;;
right) tm select-pane -R || rc=$? ;;
up) tm select-pane -U || rc=$? ;;
down) tm select-pane -D || rc=$? ;;
resize) tm resize-window -t %0 -x 50 -y 14 || rc=$? ;;
*) echo "unknown operation $op"; exit 1 ;;
esac >/dev/null 2>&1
after=$(tm list-panes -F "A $FMT" 2>&1)
result=$( (echo "$before"; echo "$after") |
awk -v mode=check -v op="$op" -v tgt="$tgt" -v rc="$rc" "$ORACLE")
if [ $? -ne 0 ] || [ -n "$result" ]; then
echo "FAILED with $n panes, start $(describe "$n" "$s"):"
echo " $result"
echo "before:"; echo "$before" | sed 's/^/ /'
echo "after:"; echo "$after" | sed 's/^/ /'
cleanup
exit 1
fi
cleanup
}
PER_PANE="select selectZ zoom hide break join kill split newpane"
GLOBAL="desktop desktop2 unzoomall nextlayout tiled rotate nextpane lastpane \
left right up down resize"
# Two panes: every state, every operation.
s=0
while [ $s -lt 64 ]; do
for op in $GLOBAL; do
run_one 2 $s "$op" %0
done
for op in $PER_PANE; do
run_one 2 $s "$op" %0
run_one 2 $s "$op" %1
done
s=$((s + 1))
done
# Three panes: every state, and one operation on each, rotating so that every
# operation and target is used with many different states.
ALL="$GLOBAL"
for op in $PER_PANE; do
ALL="$ALL $op:0 $op:1 $op:2"
done
set -- $ALL
M=$#
s=0
while [ $s -lt 512 ]; do
set -- $ALL
shift $(( (s * 7 + s / M) % M ))
item=$1
case $item in
*:*) run_one 3 $s "${item%:*}" "%${item#*:}" ;;
*) run_one 3 $s "$item" %0 ;;
esac
s=$((s + 1))
done
exit 0