diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index b1e19e9e0..41f74f1c7 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -72,14 +72,14 @@ jobs: - name: build run: | - sh autogen.sh + sh autogen.sh ./configure ${{ matrix.configure }} ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)" - name: test run: | cd regress - ${{ matrix.make }} + ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)" - name: logs if: failure() diff --git a/CHANGES b/CHANGES index 62a7f8fc5..dcb26e1fc 100644 --- a/CHANGES +++ b/CHANGES @@ -10,8 +10,8 @@ CHANGES FROM 3.7c TO 3.8 - modal panes may be created with new-pane -O; a window can have one modal pane and it prevents interaction with the other panes while it is active. -C closes the pane when the mouse is clicked outside it and -K sends all - keys, including the prefix key, to the pane. A modal pane is now used for - the editor in buffer mode; + keys, including the prefix key, to the pane. -D closes the modal pane with + Escape or C-c. A modal pane is now used for the editor in buffer mode; - new-pane -A creates a floating pane which remains above a zoomed pane; modal panes do this by default; @@ -39,6 +39,9 @@ CHANGES FROM 3.7c TO 3.8 The old format is still accepted; control mode clients receive old layouts unless they set the new-layouts flag. +* Add remain-on-exit failed-key to keep a pane open after a failed command + until a key is pressed. + * Add support for themes and improve default colours: - tmux now has builtin light and dark colour themes for terminals with 256 or @@ -115,6 +118,12 @@ CHANGES FROM 3.7c TO 3.8 in terminal-features, and add a utf8 feature for terminals which support UTF-8 output. +* Add an appesc terminal feature for mintty's application escape key mode, + avoiding unsupported sequences on other terminals (Pete Dietl, issue 5626). + +* Preserve existing Ss and Se terminfo capabilities for cursor styles and + restoring the default cursor (issue 5476). + * Add a #{A/count:frames} modifier to show a series of frames as an animation in the status line (issue 5412 from Fernando Daciuk). @@ -189,10 +198,23 @@ CHANGES FROM 3.7c TO 3.8 sent inside %begin/%end (issue 5458), do not let a stuck client prevent the server from exiting (issue 5444), and reset control mode offsets when a pane is respawned (issue 5498). Bound buffered command replies so a control mode - client that stops reading cannot consume unlimited memory (issue 5565). + client that stops reading cannot consume unlimited memory (issue 5565), and + fix a collision between the output discard and bracketed paste flags (issue + 5622). * Fix grouped sessions sometimes being left as unusable command targets while - they are being killed (Bryce Miller, issue 5180). + they are being killed (Bryce Miller, issue 5180), and prevent reuse of a + session with no windows while detaching (Heon Jeong, issue 5620). + +* Free wait-for clients when they disconnect and do not run their remaining + queued commands (issue 5614). + +* Expand formats in run-shell -c like other start-directory flags (reported by + Saul Nogueras). + +* Fix session alert flag formats to check all windows (issue 5599). + +* Do not clear the marked pane when clicking on a pane border. * Fix choose mode filtering when more than one pane exists (issue 5326), and fix an infinite loop in customize mode when a filter does not match. @@ -203,11 +225,18 @@ CHANGES FROM 3.7c TO 3.8 status lines, menus, popups, wide characters and floating panes. This includes fixes for flicker with alternate screen applications and scrollbars (Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue - 5098). + 5098), and misplaced wrapped lines during partial redraws (Ben Maurer, issue + 5625). * Do not crash looking for the next or previous session (issue 5344), or when no client is available. +* Fix crashes when destroying a zoomed pane (Romain Francoise, issue 5591), + copying from a blank source pane (Kaixuan Li), or opening a mode prompt when + the client is offset into the window. + +* Avoid an infinite loop when assigning a width to WCHAR_MAX (issue 5602). + CHANGES FROM 3.7b TO 3.7c * Build with jemalloc on macOS to avoid what appears to be a bug in calloc diff --git a/regress/Makefile b/regress/Makefile index 7a4387bd3..18518d274 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -1,33 +1,17 @@ TESTS!= echo *.sh LOGDIR=logs -.PHONY: all -.NOTPARALLEL: all +.PHONY: all prepare $(TESTS) -all: - @mkdir -p "$(LOGDIR)"; \ - rm -f "$(LOGDIR)"/*.log; \ - failed=0; failures=; \ +all: prepare $(TESTS) + @failed=0; failures=; \ for test in $(TESTS); do \ base=$${test##*/}; \ log="$(LOGDIR)/$${base%.sh}.log"; \ - rm -f "$$log"; \ - printf '%-40s ' "$$test"; \ - start=$$(date +%s); \ - env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \ - sh -x "$$test" >"$$log" 2>&1; \ - if [ $$? -eq 0 ]; then \ - end=$$(date +%s); \ - rm -f "$$log"; \ - echo "PASS ($$((end - start))s)"; \ - else \ - end=$$(date +%s); \ - echo "FAIL ($$((end - start))s)"; \ - echo " log: $$log"; \ + if [ -f "$$log" ]; then \ failed=1; \ failures="$$failures $$test"; \ fi; \ - sleep 1; \ done; \ if [ "$$failed" -ne 0 ]; then \ echo; \ @@ -39,3 +23,22 @@ all: rmdir "$(LOGDIR)" 2>/dev/null || true; \ fi; \ exit $$failed + +prepare: + @mkdir -p "$(LOGDIR)" + @rm -f "$(LOGDIR)"/*.log + +$(TESTS): prepare + @base="$@"; base=$${base##*/}; \ + log="$(LOGDIR)/$${base%.sh}.log"; \ + start=$$(date +%s); \ + if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \ + sh -x "$@" >"$$log" 2>&1; then \ + end=$$(date +%s); \ + rm -f "$$log"; \ + printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \ + else \ + end=$$(date +%s); \ + printf '%-40s FAIL (%ss)\n log: %s\n' \ + "$@" "$$((end - start))" "$$log"; \ + fi diff --git a/regress/control-client-exit-stalled.sh b/regress/control-client-exit-stalled.sh index 431fd66d8..1574c821f 100644 --- a/regress/control-client-exit-stalled.sh +++ b/regress/control-client-exit-stalled.sh @@ -17,7 +17,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest$$" $TMUX kill-server 2>/dev/null DIR=$(mktemp -d) diff --git a/regress/control-client-exit.sh b/regress/control-client-exit.sh index ac08db5cc..d615dec48 100644 --- a/regress/control-client-exit.sh +++ b/regress/control-client-exit.sh @@ -19,7 +19,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest$$" $TMUX kill-server 2>/dev/null DIR=$(mktemp -d) diff --git a/regress/control-client-run-shell-exit.sh b/regress/control-client-run-shell-exit.sh index e8acc0419..e93dff20a 100644 --- a/regress/control-client-run-shell-exit.sh +++ b/regress/control-client-run-shell-exit.sh @@ -10,7 +10,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest$$" $TMUX kill-server 2>/dev/null OUT=$(mktemp) diff --git a/regress/control-client-wait-exit.sh b/regress/control-client-wait-exit.sh index 63739b284..26fe0ac76 100644 --- a/regress/control-client-wait-exit.sh +++ b/regress/control-client-wait-exit.sh @@ -10,7 +10,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest$$" $TMUX kill-server 2>/dev/null FIFO=$(mktemp -u) diff --git a/regress/display-panes.sh b/regress/display-panes.sh index 2d3faccd5..30c983c38 100644 --- a/regress/display-panes.sh +++ b/regress/display-panes.sh @@ -153,11 +153,13 @@ wait_format "$p0" '#{pane_in_mode}' '0' wait_option @picked "$p1" # Commands after display-panes run immediately while the mode remains. +# Record the mode in the same command queue so client startup cannot race +# the 500ms timeout. $TMUX set -g @after none || fail "set @after failed" -$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast || +$TMUX display-panes -Nd 500 -t "$p0" \; \ + set -gF -t "$p0" @after '#{pane_mode}' || fail "display-panes immediate command failed" -wait_option @after fast -wait_format "$p0" '#{pane_mode}' 'panes-mode' +wait_option @after 'panes-mode' wait_format "$p0" '#{pane_in_mode}' '0' # Existing zoom is restored on exit. diff --git a/regress/format-fuzzy.sh b/regress/format-fuzzy.sh index 00bc28bd4..e4cf3de15 100644 --- a/regress/format-fuzzy.sh +++ b/regress/format-fuzzy.sh @@ -7,6 +7,8 @@ TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null' 0 +trap 'exit 1' 1 2 15 # test_format $format $expected_result test_format() diff --git a/regress/format-modifiers.sh b/regress/format-modifiers.sh index 58c02af7c..475feb524 100644 --- a/regress/format-modifiers.sh +++ b/regress/format-modifiers.sh @@ -18,6 +18,24 @@ export TZ LANG LC_ALL [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1" +FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2" +HOLD1= +HOLD2= +CC1= +CC2= + +cleanup() +{ + for pid in $HOLD1 $HOLD2 $CC1 $CC2; do + kill "$pid" 2>/dev/null + wait "$pid" 2>/dev/null + done + $TMUX kill-server 2>/dev/null + rm -f "$FIFO1" "$FIFO2" +} +trap cleanup 0 +trap 'exit 1' 1 2 15 ESC=$(printf '\033') @@ -579,8 +597,6 @@ assert_alive "verbose loop expansion" # L loops over attached clients. Attach two control-mode clients, each held # open by a background process keeping a FIFO's write end open. -FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1" -FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2" rm -f "$FIFO1" "$FIFO2" mkfifo "$FIFO1" "$FIFO2" || exit 1 # Hold the write ends open so the control clients stay attached. @@ -605,9 +621,9 @@ test_format "#{L/nr:x}" "xx" test_format "#{L/r:x}" "xx" # Now detach one and confirm the count drops to one. kill $HOLD2 2>/dev/null +wait $HOLD2 2>/dev/null +HOLD2= sleep 1 test_format "#{L:x}" "x" -kill $HOLD1 $CC1 $CC2 2>/dev/null -rm -f "$FIFO1" "$FIFO2" exit 0 diff --git a/regress/format-strings.sh b/regress/format-strings.sh index 877e18fec..c06c340e3 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -7,6 +7,8 @@ TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null' 0 +trap 'exit 1' 1 2 15 # test_format $format $expected_result test_format() diff --git a/regress/hooks-lifecycle.sh b/regress/hooks-lifecycle.sh index 3ec748ff4..8c8a41363 100644 --- a/regress/hooks-lifecycle.sh +++ b/regress/hooks-lifecycle.sh @@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \ fail "set-hook session-closed failed" # The only pane of the only window of a session exits: pane-exited, then -# window-unlinked, then session-closed, each seeing the dead object in the +# session-closed, then window-unlinked, each seeing the dead object in the # hook formats. pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') || fail "new-session doomed failed" wait_for @log \ - "|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" + "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin" assert_unchanged @log \ - "|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" + "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin" # The dead pane, window and session cannot be used as targets but the # server survives. @@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \ 'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' || fail "restore pane-exited hook failed" -# kill-window on the last window: window-unlinked then session-closed but +# kill-window on the last window: session-closed then window-unlinked but # no pane-exited for the panes in the killed window. $TMUX set -g @log '' || fail "reset @log failed" $TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed" $TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed" $TMUX kill-window -t doomed2:0 || fail "kill-window failed" -wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' -assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' +wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2' +assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2' $TMUX has -t main || fail "server died after kill-window chain" # kill-session: session-closed fires first, then window-unlinked for its diff --git a/regress/if-shell-error.sh b/regress/if-shell-error.sh index 3b2dac753..968e0508c 100644 --- a/regress/if-shell-error.sh +++ b/regress/if-shell-error.sh @@ -12,7 +12,8 @@ $TMUX kill-server 2>/dev/null TMP=$(mktemp) OUT=$(mktemp) -trap "rm -f $TMP $OUT" 0 1 15 +trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0 +trap 'exit 1' 1 2 15 cat <$TMP if 'true' 'wibble wobble' diff --git a/regress/input-common.inc b/regress/input-common.inc index ac04dbc24..a7ce572f6 100644 --- a/regress/input-common.inc +++ b/regress/input-common.inc @@ -4,6 +4,9 @@ TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +# Keep panes alive until the next case or cleanup, without reading replies. +INPUT_HOLD='while :; do sleep 3600; done' + TMP=$(mktemp) EXP=$(mktemp) trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15 @@ -37,10 +40,10 @@ start_pane_hlimit() $TMUX kill-server 2>/dev/null sleep 0.1 - $TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1 + $TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1 $TMUX set-option -g history-limit "$hlimit" || exit 1 $TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \ - "printf '$seq'; sleep 2" || exit 1 + "printf '$seq'; $INPUT_HOLD" || exit 1 $TMUX kill-session -t test-setup sleep 0.3 } diff --git a/regress/input-keys.sh b/regress/input-keys.sh index cb771d19a..76dd5ea73 100644 --- a/regress/input-keys.sh +++ b/regress/input-keys.sh @@ -22,7 +22,19 @@ assert_key () { clear-history -t$W \; \ send-keys -t$W "$key" 'EOL' || exit 1 - actual_code=$($TMUX capturep -pt$W | \ + # cat echoes the keys back asynchronously, so wait for the EOL marker + # to reach the pane instead of capturing straight away. + i=0 + while [ $i -lt 50 ]; do + screen=$($TMUX capturep -pt$W) + case "$screen" in + *EOL*) break ;; + esac + i=$((i + 1)) + sleep 0.1 + done + + actual_code=$(printf '%s\n' "$screen" | \ head -1 | \ sed -e 's/EOL.*$//') diff --git a/regress/input-malformed.sh b/regress/input-malformed.sh index 55a7e38c0..855af9f6c 100644 --- a/regress/input-malformed.sh +++ b/regress/input-malformed.sh @@ -2,6 +2,38 @@ . ./input-common.inc +# Wait for the terminator and following text to reach the parser. +check_discard() +{ + name=$1 + printf 'OK\n' >"$EXP" + i=0 + while [ "$i" -lt 300 ]; do + capture_grid "$name" >"$TMP" + cmp -s "$TMP" "$EXP" && return 0 + sleep 0.05 + i=$((i + 1)) + done + fail "$name (timed out waiting for discard)" +} + +# The missing-terminator timer may expire before the string limit is reached +# on a slow build. Clear any payload printed after the timeout, then check +# that the string did not change the title and normal output has resumed. +test_discard() +{ + name=$1 + prefix=$2 + start_cmd "$name" 8 3 "$INPUT_HOLD" + $TMUX select-pane -T discard-test || exit 1 + $TMUX respawn-pane -k \ + "perl -e 'print qq{$prefix}, q{x} x 1100000, qq{\e\\\\\e[H\e[2JOK}'; $INPUT_HOLD" || exit 1 + check_discard "$name" + $TMUX display-message -p -t "$name:" '#{pane_title}' >"$TMP" || exit 1 + printf 'discard-test\n' >"$EXP" + cmp "$TMP" "$EXP" || fail "$name title" +} + start_cmd csi-param-discard 8 3 \ "perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2" check_capture csi-param-discard 'OK' @@ -10,13 +42,8 @@ start_cmd csi-interm-discard 8 3 \ "perl -e 'print qq{\e[ \030OK}'; sleep 2" check_capture csi-interm-discard 'OK' -start_cmd osc-discard 8 3 \ - "perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2" -check_capture osc-discard 'OK' - -start_cmd apc-discard 8 3 \ - "perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2" -check_capture apc-discard 'OK' +test_discard osc-discard '\e]2;' +test_discard apc-discard '\e_' start_pane unknown-csi 8 3 '\033[?9999zOK' check_capture unknown-csi 'OK' diff --git a/regress/input-scroll.sh b/regress/input-scroll.sh index 6ae48e96d..206c4db95 100644 --- a/regress/input-scroll.sh +++ b/regress/input-scroll.sh @@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null sleep 0.1 $TMUX new-session -d -x 5 -y 3 -s history \; \ set-option -g history-limit 3 \; \ - respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1 + respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; $INPUT_HOLD" || exit 1 sleep 0.3 $TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP" printf "%s\n" '01 diff --git a/regress/respawn-pane-control-lag.sh b/regress/respawn-pane-control-lag.sh index b71ce1946..8ca8146ed 100644 --- a/regress/respawn-pane-control-lag.sh +++ b/regress/respawn-pane-control-lag.sh @@ -15,7 +15,7 @@ PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) -TMUX="$TEST_TMUX -Ltest" +TMUX="$TEST_TMUX -Ltest$$" $TMUX kill-server 2>/dev/null DIR=$(mktemp -d) diff --git a/regress/run-shell-cwd.sh b/regress/run-shell-cwd.sh index abcc89ee6..0371a43df 100644 --- a/regress/run-shell-cwd.sh +++ b/regress/run-shell-cwd.sh @@ -22,14 +22,26 @@ check_directory() fi } -# Expand the current pane's directory in the same command queue as creation. +# The start path is available immediately, before the child is scheduled. +# Use it to test the format context in the same command queue as creation. $TMUX new-session -d -s test -c "$TMP/first" 'sleep 60' \; \ - run-shell -c '#{pane_current_path}' "pwd >'$TMP/out'" || exit 1 + run-shell -c '#{pane_start_path}' "pwd >'$TMP/out'" || exit 1 check_directory "$TMP/first" # An explicit target must supply the format context, including with a delay. pane=$($TMUX new-window -d -P -F '#{pane_id}' -t test \ -c "$TMP/second dir" 'sleep 60') || exit 1 +# The current path depends on the operating system finding the child process. +i=0 +while [ "$($TMUX display-message -p -t "$pane" '#{pane_current_path}')" != \ + "$TMP/second dir" ]; do + if [ "$i" -ge 100 ]; then + echo "Timed out waiting for pane current directory" + exit 1 + fi + sleep 0.05 + i=$((i + 1)) +done $TMUX run-shell -t "$pane" -d 0.1 -c '#{pane_current_path}' \ "pwd >'$TMP/out'" || exit 1 check_directory "$TMP/second dir" diff --git a/regress/screen-redraw-tiled.sh b/regress/screen-redraw-tiled.sh index de7c57aff..809b4d4c4 100644 --- a/regress/screen-redraw-tiled.sh +++ b/regress/screen-redraw-tiled.sh @@ -95,7 +95,8 @@ layout_tee_down() { # top/bottom, bottom split: a top tee $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null -$TMUX2 new -d -x40 -y14 "sh -c 'exec sleep 100'" || exit 1 +# Keep the anchor window alive until cleanup, even on slow test runs. +$TMUX2 new -d -x40 -y14 'exec cat' || exit 1 $TMUX2 set -g status off || exit 1 $TMUX2 set -g window-size manual || exit 1 $TMUX2 set -g pane-border-format " #{pane_index} " || exit 1 diff --git a/regress/tty-keys.sh b/regress/tty-keys.sh index 0a3ef318c..914c15c84 100644 --- a/regress/tty-keys.sh +++ b/regress/tty-keys.sh @@ -9,14 +9,37 @@ $TMUX kill-server 2>/dev/null TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" $TMUX2 kill-server 2>/dev/null -TMP=$(mktemp) -trap "rm -f $TMP" 0 1 15 +TMP=$(mktemp) || exit 1 +prompt_pid= + +cleanup () { + if [ -n "$prompt_pid" ]; then + kill "$prompt_pid" 2>/dev/null + wait "$prompt_pid" 2>/dev/null + fi + $TMUX kill-server 2>/dev/null + $TMUX2 kill-server 2>/dev/null + rm -f "$TMP" +} +trap cleanup 0 +trap 'exit 1' 1 2 15 + +fail () { + echo "[FAIL] $*" >&2 + exit 1 +} $TMUX2 -f/dev/null new -d || exit 1 $TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1 -sleep 1 +i=0 +while [ -z "$($TMUX2 list-clients -F '#{client_name}')" ]; do + [ "$i" -lt 100 ] || fail "inner client did not attach" + sleep 0.05 + i=$((i + 1)) +done exit_status=0 +assertion=0 format_string () { case $1 in @@ -34,12 +57,33 @@ assert_key () { expected_name=$2 format_string=$(format_string "$expected_name") - $TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" & - sleep 0.05 + # Use a different prompt each time so a stale redraw cannot look ready. + assertion=$((assertion + 1)) + prompt="tty-keys-$assertion:" + $TMUX2 command-prompt -k -p "$prompt" \ + 'display-message -pl '"$format_string" > "$TMP" & + prompt_pid=$! + i=0 + while ! $TMUX capture-pane -p | grep -Fq "$prompt"; do + kill -0 "$prompt_pid" 2>/dev/null || \ + fail "$keys -> $expected_name: prompt exited before becoming ready" + [ "$i" -lt 100 ] || \ + fail "$keys -> $expected_name: timed out waiting for prompt" + sleep 0.05 + i=$((i + 1)) + done - $TMUX send-keys $keys + $TMUX send-keys $keys || fail "could not send $keys" - wait + i=0 + while kill -0 "$prompt_pid" 2>/dev/null; do + [ "$i" -lt 100 ] || \ + fail "$keys -> $expected_name: timed out waiting for key" + sleep 0.05 + i=$((i + 1)) + done + wait "$prompt_pid" || fail "$keys -> $expected_name: prompt failed" + prompt_pid= keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]') actual_name=$(tr -d '[:space:]' < "$TMP") @@ -365,7 +409,4 @@ assert_key 'Escape [32;2u' 'S-Space' assert_key 'Escape [9;5u' 'C-Tab' assert_key 'Escape [1;5Z' 'C-S-Tab' -$TMUX kill-server 2>/dev/null -$TMUX2 kill-server 2>/dev/null - exit $exit_status