From cd3f577c8520035ab4b51dd62d3b033a253a3911 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 3 Aug 2026 20:27:10 +0100 Subject: [PATCH] Add test for control mode events and guards. --- regress/control-notify-guard.sh | 158 ++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100755 regress/control-notify-guard.sh diff --git a/regress/control-notify-guard.sh b/regress/control-notify-guard.sh new file mode 100755 index 000000000..459ec9f35 --- /dev/null +++ b/regress/control-notify-guard.sh @@ -0,0 +1,158 @@ +#!/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 -LtestA$$ -f/dev/null" + +TMPDIR=$(mktemp -d) +IN="$TMPDIR/in" +OUT="$TMPDIR/out" +BADCFG="$TMPDIR/bad.conf" +PID= + +cleanup() +{ + exec 3>&- 2>/dev/null + [ -n "$PID" ] && kill "$PID" 2>/dev/null + $TMUX kill-server 2>/dev/null + rm -rf "$TMPDIR" +} +trap cleanup EXIT + +fail() +{ + echo "$1" >&2 + [ -s "$OUT" ] && cat "$OUT" >&2 + exit 1 +} + +wait_for() +{ + pattern=$1 + timeout=${2:-60} + i=0 + + while [ "$i" -lt "$timeout" ]; do + grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "missing: $pattern" +} + +wait_for_count() +{ + pattern=$1 + expected=$2 + timeout=${3:-60} + i=0 + + while [ "$i" -lt "$timeout" ]; do + count=$(grep -F -c -- "$pattern" "$OUT" 2>/dev/null) + [ "$count" -ge "$expected" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "missing count $expected for: $pattern" +} + +send() +{ + printf '%s\n' "$1" >&3 || fail "failed to send: $1" +} + +# A notification is a standalone protocol message. It must never be part of +# the response between the real %begin and matching %end or %error for a +# command. Ignore guard-looking command output while a real guard is open. +check_guards() +{ + awk ' + function notification(line) { + return line ~ /^%(sessions-changed|session-changed |unlinked-window-add )/ || + line ~ /^%(pause |continue |config-error )/ + } + !open && $0 ~ /^%begin [0-9]+ [0-9]+ [0-9]+$/ { + open = 1 + time = $2 + number = $3 + flags = $4 + next + } + open && ($0 == "%end " time " " number " " flags || + $0 == "%error " time " " number " " flags) { + open = 0 + next + } + notification($0) { + seen[$1]++ + if (open) { + print "notification inside guard: " $0 > "/dev/stderr" + bad = 1 + } + } + END { + if (open) { + print "unterminated command guard" > "/dev/stderr" + bad = 1 + } + if (seen["%sessions-changed"] < 2 || + seen["%session-changed"] < 1 || + seen["%unlinked-window-add"] < 2 || + seen["%pause"] < 1 || seen["%continue"] < 1 || + seen["%config-error"] < 1) { + print "missing expected notification" > "/dev/stderr" + bad = 1 + } + exit bad + }' "$OUT" || fail "bad control protocol notification ordering" +} + +$TMUX kill-server 2>/dev/null +$TMUX new-session -d -s guard -x 80 -y 24 || exit 1 +pane=$($TMUX display-message -p -t guard:0.0 '#{pane_id}') || exit 1 + +mkfifo "$IN" || exit 1 +: >"$OUT" +$TMUX -C attach-session -t guard <"$IN" >"$OUT" 2>&1 & +PID=$! +exec 3>"$IN" + +# Attaching generates a session notification synchronously from inside the +# attach command. It must follow the closing guard. +wait_for '%session-changed ' + +# Guard-looking output is arbitrary command output, not protocol state. Make +# new-session both generate notifications and print an unmatched fake %begin; +# it must not leave those notifications permanently deferred. +send "set-option -g @fake-begin '%begin 1 2 3'" +send "new-session -d -P -F '#{@fake-begin}' -s fake-begin" +wait_for_count '%sessions-changed' 1 + +# Likewise, fake %end output from a command that generates multiple +# notifications must not flush them before the command actually ends. +send "set-option -g @fake-end '%end 1 2 3'" +send "new-session -d -P -F '#{@fake-end}' -s fake-end" +wait_for_count '%sessions-changed' 2 + +# Exercise notification paths outside control-notify.c. +send "refresh-client -A '$pane:pause'" +wait_for "%pause $pane" +send "refresh-client -A '$pane:continue'" +wait_for "%continue $pane" + +printf '%s\n' 'not-a-command' >"$BADCFG" +send "source-file '$BADCFG'" +wait_for '%config-error ' + +# A final command guarantees all earlier command guards have completed before +# the transcript is checked. +send "display-message -p 'guard-check-done'" +wait_for 'guard-check-done' +check_guards + +exit 0