From 2c0f06782dcf4d6def19d1e188997b4ab9c7d912 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 6 Jul 2026 08:47:57 +0100 Subject: [PATCH] Add alerts test. --- regress/alerts.sh | 291 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 regress/alerts.sh diff --git a/regress/alerts.sh b/regress/alerts.sh new file mode 100644 index 000000000..51554c46a --- /dev/null +++ b/regress/alerts.sh @@ -0,0 +1,291 @@ +#!/bin/sh + +# monitor-activity, monitor-bell and monitor-silence: both the +# alert-activity, alert-bell and alert-silence hooks and the winlink alert +# flags (window_activity_flag, window_bell_flag, window_silence_flag and +# the #, !, ~ characters in window_flags). The sessions are detached so +# alert flags are set even on the current window; the *-action options +# still decide whether the hooks fire. Panes run cat: activity is +# generated by the tty echo of send-keys and a bell by sending a BEL and +# newline for cat to write back. + +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) +OUT=$(mktemp -d) +TMUX_TMPDIR="$OUT" +export TMUX_TMPDIR +TMUX="$TEST_TMUX -Ltest-alerts-$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null || true + rm -rf "$OUT" +} +trap cleanup EXIT + +wait_for() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $option to be '$expected' but got '$value'" +} + +assert_unchanged() +{ + option=$1 + expected=$2 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX show -gqv "$option" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $option to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +wait_for_fmt() +{ + target=$1 + fmt=$2 + expected=$3 + i=0 + + while [ $i -lt 30 ]; do + value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) + [ "$value" = "$expected" ] && return 0 + i=$((i + 1)) + sleep 0.2 + done + fail "expected $fmt for $target to be '$expected' but got '$value'" +} + +assert_fmt_unchanged() +{ + target=$1 + fmt=$2 + expected=$3 + i=0 + + while [ $i -lt 10 ]; do + value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) + [ "$value" = "$expected" ] || \ + fail "expected $fmt for $target to remain '$expected' but got '$value'" + i=$((i + 1)) + sleep 0.2 + done +} + +flags_have() +{ + target=$1 + char=$2 + + flags=$($TMUX display -pt "$target" '#{window_flags}') || + fail "display window_flags failed" + case "$flags" in + *"$char"*) ;; + *) fail "expected '$char' in window_flags for $target: '$flags'" ;; + esac +} + +flags_lack() +{ + target=$1 + char=$2 + + flags=$($TMUX display -pt "$target" '#{window_flags}') || + fail "display window_flags failed" + case "$flags" in + *"$char"*) fail "unexpected '$char' in window_flags for $target: '$flags'" ;; + esac +} + +bell() +{ + $TMUX send-keys -t "$1" -H 07 0a || fail "send-keys bell failed" +} + +activity() +{ + $TMUX send-keys -t "$1" -l x || fail "send-keys activity failed" +} + +$TMUX new -d -s mon -n w0 cat || fail "new-session mon failed" + +$TMUX set -g @log '' || fail "set @log failed" +$TMUX set-hook -g alert-bell \ + 'set -gF @log "#{@log}|alert-bell:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-bell failed" +$TMUX set-hook -g alert-activity \ + 'set -gF @log "#{@log}|alert-activity:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-activity failed" +$TMUX set-hook -g alert-silence \ + 'set -gF @log "#{@log}|alert-silence:#{hook_session_name}:#{hook_window_name}"' || + fail "set-hook alert-silence failed" + +# A bell in a non-current window (monitor-bell defaults to on) fires +# alert-bell and sets the bell flag, shown as ! in window_flags. +$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed" +assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 +flags_lack mon:bellw '!' +bell mon:bellw +wait_for @log '|alert-bell:mon:bellw' +wait_for_fmt mon:bellw '#{window_bell_flag}' 1 +flags_have mon:bellw '!' + +# Bells are not deduplicated: a second bell fires the hook again even +# though the flag is still set. +bell mon:bellw +wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw' + +# Selecting the window clears the alert flags. +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +wait_for_fmt mon:bellw '#{window_bell_flag}' 0 +flags_lack mon:bellw '!' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# A bell in the current window of a detached session still sets the flag +# and bell-action any (the default) fires the hook for the current window. +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:w0 +wait_for @log '|alert-bell:mon:w0' +wait_for_fmt mon:w0 '#{window_bell_flag}' 1 +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +wait_for_fmt mon:w0 '#{window_bell_flag}' 0 + +# bell-action none: the flag is still set but the hook does not fire. +$TMUX set -t mon bell-action none || fail "set bell-action none failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:bellw +wait_for_fmt mon:bellw '#{window_bell_flag}' 1 +assert_unchanged @log '' +$TMUX set -ut mon bell-action || fail "unset bell-action failed" +$TMUX selectw -t mon:bellw || fail "select-window bellw failed" +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# monitor-bell off: neither the flag nor the hook. +$TMUX set -wt mon:bellw monitor-bell off || fail "set monitor-bell failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:bellw +assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 +assert_unchanged @log '' + +# monitor-activity defaults to off: output sets neither flag nor hook. +$TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed" +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:actw +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0 +assert_unchanged @log '' + +# With monitor-activity on, output fires alert-activity and sets the +# activity flag, shown as # in window_flags. +$TMUX set -wt mon:actw monitor-activity on || + fail "set monitor-activity failed" +activity mon:actw +wait_for @log '|alert-activity:mon:actw' +wait_for_fmt mon:actw '#{window_activity_flag}' 1 +flags_have mon:actw '#' + +# While the flag is set further activity does not fire the hook again. +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:actw +assert_unchanged @log '' + +# On a detached session selecting the window does not clear the activity +# flag: session_set_current treats the selection itself as activity and +# the flag is raised again at once because the session is not attached. +# No hook fires as activity-action other does not apply to the current +# window. +$TMUX selectw -t mon:actw || fail "select-window actw failed" +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 +assert_unchanged @log '' + +# With a client attached, selecting the window does clear the activity +# flag and it stays clear for the current window. +mkfifo "$OUT/fifo" || fail "mkfifo failed" +$TMUX -C attach -t mon <"$OUT/fifo" >"$OUT/control.out" 2>&1 & +exec 3>"$OUT/fifo" +wait_for_fmt mon: '#{session_attached}' 1 +$TMUX selectw -t mon:actw || fail "select-window actw failed" +wait_for_fmt mon:actw '#{window_activity_flag}' 0 +flags_lack mon:actw '#' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" +exec 3>&- +wait_for_fmt mon: '#{session_attached}' 0 + +# Once the flag is clear the alert is re-armed. +activity mon:actw +wait_for @log '|alert-activity:mon:actw' + +# Activity in the current window of a detached session sets the flag but +# activity-action other (the default) does not fire the hook. +$TMUX set -wt mon:w0 monitor-activity on || + fail "set monitor-activity w0 failed" +$TMUX set -g @log '' || fail "reset @log failed" +activity mon:w0 +wait_for_fmt mon:w0 '#{window_activity_flag}' 1 +assert_unchanged @log '' +$TMUX set -wut mon:w0 monitor-activity || + fail "unset monitor-activity w0 failed" + +# monitor-silence: a quiet window fires alert-silence after the interval +# and sets the silence flag, shown as ~ in window_flags. The hook fires +# only once while the flag remains set even though the timer keeps +# running. +$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed" +assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0 +$TMUX set -g @log '' || fail "reset @log failed" +$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed" +wait_for @log '|alert-silence:mon:silw' +wait_for_fmt mon:silw '#{window_silence_flag}' 1 +flags_have mon:silw '~' +assert_unchanged @log '|alert-silence:mon:silw' +$TMUX set -wt mon:silw monitor-silence 0 || + fail "reset monitor-silence failed" +$TMUX selectw -t mon:silw || fail "select-window silw failed" +wait_for_fmt mon:silw '#{window_silence_flag}' 0 +flags_lack mon:silw '~' +$TMUX selectw -t mon:w0 || fail "select-window w0 failed" + +# A window linked into two sessions: the hook fires once per winlink and +# the flag is set in both sessions. Selecting the window in one session +# clears the flags on every winlink. +$TMUX new -d -s mon2 || fail "new-session mon2 failed" +$TMUX neww -d -t mon: -n shw cat || fail "new-window shw failed" +$TMUX link-window -d -s mon:shw -t mon2:5 || fail "link-window failed" +$TMUX set -g @log '' || fail "reset @log failed" +bell mon:shw +wait_for @log '|alert-bell:mon:shw|alert-bell:mon2:shw' +wait_for_fmt mon:shw '#{window_bell_flag}' 1 +wait_for_fmt mon2:5 '#{window_bell_flag}' 1 +$TMUX selectw -t mon2:5 || fail "select-window mon2:5 failed" +wait_for_fmt mon2:5 '#{window_bell_flag}' 0 +wait_for_fmt mon:shw '#{window_bell_flag}' 0 + +exit 0