mirror of
https://github.com/tmux/tmux.git
synced 2026-07-08 18:39:30 +00:00
Add a few more tests.
This commit is contained in:
281
regress/format-render-contexts.sh
Normal file
281
regress/format-render-contexts.sh
Normal file
@@ -0,0 +1,281 @@
|
||||
#!/bin/sh
|
||||
# Exercise format/style rendering in live contexts. These paths use
|
||||
# format_draw rather than plain format expansion, so they have historically
|
||||
# found different crashes and runaway output.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LANG=C.UTF-8
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
LIMIT=20000
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_cmd()
|
||||
{
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 10 "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
bounded()
|
||||
{
|
||||
name="$1"
|
||||
text="$2"
|
||||
n=$(printf '%s' "$text" | wc -c | tr -d ' ')
|
||||
|
||||
[ "$n" -le "$LIMIT" ] || fail "$name produced $n bytes"
|
||||
}
|
||||
|
||||
tmux_run()
|
||||
{
|
||||
name="$1"
|
||||
shift
|
||||
|
||||
out=$(run_cmd $TMUX "$@" 2>&1)
|
||||
rc=$?
|
||||
bounded "$name" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "$name failed ($rc): $out"
|
||||
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
capture()
|
||||
{
|
||||
out=$(run_cmd $TMUX2 capture-pane -p -t out:0 2>/dev/null)
|
||||
rc=$?
|
||||
bounded "capture-pane" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "capture-pane failed"
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
capture_esc()
|
||||
{
|
||||
out=$(run_cmd $TMUX2 capture-pane -Cep -t out:0 2>/dev/null)
|
||||
rc=$?
|
||||
bounded "capture-pane -e" "$out"
|
||||
[ "$rc" -eq 0 ] || fail "capture-pane -e failed"
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
out=$(tmux_run "server alive ($1)" display-message -p alive)
|
||||
[ "$out" = "alive" ] || fail "server not alive after $1"
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
marker="$1"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if capture | grep -q "$marker"; then
|
||||
sleep 0.1
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for $marker"
|
||||
}
|
||||
|
||||
render_status_options()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label status-left" \
|
||||
set-option -g status-left "SL$label: $fmt" >/dev/null
|
||||
tmux_run "$label status-right" \
|
||||
set-option -g status-right "SR$label: $fmt" >/dev/null
|
||||
tmux_run "$label status-format left/right" \
|
||||
set-option -g status-format[0] \
|
||||
'#{E:status-left}#[align=right]#{E:status-right}' >/dev/null
|
||||
wait_for "SL$label"
|
||||
assert_alive "$label status-left/right"
|
||||
}
|
||||
|
||||
render_status_format()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label clear status-left" set-option -g status-left '' >/dev/null
|
||||
tmux_run "$label clear status-right" set-option -g status-right '' >/dev/null
|
||||
tmux_run "$label status-format" \
|
||||
set-option -g status-format[0] "SF$label: $fmt" >/dev/null
|
||||
wait_for "SF$label"
|
||||
assert_alive "$label status-format"
|
||||
}
|
||||
|
||||
render_message()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label display-message" \
|
||||
display-message -t fmt:0 -d 1000 "DM$label: $fmt" >/dev/null
|
||||
wait_for "DM$label"
|
||||
assert_alive "$label display-message"
|
||||
}
|
||||
|
||||
render_choose_tree()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label choose-tree" \
|
||||
choose-tree -t fmt:0 -F "CT$label: $fmt" >/dev/null
|
||||
wait_for "CT$label"
|
||||
tmux_run "$label quit choose-tree" send-keys -t fmt:0 q >/dev/null
|
||||
assert_alive "$label choose-tree"
|
||||
}
|
||||
|
||||
render_customize()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label customize-mode" \
|
||||
customize-mode -t fmt:0 -F "CM$label: $fmt" >/dev/null
|
||||
sleep 0.5
|
||||
out=$(capture)
|
||||
bounded "$label customize-mode capture" "$out"
|
||||
tmux_run "$label quit customize-mode" send-keys -t fmt:0 q >/dev/null
|
||||
assert_alive "$label customize-mode"
|
||||
}
|
||||
|
||||
render_list_output()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
tmux_run "$label bind-key" \
|
||||
bind-key -T root F12 display-message "LK$label: $fmt" >/dev/null
|
||||
out=$(tmux_run "$label list-keys" list-keys -T root F12)
|
||||
bounded "$label list-keys output" "$out"
|
||||
|
||||
tmux_run "$label option value" \
|
||||
set-option -g status-left "LO$label: $fmt" >/dev/null
|
||||
out=$(tmux_run "$label list-options" show-options -g status-left)
|
||||
bounded "$label list-options output" "$out"
|
||||
|
||||
assert_alive "$label list-keys/list-options"
|
||||
}
|
||||
|
||||
check_sgr_sanity()
|
||||
{
|
||||
tmux_run "sgr status style" \
|
||||
set-option -g status-style fg=default,bg=default >/dev/null
|
||||
tmux_run "sgr status-format" \
|
||||
set-option -g status-format[0] \
|
||||
'#[fg=colour1,bg=default]SGR-STATUS#[default]' >/dev/null
|
||||
wait_for "SGR-STATUS"
|
||||
out=$(capture_esc | tail -n 1)
|
||||
bounded "sgr status capture" "$out"
|
||||
case "$out" in
|
||||
*SGR-STATUS*) ;;
|
||||
*) fail "status SGR capture lost text: $out" ;;
|
||||
esac
|
||||
case "$out" in
|
||||
*"\\033["*) ;;
|
||||
*) fail "status SGR capture has no escape reset/style data: $out" ;;
|
||||
esac
|
||||
|
||||
tmux_run "sgr message style" \
|
||||
set-option -g message-style fg=colour2,bg=default >/dev/null
|
||||
tmux_run "sgr display-message" \
|
||||
display-message -t fmt:0 -d 1000 \
|
||||
'#[bg=default]SGR-MESSAGE#[default]' >/dev/null
|
||||
wait_for "SGR-MESSAGE"
|
||||
out=$(capture_esc | tail -n 1)
|
||||
bounded "sgr message capture" "$out"
|
||||
case "$out" in
|
||||
*SGR-MESSAGE*) ;;
|
||||
*) fail "message SGR capture lost text: $out" ;;
|
||||
esac
|
||||
case "$out" in
|
||||
*"\\033["*) ;;
|
||||
*) fail "message SGR capture has no escape reset/style data: $out" ;;
|
||||
esac
|
||||
|
||||
assert_alive "SGR sanity"
|
||||
}
|
||||
|
||||
run_corpus()
|
||||
{
|
||||
label="$1"
|
||||
fmt="$2"
|
||||
|
||||
render_status_options "$label" "$fmt"
|
||||
render_status_format "$label" "$fmt"
|
||||
render_message "$label" "$fmt"
|
||||
render_choose_tree "$label" "$fmt"
|
||||
render_customize "$label" "$fmt"
|
||||
render_list_output "$label" "$fmt"
|
||||
}
|
||||
|
||||
trap cleanup 0 1 15
|
||||
cleanup
|
||||
|
||||
tmux_run "new inner session" \
|
||||
new-session -d -s fmt -x 100 -y 30 "exec sleep 1000" >/dev/null
|
||||
tmux_run "new inner window" \
|
||||
new-window -t fmt -n second "exec sleep 1000" >/dev/null
|
||||
tmux_run "select visible window" select-window -t fmt:0 >/dev/null
|
||||
tmux_run "set status interval" set-option -g status-interval 1 >/dev/null
|
||||
tmux_run "set base message style" \
|
||||
set-option -g message-style fg=default,bg=default >/dev/null
|
||||
tmux_run "set nested option" \
|
||||
set-option -g @nested 'NESTED-#{session_name}-#{window_index}' >/dev/null
|
||||
|
||||
run_cmd $TMUX2 new-session -d -s out -x 100 -y 30 "$TMUX attach -t fmt" \
|
||||
>/dev/null 2>&1 || fail "failed to start outer client"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
c=$(tmux_run "wait clients" list-clients -F x | grep -c x)
|
||||
[ "$c" -eq 1 ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "inner client did not attach"
|
||||
|
||||
run_corpus A \
|
||||
'#[bold,dim,underscore,blink,reverse,italics,strikethrough,double-underscore,curly-underscore,dotted-underscore,dashed-underscore,overline,fg=colour196,bg=colour22,us=colour45]LONGSTYLE#[default]plain'
|
||||
run_corpus B \
|
||||
'wide: Ελληνικά 中文 😀 #{=12:中文😀abc}'
|
||||
run_corpus C \
|
||||
'#[fg=colour2,bg=colour4]base#[push-default]#[fg=colour1,bg=default]push#[pop-default]pop#[default]'
|
||||
run_corpus D \
|
||||
'#[list=on]list#[list=focus]focus#[nolist] #[align=centre]centre#[noalign] #[range=left]L#[range=right]R#[norange]'
|
||||
run_corpus E \
|
||||
'#{E:@nested}:#{T:%%H:%%M}:#{?#{==:#{session_name},fmt},yes,no}:#{?0,bad,}'
|
||||
run_corpus F \
|
||||
'job:#(printf job-ok)'
|
||||
run_corpus G \
|
||||
'empty:#{E:}:#{T:}:#{definitely_not_a_variable}:#{?0,no,}'
|
||||
|
||||
check_sgr_sanity
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
281
regress/input-reflow-stress.sh
Normal file
281
regress/input-reflow-stress.sh
Normal file
@@ -0,0 +1,281 @@
|
||||
#!/bin/sh
|
||||
|
||||
WIDTHS="80 40 20 10 7 5 4 3 2 1 2 3 4 5 7 10 20 40 80"
|
||||
HISTORY_LIMIT=220
|
||||
HISTORY_BOUND=12000
|
||||
JOINED_BOUND=500000
|
||||
RAW_BOUND=5000000
|
||||
|
||||
record_fail()
|
||||
{
|
||||
echo "FAIL: $1"
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
make_payload()
|
||||
{
|
||||
n=0
|
||||
i=0
|
||||
while [ "$i" -lt 16 ]; do
|
||||
printf 'L%04d|ascii|abcdefghijklmnopqrstuvwxyz\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|wrap|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|wide|AあB界C\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|wide-edge|abcdあZ\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|combining|é é é\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|orphan-combining|́A\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|variation|✔️ ⚠️ 🏝️\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|emoji|🙂🙂🙂\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|flag|🇬🇧 🇺🇸 🇯🇵\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|mixed|Aあé✔️B\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|style|plain \033[31mred\033[0m plain\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|hyperlink|\033]8;;https://example.com/%04d\033\\LINK\033]8;;\033\\\n' "$n" "$n"; n=$((n + 1))
|
||||
printf 'L%04d|tabs|a\tb\tc\n' "$n"; n=$((n + 1))
|
||||
printf 'L%04d|backspace|abc\bX\n' "$n"; n=$((n + 1))
|
||||
printf 'carriage-return-%04d|abcdef\rL%04d|carriage-return|XY\n' "$n" "$n"; n=$((n + 1))
|
||||
i=$((i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
make_alternate_payload()
|
||||
{
|
||||
printf '\033[?1049h'
|
||||
printf 'ALT000|wide|AあB界C\n'
|
||||
printf 'ALT001|combining|é é é\n'
|
||||
printf 'ALT002|style|plain \033[32mgreen\033[0m plain\n'
|
||||
printf 'ALT003|emoji|🙂🙂🙂\n'
|
||||
}
|
||||
|
||||
load_and_paste()
|
||||
{
|
||||
buffer=$1
|
||||
shift
|
||||
|
||||
"$@" | $TMUX load-buffer -b "$buffer" - || exit 1
|
||||
$TMUX paste-buffer -d -b "$buffer" -t stress:0.0 || exit 1
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
alive=$($TMUX display-message -p -t stress: alive 2>&1) || {
|
||||
record_fail "server died after $1"
|
||||
return
|
||||
}
|
||||
[ "$alive" = alive ] || record_fail "server died after $1"
|
||||
}
|
||||
|
||||
capture_joined()
|
||||
{
|
||||
$TMUX capture-pane -pNJ -t stress: -S - -E - >"$TMP"
|
||||
}
|
||||
|
||||
assert_joined_sane()
|
||||
{
|
||||
label=$1
|
||||
check_sentinels=${2:-yes}
|
||||
|
||||
capture_joined || {
|
||||
record_fail "joined capture failed after $label"
|
||||
return
|
||||
}
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$JOINED_BOUND" ]; then
|
||||
record_fail "joined capture too large after $label: $bytes"
|
||||
fi
|
||||
|
||||
if grep -q "$(printf '\357\277\275')" "$TMP"; then
|
||||
record_fail "replacement character in joined capture after $label"
|
||||
fi
|
||||
|
||||
awk -v label="$label" '
|
||||
{
|
||||
line = $0
|
||||
seen_on_line = 0
|
||||
while (match(line, /L[0-9][0-9][0-9][0-9]\|/)) {
|
||||
id = substr(line, RSTART + 1, 4) + 0
|
||||
seen_on_line++
|
||||
seen[id]++
|
||||
if (id < last) {
|
||||
printf("IDs out of order after %s: L%04d after L%04d\n",
|
||||
label, id, last)
|
||||
exit 1
|
||||
}
|
||||
if (seen[id] > 1) {
|
||||
printf("duplicate ID after %s: L%04d\n", label, id)
|
||||
exit 1
|
||||
}
|
||||
last = id
|
||||
found = 1
|
||||
line = substr(line, RSTART + RLENGTH)
|
||||
}
|
||||
if (seen_on_line > 1) {
|
||||
printf("fused IDs after %s: %s\n", label, $0)
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
printf("no line IDs after %s\n", label)
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$TMP" || {
|
||||
cp "$TMP" "$EXP"
|
||||
record_fail "joined structure failed after $label"
|
||||
}
|
||||
|
||||
if [ "$check_sentinels" = yes ]; then
|
||||
for expected in \
|
||||
'L0234|mixed|' \
|
||||
'L0236|hyperlink|' \
|
||||
'L0239|carriage-return|XY'; do
|
||||
if ! grep -Fq "$expected" "$TMP"; then
|
||||
printf '%s\n' "$expected" >"$EXP"
|
||||
record_fail "missing sentinel after $label"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
assert_raw_sane()
|
||||
{
|
||||
label=$1
|
||||
width=$2
|
||||
|
||||
$TMUX capture-pane -pR -t stress: >"$TMP" || {
|
||||
record_fail "raw capture failed after $label"
|
||||
return
|
||||
}
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt "$RAW_BOUND" ]; then
|
||||
record_fail "raw capture too large after $label: $bytes"
|
||||
fi
|
||||
|
||||
awk -v label="$label" -v width="$width" '
|
||||
/^C / {
|
||||
cell = $0
|
||||
sub(/^C /, "", cell)
|
||||
split(cell, fields, " ")
|
||||
split(fields[1], position, ",")
|
||||
row = position[1] + 0
|
||||
col = position[2] + 0
|
||||
if (col >= width) {
|
||||
printf("cell column outside pane after %s: %s\n", label, $0)
|
||||
exit 1
|
||||
}
|
||||
|
||||
data = $0
|
||||
if (data !~ /data=\(/)
|
||||
next
|
||||
sub(/^.*data=\(/, "", data)
|
||||
split(data, data_fields, ",")
|
||||
cell_width = data_fields[1] + 0
|
||||
padding = ($0 ~ /flags=PADDING/)
|
||||
|
||||
if (!padding && cell_width == 0) {
|
||||
printf("zero-width non-padding cell after %s: %s\n", label, $0)
|
||||
exit 1
|
||||
}
|
||||
if (padding && !(last_row == row && last_width > 1 && !last_padding)) {
|
||||
printf("padding without preceding wide cell after %s: %s\n", label, $0)
|
||||
exit 1
|
||||
}
|
||||
|
||||
last_row = row
|
||||
last_width = cell_width
|
||||
last_padding = padding
|
||||
}
|
||||
' "$TMP" || {
|
||||
cp "$TMP" "$EXP"
|
||||
record_fail "raw structure failed after $label"
|
||||
}
|
||||
}
|
||||
|
||||
assert_history_sane()
|
||||
{
|
||||
label=$1
|
||||
|
||||
size=$($TMUX display-message -p -t stress: '#{history_size}') || {
|
||||
record_fail "history-size failed after $label"
|
||||
return
|
||||
}
|
||||
case "$size" in
|
||||
''|*[!0-9]*)
|
||||
record_fail "bad history size after $label: $size"
|
||||
;;
|
||||
*)
|
||||
if [ "$size" -gt "$HISTORY_BOUND" ]; then
|
||||
record_fail "history too large after $label: $size"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_copy_mode_sane()
|
||||
{
|
||||
label=$1
|
||||
|
||||
$TMUX capture-pane -pM -t stress: >"$TMP" || {
|
||||
record_fail "copy-mode capture failed after $label"
|
||||
return
|
||||
}
|
||||
|
||||
bytes=$(wc -c <"$TMP")
|
||||
if [ "$bytes" -gt 20000 ]; then
|
||||
record_fail "copy-mode capture too large after $label"
|
||||
fi
|
||||
if grep -q "$(printf '\357\277\275')" "$TMP"; then
|
||||
record_fail "replacement character in copy-mode after $label"
|
||||
fi
|
||||
if ! grep -Eq 'L[0-9][0-9][0-9][0-9]\|' "$TMP"; then
|
||||
record_fail "no line ID in copy-mode after $label"
|
||||
fi
|
||||
}
|
||||
|
||||
run_resize_checks()
|
||||
{
|
||||
for width in $WIDTHS; do
|
||||
$TMUX resize-window -t stress: -x "$width" -y 8 || exit 1
|
||||
sleep 0.1
|
||||
assert_alive "resize to $width"
|
||||
assert_joined_sane "resize to $width"
|
||||
assert_raw_sane "resize to $width" "$width"
|
||||
assert_history_sane "resize to $width"
|
||||
done
|
||||
}
|
||||
|
||||
$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 set-option -g history-limit "$HISTORY_LIMIT" || exit 1
|
||||
$TMUX new-session -d -x 80 -y 8 -s stress "stty -echo; cat" || exit 1
|
||||
$TMUX kill-session -t test-setup
|
||||
sleep 0.3
|
||||
|
||||
load_and_paste stress-data make_payload
|
||||
run_resize_checks
|
||||
|
||||
$TMUX copy-mode -H -t stress: || exit 1
|
||||
for cmd in history-top page-down halfpage-down halfpage-up page-up history-bottom; do
|
||||
$TMUX send-keys -t stress: -X "$cmd" || exit 1
|
||||
sleep 0.1
|
||||
assert_copy_mode_sane "$cmd"
|
||||
done
|
||||
$TMUX send-keys -t stress: -X cancel || exit 1
|
||||
|
||||
load_and_paste stress-alt make_alternate_payload
|
||||
for width in 10 4 1 4 10 80; do
|
||||
$TMUX resize-window -t stress: -x "$width" -y 8 || exit 1
|
||||
sleep 0.1
|
||||
assert_alive "alternate resize to $width"
|
||||
assert_raw_sane "alternate resize to $width" "$width"
|
||||
done
|
||||
printf '\033[?1049l' | $TMUX load-buffer -b stress-alt-exit - || exit 1
|
||||
$TMUX paste-buffer -d -b stress-alt-exit -t stress:0.0 || exit 1
|
||||
sleep 0.3
|
||||
assert_joined_sane "alternate screen exit" no
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
216
regress/lifecycle-deferred.sh
Normal file
216
regress/lifecycle-deferred.sh
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Llifecycle-deferred -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Llifecycle-deferred-outer -f/dev/null"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
IN="$TMPDIR/in"
|
||||
OUT="$TMPDIR/out"
|
||||
CONTROL_PID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&- 2>/dev/null
|
||||
[ -n "$CONTROL_PID" ] && kill "$CONTROL_PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
[ -s "$OUT" ] && sed -n '1,120p' "$OUT" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_tmux()
|
||||
{
|
||||
out=
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
out=$(timeout 10 $TMUX "$@" 2>&1)
|
||||
else
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
fi
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "tmux $* failed ($rc): $out"
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
send_control()
|
||||
{
|
||||
printf '%s\n' "$1" >&3 || fail "failed to write control command: $1"
|
||||
}
|
||||
|
||||
wait_clients()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
have=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$have" -eq "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_format()
|
||||
{
|
||||
target=$1
|
||||
format=$2
|
||||
want=$3
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
have=$($TMUX display-message -p -t "$target" "$format" 2>/dev/null)
|
||||
[ "$have" = "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
run_tmux has-session -t life >/dev/null
|
||||
fields=$(run_tmux display-message -p -t life \
|
||||
'#{session_name}:#{window_id}:#{pane_id}:#{session_windows}:#{window_panes}')
|
||||
case "$fields" in
|
||||
life:@*:%*:*) ;;
|
||||
*) fail "bad current fields after $1: $fields" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_control_output()
|
||||
{
|
||||
sleep 1
|
||||
|
||||
if grep -E '(^%error |server exited|lost server|\(null\)|no current)' \
|
||||
"$OUT" >/dev/null 2>&1; then
|
||||
fail "control client reported an error or invalid object"
|
||||
fi
|
||||
|
||||
awk '
|
||||
$1 == "%session-window-changed" {
|
||||
if (NF != 3 || $2 !~ /^\$[0-9]+$/ || $3 !~ /^@[0-9]+$/)
|
||||
bad = 1
|
||||
}
|
||||
$1 == "%subscription-changed" {
|
||||
colon = 0
|
||||
for (i = 2; i <= NF; i++)
|
||||
if ($i == ":")
|
||||
colon = 1
|
||||
if (NF < 7 || !colon)
|
||||
bad = 1
|
||||
}
|
||||
END { exit bad }
|
||||
' "$OUT" || fail "control client received a malformed notification"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
run_tmux new-session -d -s life -n prompt -x 80 -y 24 'sleep 1000' \
|
||||
>/dev/null
|
||||
run_tmux set-option -g detach-on-destroy off >/dev/null
|
||||
run_tmux new-window -t life -n tree 'sleep 1000' >/dev/null
|
||||
run_tmux new-window -t life -n work 'sleep 1000' >/dev/null
|
||||
run_tmux select-window -t life:prompt >/dev/null
|
||||
|
||||
run_tmux set-hook -g after-new-window \
|
||||
'display-message -p "hook-new #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g after-split-window \
|
||||
'display-message -p "hook-split #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g after-kill-pane \
|
||||
'display-message -p "hook-kill #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g pane-exited \
|
||||
'display-message -p "hook-exit #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g window-layout-changed \
|
||||
'display-message -p "hook-layout #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux set-hook -g session-window-changed \
|
||||
'display-message -p "hook-current #{session_name}:#{window_id}:#{pane_id}"' \
|
||||
>/dev/null
|
||||
run_tmux bind-key -n M-p command-prompt -P -p '(life)' \
|
||||
"set -g @lifecycle-prompt '%% #{session_name}:#{window_id}:#{pane_id}'" \
|
||||
>/dev/null
|
||||
|
||||
$TMUX2 new-session -d -s outer -n prompt -x 80 -y 24 "$TMUX attach -t life" \
|
||||
|| fail "failed to start first attached client"
|
||||
$TMUX2 new-window -t outer -n tree "$TMUX attach -t life" \
|
||||
|| fail "failed to start second attached client"
|
||||
wait_clients 2 || fail "normal clients did not attach"
|
||||
|
||||
$TMUX2 send-keys -t outer:prompt M-p || fail "failed to open command prompt"
|
||||
sleep 1
|
||||
run_tmux choose-tree -t life:tree.0 \
|
||||
-F 'tree #{session_name}:#{window_id}:#{pane_id}' >/dev/null
|
||||
wait_format life:tree.0 '#{pane_mode}' tree-mode || \
|
||||
fail "choose-tree did not enter tree-mode"
|
||||
|
||||
mkfifo "$IN" || fail "failed to create control fifo"
|
||||
(cat "$IN" | $TMUX -C attach -t life >"$OUT" 2>&1) &
|
||||
CONTROL_PID=$!
|
||||
exec 3>"$IN"
|
||||
wait_clients 3 || fail "control client did not attach"
|
||||
|
||||
CONTROL_CLIENT=$($TMUX list-clients -F '#{client_name} #{client_control_mode}' |
|
||||
awk '$2 == 1 { print $1; exit }')
|
||||
[ -n "$CONTROL_CLIENT" ] || fail "missing control client"
|
||||
|
||||
send_control "refresh-client -B 'all:%*:#{session_name}:#{window_id}:#{pane_id}:#{session_windows}:#{window_panes}'"
|
||||
send_control "refresh-client -B 'windows:@*:#{session_name}:#{window_id}:#{window_index}:#{window_panes}'"
|
||||
sleep 1
|
||||
|
||||
run_tmux kill-pane -t life:prompt.0 >/dev/null
|
||||
run_tmux kill-window -t life:tree >/dev/null
|
||||
assert_alive "killing prompt and tree panes"
|
||||
|
||||
i=1
|
||||
while [ "$i" -le 20 ]; do
|
||||
# Keep this sequence fixed: failures should reproduce on the same pass.
|
||||
s=ld$i
|
||||
ctl=ctl$i
|
||||
idx=$((50 + i))
|
||||
|
||||
run_tmux new-session -d -s "$s" -n base 'sleep 1000' >/dev/null
|
||||
run_tmux split-window -t "$s:base" 'sleep 1000' >/dev/null
|
||||
run_tmux respawn-pane -k -t "$s:base.1" 'sleep 1000' >/dev/null
|
||||
run_tmux kill-pane -t "$s:base.1" >/dev/null
|
||||
|
||||
run_tmux new-window -t "$s" -n second 'sleep 1000' >/dev/null
|
||||
run_tmux link-window -s "$s:second" -t "life:$idx" >/dev/null
|
||||
run_tmux unlink-window -t "life:$idx" >/dev/null
|
||||
|
||||
run_tmux new-window -t "$s" -n single 'sleep 1000' >/dev/null
|
||||
run_tmux kill-pane -t "$s:single.0" >/dev/null
|
||||
run_tmux kill-window -t "$s:base" >/dev/null
|
||||
|
||||
run_tmux new-session -d -s "$ctl" -n ctl 'sleep 1000' >/dev/null
|
||||
run_tmux switch-client -c "$CONTROL_CLIENT" -t "$ctl" >/dev/null
|
||||
run_tmux kill-session -t "$ctl" >/dev/null
|
||||
|
||||
run_tmux kill-session -t "$s" >/dev/null
|
||||
assert_alive "iteration $i"
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
check_control_output
|
||||
cleanup
|
||||
exit 0
|
||||
297
regress/mode-mutation.sh
Normal file
297
regress/mode-mutation.sh
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise modes while their backing objects are changed from outside the
|
||||
# client displaying the mode. This catches stale selection indexes and pointers
|
||||
# after a mode list is shrunk or rebuilt.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
$TMUX display-message -p 'alive' >/dev/null 2>&1 || \
|
||||
fail "$1: server exited"
|
||||
}
|
||||
|
||||
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_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
capture | grep -q "$1" && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
t=$1
|
||||
want=$2
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($TMUX display-message -p -t "$t" '#{pane_in_mode}' \
|
||||
2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane $t mode state is $got, expected $want"
|
||||
}
|
||||
|
||||
repeat_key()
|
||||
{
|
||||
t=$1
|
||||
key=$2
|
||||
count=$3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt "$count" ]; do
|
||||
$TMUX send-keys -t "$t" "$key" || \
|
||||
fail "failed to send $key to $t"
|
||||
i=$((i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
start_client()
|
||||
{
|
||||
s=$1
|
||||
cmd=${2:-cat}
|
||||
|
||||
cleanup
|
||||
$TMUX new-session -d -s "$s" -n main -x 80 -y 24 "$cmd" || \
|
||||
fail "$s: new-session failed"
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t $s" || \
|
||||
fail "$s: outer client failed"
|
||||
wait_clients 1
|
||||
}
|
||||
|
||||
new_outer_client()
|
||||
{
|
||||
s=$1
|
||||
|
||||
$TMUX2 new-window -d -t out: "$TMUX attach -t $s" || \
|
||||
fail "$s: outer client failed"
|
||||
}
|
||||
|
||||
client_for_session()
|
||||
{
|
||||
$TMUX list-clients -F '#{client_name} #{client_session}' |
|
||||
awk -v s="$1" '$2 == s { print $1; exit }'
|
||||
}
|
||||
|
||||
test_choose_tree()
|
||||
{
|
||||
start_client tree-a
|
||||
$TMUX new-session -d -s tree-b -n one 'cat' || fail "tree-b failed"
|
||||
$TMUX new-window -d -t tree-b -n two 'cat' || fail "tree-b:1 failed"
|
||||
$TMUX new-session -d -s tree-c -n one 'cat' || fail "tree-c failed"
|
||||
$TMUX new-session -d -s tree-d -n one 'cat' || fail "tree-d failed"
|
||||
$TMUX split-window -d -t tree-a:0 'cat' || fail "tree split failed"
|
||||
|
||||
$TMUX choose-tree -t tree-a:0 -O index -F 'MT #{session_name}:#{window_index}.#{pane_index}' || \
|
||||
fail "choose-tree failed"
|
||||
wait_for 'MT '
|
||||
repeat_key tree-a:0 j 40
|
||||
|
||||
$TMUX kill-session -t tree-d || fail "tree kill-session failed"
|
||||
$TMUX kill-session -t tree-c || fail "tree kill-session failed"
|
||||
$TMUX rename-session -t tree-b tree-renamed || fail "tree rename failed"
|
||||
$TMUX rename-window -t tree-renamed:0 renamed || fail "tree rename-window failed"
|
||||
$TMUX kill-window -t tree-renamed:1 || fail "tree kill-window failed"
|
||||
side=$($TMUX split-window -d -P -F '#{pane_id}' -t tree-a:0 'cat') || \
|
||||
fail "tree side split failed"
|
||||
$TMUX break-pane -d -s "$side" || fail "tree break-pane failed"
|
||||
$TMUX join-pane -d -s "$side" -t tree-a:0.0 || \
|
||||
fail "tree join-pane failed"
|
||||
i=0
|
||||
while [ "$i" -lt 12 ]; do
|
||||
$TMUX new-window -d -t tree-a -n "new$i" 'cat' || \
|
||||
fail "tree new-window failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
assert_alive "choose-tree mutation"
|
||||
$TMUX send-keys -t tree-a:0 k j l h Enter || \
|
||||
fail "choose-tree keys failed"
|
||||
wait_mode tree-a:0 0
|
||||
assert_alive "choose-tree exit"
|
||||
}
|
||||
|
||||
test_choose_buffer()
|
||||
{
|
||||
start_client buffer-a
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-buffer -b "mbuf$i" "buffer mutation $i" || \
|
||||
fail "set-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX choose-buffer -t buffer-a:0 -F 'MB #{buffer_name}' || \
|
||||
fail "choose-buffer failed"
|
||||
wait_for 'MB '
|
||||
repeat_key buffer-a:0 j 40
|
||||
|
||||
i=8
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX delete-buffer -b "mbuf$i" || fail "delete-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=30
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX set-buffer -b "mbuf$i" "new buffer mutation $i" || \
|
||||
fail "new set-buffer failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
assert_alive "choose-buffer mutation"
|
||||
$TMUX send-keys -t buffer-a:0 k j Enter || \
|
||||
fail "choose-buffer keys failed"
|
||||
wait_mode buffer-a:0 0
|
||||
assert_alive "choose-buffer exit"
|
||||
}
|
||||
|
||||
test_choose_client()
|
||||
{
|
||||
start_client client-a
|
||||
$TMUX new-session -d -s client-b -n main 'cat' || fail "client-b failed"
|
||||
$TMUX new-session -d -s client-c -n main 'cat' || fail "client-c failed"
|
||||
new_outer_client client-b
|
||||
new_outer_client client-c
|
||||
wait_clients 3
|
||||
|
||||
$TMUX choose-client -t client-a:0 -F 'MC #{client_session}' || \
|
||||
fail "choose-client failed"
|
||||
wait_for 'MC '
|
||||
repeat_key client-a:0 j 20
|
||||
|
||||
c=$(client_for_session client-c)
|
||||
[ -n "$c" ] || fail "client-c client not found"
|
||||
$TMUX detach-client -t "$c" || fail "detach client-c failed"
|
||||
c=$(client_for_session client-b)
|
||||
[ -n "$c" ] || fail "client-b client not found"
|
||||
$TMUX detach-client -t "$c" || fail "detach client-b failed"
|
||||
|
||||
$TMUX new-session -d -s client-d -n main 'cat' || fail "client-d failed"
|
||||
new_outer_client client-d
|
||||
wait_clients 2
|
||||
|
||||
assert_alive "choose-client mutation"
|
||||
$TMUX send-keys -t client-a:0 k j Enter || \
|
||||
fail "choose-client keys failed"
|
||||
wait_mode client-a:0 0
|
||||
assert_alive "choose-client exit"
|
||||
}
|
||||
|
||||
test_customize_mode()
|
||||
{
|
||||
start_client option-a
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-option -g "@mode_mut_$i" "$i" || \
|
||||
fail "set option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX customize-mode -t option-a:0 -F 'MO #{option_name}=#{option_value}' || \
|
||||
fail "customize-mode failed"
|
||||
wait_mode option-a:0 1
|
||||
repeat_key option-a:0 j 80
|
||||
|
||||
i=10
|
||||
while [ "$i" -lt 30 ]; do
|
||||
$TMUX set-option -gu "@mode_mut_$i" || fail "unset option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=30
|
||||
while [ "$i" -lt 55 ]; do
|
||||
$TMUX set-option -g "@mode_mut_$i" "$i" || \
|
||||
fail "new option failed"
|
||||
i=$((i + 1))
|
||||
done
|
||||
$TMUX set-option -g status-left 'mutated' || fail "status-left failed"
|
||||
$TMUX rename-session -t option-a option-renamed || fail "option rename failed"
|
||||
|
||||
assert_alive "customize-mode mutation"
|
||||
$TMUX send-keys -t option-renamed:0 k j C-d C-u q || \
|
||||
fail "customize-mode keys failed"
|
||||
wait_mode option-renamed:0 0
|
||||
assert_alive "customize-mode exit"
|
||||
}
|
||||
|
||||
test_copy_mode()
|
||||
{
|
||||
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
|
||||
$TMUX set-window-option -g mode-keys vi || fail "mode-keys failed"
|
||||
$TMUX split-window -d -t copy-a:0 'cat' || fail "copy split failed"
|
||||
|
||||
$TMUX copy-mode -t copy-a:0 || fail "copy-mode failed"
|
||||
wait_mode copy-a:0 1
|
||||
repeat_key copy-a:0 k 20
|
||||
|
||||
$TMUX rename-window -t copy-a:0 renamed || fail "copy rename-window failed"
|
||||
side=$($TMUX split-window -d -P -F '#{pane_id}' -t copy-a:renamed 'cat') || \
|
||||
fail "copy side split failed"
|
||||
$TMUX break-pane -d -s "$side" || fail "copy break-pane failed"
|
||||
$TMUX join-pane -d -s "$side" -t copy-a:renamed.0 || \
|
||||
fail "copy join-pane failed"
|
||||
$TMUX kill-pane -t copy-a:renamed.1 || fail "copy kill-pane failed"
|
||||
$TMUX new-window -d -t copy-a -n extra 'cat' || fail "copy new-window failed"
|
||||
$TMUX kill-window -t copy-a:extra || fail "copy kill-window failed"
|
||||
$TMUX rename-session -t copy-a copy-renamed || fail "copy rename failed"
|
||||
|
||||
assert_alive "copy-mode mutation"
|
||||
$TMUX send-keys -t copy-renamed:renamed.0 j k C-d C-u q || \
|
||||
fail "copy-mode keys failed"
|
||||
wait_mode copy-renamed:renamed.0 0
|
||||
assert_alive "copy-mode exit"
|
||||
}
|
||||
|
||||
cleanup
|
||||
test_choose_tree
|
||||
test_choose_buffer
|
||||
test_choose_client
|
||||
test_customize_mode
|
||||
test_copy_mode
|
||||
cleanup
|
||||
exit 0
|
||||
Reference in New Issue
Block a user