Update some tests.

This commit is contained in:
Nicholas Marriott committed 2026-10-23 13:28:39 +01:00
1 parent 48b95b56ea
commit 98bac274e0
10 files changed
+166 -58

No files matched your search

+1
View File
@@ -6,6 +6,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
python3 - "$TEST_TMUX" "$(dirname "$0")/../tmux-protocol.h" <<'PY'
import os
+98 -23
View File
@@ -1,38 +1,113 @@
#!/bin/sh
# A waited run-shell command can complete after a control client has closed its
# input and exited. The command queue still references the client until the job
# callback runs, but control_stop has already destroyed c->control_state. Late
# command output must be discarded instead of dereferencing the stopped control
# state and killing the server.
. ./regress-common.inc
# Late command output and parse errors must be safe after a control client
# has exited or lost its session. Keep a separate session alive throughout.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
DIR=$(test_tmpdir) || exit 1
TMUX="$TEST_TMUX -S$DIR/socket -f/dev/null"
OUT=$DIR/output
pid=
OUT=$(mktemp)
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
cleanup()
{
# Release any shell still waiting, including on a failed assertion.
touch "$DIR/go"
exec 3>&-
if [ -n "$pid" ]; then
kill -CONT "$pid" 2>/dev/null
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
fi
stop_server $TMUX || exit 1
rm -rf "$DIR"
}
trap cleanup 0
trap 'exit 1' 1 2 15
$TMUX -f/dev/null new-session -d -s main || exit 1
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
$TMUX -f/dev/null -C >"$OUT" 2>&1
sleep 1
$TMUX has-session -t main 2>/dev/null || {
echo "server exited after late run-shell output"
cat "$OUT"
fail()
{
echo "$*" >&2
[ ! -f "$OUT" ] || cat "$OUT" >&2
exit 1
}
grep -q '^%exit' "$OUT" || {
echo "control client did not exit"
cat "$OUT"
exit 1
# The shell signals that it has started, then waits for the test to release it.
# Neither the job callback nor its output can run before the client is lost.
GATE="touch '$DIR/started'; while [ ! -f '$DIR/go' ]; do sleep 0.05; done"
start_client()
{
rm -f "$DIR/go" "$DIR/started"
$TMUX -C attach -t "$1" <"$DIR/input" >"$OUT" 2>&1 &
pid=$!
exec 3>"$DIR/input"
wait_for_clients 1 $TMUX || fail "control client did not attach"
}
lose_client()
{
exec 3>&-
wait_for_exit "$pid" || fail "control client did not exit"
wait "$pid" || fail "control client failed"
pid=
wait_for_clients 0 $TMUX || fail "server did not lose control client"
grep -q '^%exit' "$OUT" || fail "control client did not report exit"
}
error_or_exit()
{
! $TMUX has-session -t main 2>/dev/null ||
output_has "Unknown command: $1" $TMUX show-messages
}
late_error()
{
wait_until error_or_exit "$1" || fail "no late error from $1"
$TMUX has-session -t main || fail "server exited after $1"
}
mkfifo "$DIR/input" || exit 1
$TMUX new-session -d -s main || exit 1
# A waited run-shell retains a queue reference after control_stop has freed
# the control state. Its output must be discarded once the client is lost.
start_client main
printf 'run-shell "%s; echo TEST"\n' "$GATE" >&3
wait_until test -f "$DIR/started" || fail "run-shell did not start"
lose_client
touch "$DIR/go"
# Wait for the job callback and its output handling, not just the shell exit.
wait_for_output '' $TMUX show-messages -J || fail "late run-shell did not finish"
$TMUX has-session -t main || fail "server exited after late run-shell output"
start_client main
printf 'if-shell -b "%s" wibble1\n' "$GATE" >&3
wait_until test -f "$DIR/started" || fail "if-shell did not start"
lose_client
touch "$DIR/go"
late_error wibble1
# A stopped client cannot finish the exit handshake when its session dies.
$TMUX new-session -d -s stopped || exit 1
start_client stopped
printf 'if-shell -b "%s" wibble3\n' "$GATE" >&3
wait_until test -f "$DIR/started" || fail "stopped if-shell did not start"
kill -STOP "$pid" || exit 1
# Arm the timer and destroy its target session in one command queue. The
# timer cannot parse wibble2 until that queue has cleared the client's session.
$TMUX run-shell -b -d 0.01 -t stopped -C wibble2 \; \
kill-session -t stopped || fail "destroy stopped session failed"
$TMUX has-session -t stopped 2>/dev/null && fail "stopped session still exists"
kill -0 "$pid" || fail "stopped client disappeared"
touch "$DIR/go"
late_error wibble2
late_error wibble3
exit 0
+5 -9
View File
@@ -16,22 +16,18 @@ OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
cleanup()
{
$OUTER kill-server 2>/dev/null
$INNER kill-server 2>/dev/null
}
trap cleanup 0 1 15
has_client()
{
[ -n "$($INNER list-clients -F x 2>/dev/null)" ]
stop_server $OUTER || exit 1
stop_server $INNER || exit 1
}
trap cleanup 0
trap 'exit 1' 1 2 15
cleanup
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
$INNER set-option -g status off || exit 1
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
$OUTER set-option -g status off || exit 1
wait_until has_client || exit 1
wait_for_clients 1 $INNER || exit 1
# U+00E4 is two bytes in UTF-8, U+20AC three.
$INNER bind-key -n ä set-option -g @a yes || exit 1
+10 -4
View File
@@ -10,6 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
python3 - "$TEST_TMUX" <<'PY'
import os
@@ -25,7 +26,7 @@ server = [tmux, "-L" + label, "-f/dev/null"]
def run(*args, check=True):
return subprocess.run(server + list(args), check=check,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10)
def attach():
pid, fd = os.forkpty()
@@ -37,9 +38,9 @@ def attach():
return pid, fd
def read_until(fd, needle, timeout=5):
end = time.time() + timeout
end = time.monotonic() + timeout
data = b""
while time.time() < end:
while time.monotonic() < end:
r, _, _ = select.select([fd], [], [], 0.05)
if fd in r:
try:
@@ -87,7 +88,10 @@ run("kill-server", check=False)
run("new-session", "-d", "-x", "80", "-y", "24", "-s", "dead", "sleep 60")
pid, fd = attach()
try:
time.sleep(0.5)
deadline = time.monotonic() + 10
while not run("list-clients").stdout.strip():
assert time.monotonic() < deadline, "client did not attach"
time.sleep(.05)
run("set-option", "-g", "remain-on-exit", "on")
run("set-option", "-s", "set-clipboard", "on")
run("set-option", "-s", "get-clipboard", "request")
@@ -104,6 +108,8 @@ try:
# waiting; if it timed out before the answer arrived, try again.
run("set-option", "-s", "get-clipboard", "both")
for attempt in range(5):
# A previous attempt must not make a timed-out reply look successful.
run("delete-buffer", check=False)
request(query)
os.write(fd, b"\033]52;c;UmVwbHk=\033\\")
end = time.monotonic() + 1
+2
View File
@@ -11,6 +11,8 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
stop_server $TMUX || exit 1
trap 'stop_server $TMUX || exit 1' 0
trap 'exit 1' 1 2 15
# The only pane of the only session: the server must not exit.
$TMUX new -d 'sleep 1000' || exit 1
@@ -10,6 +10,7 @@ LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
python3 - "$TEST_TMUX" <<'PY'
import re
+33 -15
View File
@@ -1,5 +1,7 @@
#!/bin/sh
. ./regress-common.inc
# Status drags must target the window under the pointer as it moves.
PATH=/bin:/usr/bin
@@ -9,12 +11,15 @@ export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
DIR=$(test_tmpdir) || exit 1
CAPTURE=$DIR/capture
# Stop both isolated servers.
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
stop_server $TMUX2 || exit 1
stop_server $TMUX || exit 1
rm -rf "$DIR"
}
trap cleanup 0
trap 'exit 1' 1 2 15
@@ -31,15 +36,23 @@ mouse()
{
seq=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail 'send mouse failed'
sleep 0.2
}
# Check the window order after each drag update.
check_order()
{
got=$($TMUX list-windows -F '#{window_name}') || fail 'server exited'
[ "$got" = "$(printf '%s\n' "$@")" ] ||
fail "window order '$got', expected '$*'"
wait_for_output "$(printf '%s\n' "$@")" \
$TMUX list-windows -F '#{window_name}' ||
fail "window order did not become '$*'"
}
# Check the actual terminal columns before sending mouse input to a side line.
side_drawn()
{
$TMUX2 capture-pane -p -S0 -E2 >"$CAPTURE" || return 1
awk -v column="$1" '
{ text = substr($0, column, 12); gsub(/[^A-Z]/, "", text); print text }
' "$CAPTURE"
}
$TMUX new-session -d -s inner -n AAA -x 60 -y 20 'sleep 180' || exit 1
@@ -59,13 +72,14 @@ $TMUX bind -n MouseDragEnd1Status set -u @drag_window || exit 1
$TMUX2 new-session -d -s outer -x 60 -y 20 'sleep 180' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
wait_for_clients 1 $TMUX || fail 'inner client did not attach'
for position in bottom top; do
$TMUX set -g status-position "$position" || exit 1
row=20
[ "$position" = top ] && row=1
sleep 0.5
wait_for_text 'AAA BBB CCC' $TMUX2 capture-pane -p \
-S "$((row - 1))" -E "$((row - 1))" || fail 'status did not draw'
# Establish the drag on AAA, then cross BBB and CCC without releasing.
mouse 0 1 "$row" M
@@ -75,7 +89,7 @@ for position in bottom top; do
mouse 32 9 "$row" M
check_order BBB CCC AAA
mouse 0 9 "$row" m
[ -z "$($TMUX show -qv @drag_window)" ] ||
wait_for_output '' $TMUX show -qv @drag_window ||
fail 'drag end did not clear the saved window'
# Restore the order for the next position.
@@ -91,7 +105,10 @@ for side in left right; do
$TMUX set -g side-status "$side" || exit 1
column=2
[ "$side" = right ] && column=48
sleep 0.5
start=1
[ "$side" = right ] && start=49
wait_for_output "$(printf 'AAA\nBBB\nCCC')" side_drawn "$start" ||
fail 'side status did not draw'
mouse 0 "$column" 1 M
mouse 32 "$((column + 1))" 1 M
mouse 32 "$column" 2 M
@@ -99,7 +116,7 @@ for side in left right; do
mouse 32 "$column" 3 M
check_order BBB CCC AAA
mouse 0 "$column" 3 m
[ -z "$($TMUX show -qv @drag_window)" ] ||
wait_for_output '' $TMUX show -qv @drag_window ||
fail 'side drag end did not clear the saved window'
$TMUX swap-window -d -s inner:2 -t inner:1 || exit 1
$TMUX swap-window -d -s inner:1 -t inner:0 || exit 1
@@ -116,20 +133,21 @@ $TMUX bind -n MouseDrag1Status \
$TMUX bind -n MouseDrag1Control0 set -g @control yes || exit 1
$TMUX bind -n MouseDrag1Pane set -g @pane yes || exit 1
$TMUX bind -n MouseDrag1Empty set -g @pane yes || exit 1
sleep 0.5
wait_for_text CONTROL $TMUX2 capture-pane -p -S1 -E1 ||
fail 'second status line did not draw'
mouse 0 1 1 M
mouse 32 2 1 M
mouse 32 2 2 M
[ "$($TMUX show -gv @control)" = yes ] ||
wait_for_output yes $TMUX show -gv @control ||
fail 'drag did not target the second status line'
mouse 32 3 6 M
[ "$($TMUX show -gv @position)" = 2,5 ] ||
wait_for_output 2,5 $TMUX show -gv @position ||
fail 'status drag did not retain the pointer coordinates outside status'
# Changing the status height during a drag must leave it usable.
$TMUX set -g status on || exit 1
mouse 32 3 5 M
[ "$($TMUX show -gv @position)" = 2,4 ] ||
wait_for_output 2,4 $TMUX show -gv @position ||
fail 'status drag stopped after the status height changed'
[ -z "$($TMUX show -qgv @pane)" ] ||
fail 'status drag invoked a pane binding'
+1
View File
@@ -8,6 +8,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
python3 -B - "$TEST_TMUX" <<'PY'
import fcntl
+14 -7
View File
@@ -8,6 +8,7 @@ TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
python3 -B - "$TEST_TMUX" <<'PY'
import fcntl
@@ -25,7 +26,7 @@ server = [tmux, "-LtestWinsize%d" % os.getpid(), "-f/dev/null"]
pid = fd = None
def run(*args, check=True):
p = subprocess.run(server + list(args), capture_output=True)
p = subprocess.run(server + list(args), capture_output=True, timeout=10)
if check and p.returncode != 0:
raise RuntimeError("%r: %s" % (args, p.stderr.decode()))
return p.stdout.decode().strip()
@@ -59,9 +60,13 @@ def check_escape(context):
os.write(fd, b"\033")
pump(.15)
os.write(fd, b"\r")
pump(.1)
actual = run("show-option", "-gv", "@seen")
assert actual == "EER", (context, "expected EER", actual)
deadline = time.monotonic() + 1
while True:
pump(.01)
actual = run("show-option", "-gv", "@seen")
if actual == "EER":
break
assert time.monotonic() < deadline, (context, "expected EER", actual)
try:
run("new-session", "-d", "-s", "winsize", "-x80", "-y24", "sleep 60")
@@ -92,9 +97,11 @@ try:
# A successful reply must release the delay without waiting five seconds.
resize(26, 82)
os.write(fd, b"\033[8;26;82t\033[4;520;820t")
pump(.1)
assert run("list-clients", "-F",
"#{client_cell_width}x#{client_cell_height}") == "10x20"
deadline = time.monotonic() + 1
while run("list-clients", "-F",
"#{client_cell_width}x#{client_cell_height}") != "10x20":
assert time.monotonic() < deadline, "window size reply not handled"
pump(.01)
check_escape("answered resize query")
# Disconnect while a query is pending. Its timer must not outlive the
+1
View File
@@ -11,6 +11,7 @@ LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
command -v python3 >/dev/null || exit 101
DIR=$(test_tmpdir) || exit 1
TMUX_TMPDIR=$DIR
export TMUX_TMPDIR