#!/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