diff --git a/regress/mode-kill.sh b/regress/mode-kill.sh new file mode 100755 index 000000000..a3e0d09b9 --- /dev/null +++ b/regress/mode-kill.sh @@ -0,0 +1,171 @@ +#!/bin/sh + +# Tests of the -k flag on the mode-entering commands (cmd-copy-mode.c and +# cmd-choose-tree.c). With -k the pane is killed when the mode is exited: this +# is stored on the mode entry in window_pane_set_mode() and acted on in +# window_pane_reset_mode() (window.c). It is exercised here for: +# +# - copy-mode -k (window-copy.c); +# - choose-tree -k (window-tree.c); +# - choose-buffer -k (window-buffer.c). +# +# choose-tree and choose-buffer share cmd_choose_tree_exec(), which also backs +# choose-client and customize-mode, so those are not repeated. +# +# Each mode is entered in the active pane of a two-pane window: exiting with +# -k must remove that pane and leave the other. copy-mode is left with the +# server-side "-X cancel" and needs no client, so those tests run first. The +# tree modes only act on a key once the client has drawn the mode, so - as in +# choose-tree.sh - a second server then provides a client (an inner "tmux +# attach") and its pane is captured to wait until the mode has rendered before +# the exit key is sent. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMP=$(mktemp -d) || exit 1 +TMUX_TMPDIR="$TMP" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +cleanup() +{ + $TMUX kill-server 2>/dev/null + $TMUX2 kill-server 2>/dev/null + rm -rf "$TMP" +} +trap cleanup EXIT + +fail() +{ + echo "$1" >&2 + exit 1 +} + +# capture the screen rendered by the inner client. +capture() +{ + $TMUX2 capture-pane -p -t out:0 2>/dev/null +} + +# wait_clients $n: wait until the test server has exactly $n clients. +wait_clients() +{ + i=0 + while [ "$i" -lt 50 ]; do + c=$($TMUX list-clients -F x 2>/dev/null | grep -c x) + [ "$c" -eq "$1" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "expected $1 clients, have $c" +} + +# wait_mode $target $state: wait until a pane enters (1) or leaves (0) mode. +wait_mode() +{ + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t "$1" '#{pane_in_mode}' \ + 2>/dev/null) + [ "$got" = "$2" ] && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "pane $1 mode state is '$got', expected '$2'" +} + +# wait_for $marker: wait until the rendered screen contains $marker. +wait_for() +{ + i=0 + while [ "$i" -lt 50 ]; do + capture | grep -F -q "$1" && return 0 + sleep 0.2 + i=$((i + 1)) + done + fail "timed out waiting for '$1' to render" +} + +# pane_gone $pane-id: true if the pane no longer exists. (display-message -t +# on a missing pane id falls back to a default target and succeeds, so the +# pane list is searched instead.) +pane_gone() +{ + ! $TMUX list-panes -s -t m -F '#{pane_id}' 2>/dev/null | \ + grep -q -x "$1" +} + +# open_window: make a fresh two-pane window and leave the new (active) pane +# as $active and the other as $other. +open_window() +{ + $TMUX new-window -t m: -n w 'cat' || fail "new-window failed" + $TMUX split-window -t m:w 'cat' || fail "split-window failed" + other=$($TMUX display-message -p -t m:w.0 '#{pane_id}') + active=$($TMUX display-message -p -t m:w '#{pane_id}') +} + +# check_killed $label: wait for the active pane to be killed, leaving only the +# other pane, then drop the window. +check_killed() +{ + i=0 + while [ "$i" -lt 50 ]; do + pane_gone "$active" && break + sleep 0.2 + i=$((i + 1)) + done + pane_gone "$active" || fail "$1: pane not killed on exit" + panes=$($TMUX list-panes -t m:w -F '#{pane_id}' | tr '\n' ' ') + [ "$panes" = "$other " ] || \ + fail "$1: expected only $other left, have $panes" + $TMUX kill-window -t m:w 2>/dev/null +} + +# Session m; window 0 keeps a live pane so the session (and later the client) +# survives each test killing a pane. +$TMUX new-session -d -s m -x 80 -y 24 'cat' || exit 1 + +# --- copy-mode -k kills the pane, plain copy-mode does not ------------------- +# +# copy-mode is exited with the server-side "-X cancel", so no client is needed +# and none is attached yet. +open_window +$TMUX copy-mode -k -t m:w || fail "copy-mode -k failed" +wait_mode "$active" 1 +$TMUX send-keys -t m:w -X cancel || fail "copy cancel failed" +check_killed 'copy-mode -k' + +open_window +$TMUX copy-mode -t m:w || fail "copy-mode failed" +wait_mode "$active" 1 +$TMUX send-keys -t m:w -X cancel || fail "copy cancel failed" +wait_mode "$active" 0 +pane_gone "$active" && fail 'copy-mode: pane killed without -k' +$TMUX kill-window -t m:w 2>/dev/null + +# --- choose-tree -k and choose-buffer -k kill the pane ---------------------- +# +# These need the client to draw the mode before a key acts, so attach one now. +# A paste buffer is needed for choose-buffer to have something to show, and a +# distinct -F marker per call is waited for in the capture so the exit key is +# only sent once the mode is drawn. +$TMUX set-buffer 'mode-kill buffer' || exit 1 +$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t m" || exit 1 +wait_clients 1 +open_window +$TMUX choose-tree -k -F 'TREEMARK' -t m:w || fail "choose-tree -k failed" +wait_for 'TREEMARK' +$TMUX send-keys -t m:w q || fail "choose-tree exit failed" +check_killed 'choose-tree -k' + +open_window +$TMUX choose-buffer -k -F 'BUFMARK' -t m:w || fail "choose-buffer -k failed" +wait_for 'BUFMARK' +$TMUX send-keys -t m:w q || fail "choose-buffer exit failed" +check_killed 'choose-buffer -k' + +exit 0 diff --git a/regress/pane-ops.sh b/regress/pane-ops.sh index 762bea200..29c79e2e2 100644 --- a/regress/pane-ops.sh +++ b/regress/pane-ops.sh @@ -399,6 +399,19 @@ check_fmt 'P:9.0' '#{pane_pid}' '' check_fail 'command cannot be given for empty pane' \ new-window -d -E -t P:10 -n empty 'true' +# An empty string as the sole argument is equivalent to -E: the pane is +# created empty, running no command. +check_ok new-window -d -t P:12 -n empty-str '' +check_fmt 'P:12.0' '#{pane_dead}' '0' +check_fmt 'P:12.0' '#{pane_pid}' '' +check_ok kill-window -t P:12 + +# A missing command (rather than an empty one) runs the default command, so +# the pane is not empty and has a process. +check_ok new-window -d -t P:12 -n default-cmd +check_fmt 'P:12.0' '#{?pane_pid,live,empty}' 'live' +check_ok kill-window -t P:12 + # respawn-pane -E stores the command and cwd without starting it. tmp=${TMPDIR:-/tmp}/tmux-pane-ops-empty-$$ rm -f "$tmp" @@ -453,6 +466,13 @@ check_fmt 'P:2' '#{window_panes}' '2' check_fail 'command cannot be given for empty pane' \ split-window -d -E -t P:2.0 'sleep 5' +# An empty string as the sole argument splits with an empty pane, like -E. +eid=$($TMUX split-window -d -P -F '#{pane_id}' -t P:2.0 '') +check_fmt "$eid" '#{pane_dead}' '0' +check_fmt "$eid" '#{pane_pid}' '' +check_ok kill-pane -t "$eid" +check_fmt 'P:2' '#{window_panes}' '2' + # -e adds to the new pane's environment. eid=$($TMUX split-window -d -P -F '#{pane_id}' -e GREETING=hello -t P:2.0 \ 'echo $GREETING; exec cat')