Add options tests.

This commit is contained in:
Nicholas Marriott
2026-07-02 08:15:22 +01:00
parent baf16f0419
commit 7713cbebd2
3 changed files with 562 additions and 0 deletions

161
regress/options-array.sh Normal file
View File

@@ -0,0 +1,161 @@
#!/bin/sh
# Tests of array options in the options engine (options_array_* in options.c
# and the array handling in cmd-set-option.c / cmd-show-options.c).
#
# Array options are indexed by integer. This exercises: setting a whole array
# from a separator-delimited string; per-index set with option[N]; -a append
# (which lands at the next free index); show ordering by ascending index and
# preservation of gaps; per-index unset with -u; show -v of a single index and
# of a missing index; and per-option separators (user-keys splits only on
# comma, update-environment on space or comma).
#
# update-environment (session), status-format (session), user-keys (server)
# and command-alias (server) are used as representative array options.
#
# options-scope.sh covers scoping/inheritance and options-values.sh covers
# value validation.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# check_array $args $expected
#
# Compare the full (multi-line) show output for an array option with a
# newline-separated $expected string.
check_array()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$(printf '%s' "$2")" ]; then
echo "show $1 (array) failed."
echo "Expected:"; printf '%s\n' "$2"
echo "But got:"; printf '%s\n' "$out"
exit 1
fi
}
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- whole-array assignment splits on the separator -----------------------
#
# update-environment has the default " ," separator, so a single string value
# is split into consecutive indices starting at 0.
check_ok set -g update-environment "AAA BBB,CCC"
check_array "-g update-environment" "update-environment[0] AAA
update-environment[1] BBB
update-environment[2] CCC"
# --- -a append goes to the next free index --------------------------------
check_ok set -ga update-environment "DDD"
check_array "-g update-environment" "update-environment[0] AAA
update-environment[1] BBB
update-environment[2] CCC
update-environment[3] DDD"
# --- per-index unset leaves a gap; show preserves order and gaps ----------
check_ok set -gu update-environment[1]
check_array "-g update-environment" "update-environment[0] AAA
update-environment[2] CCC
update-environment[3] DDD"
# show -v of an existing index returns its value; a missing index is empty.
check_value "-gv update-environment[0]" "AAA"
check_value "-gv update-environment[1]" ""
# --- explicit indexed set, including out-of-order and gaps ----------------
#
# status-format is a session array; assigning an empty string first clears its
# multi-index default, then set specific indices out of order and confirm show
# sorts by ascending index and keeps the gap at [1].
check_ok set -g status-format ""
check_array "-g status-format" "status-format"
check_ok set -g status-format[5] "five"
check_ok set -g status-format[0] "zero"
check_ok set -g status-format[2] "two"
check_array "-g status-format" "status-format[0] zero
status-format[2] two
status-format[5] five"
# --- comma-only separator (user-keys) -------------------------------------
#
# user-keys splits only on comma, so an embedded space stays within one entry
# (and show quotes a value containing a space).
check_ok set -g user-keys "One,Two Three"
check_array "-g user-keys" 'user-keys[0] One
user-keys[1] "Two Three"'
# --- command-type array (a hook) ------------------------------------------
#
# Hooks are command arrays: an indexed value is parsed as a command when set
# and re-printed from the parsed command list; a syntax error is reported.
check_ok set -g alert-bell[0] "display-message hi"
check_value "-gv alert-bell[0]" "display-message hi"
check_fail "syntax error" set -g alert-bell[0] "if -x {"
# --- colour-type array ----------------------------------------------------
#
# pane-colours is a colour array; an indexed value is validated as a colour.
check_ok set -w pane-colours[0] red
check_value "-wv pane-colours[0]" "red"
check_fail "bad colour: xxxyyy" set -w pane-colours[1] xxxyyy
# --- -o refuses to overwrite an already-set index -------------------------
check_ok set -g command-alias[9] "x=list-keys"
check_fail "already set: command-alias[9]" set -go command-alias[9] "y=list-keys"
# --- non-array option rejects index syntax --------------------------------
#
# status-left is a plain string; indexing it is an error.
check_fail "not an array: status-left[0]" set -g status-left[0] "x"
assert_alive "after options-array tests"
$TMUX kill-server 2>/dev/null
exit 0

208
regress/options-scope.sh Normal file
View File

@@ -0,0 +1,208 @@
#!/bin/sh
# Tests of the options engine scoping and inheritance, as described in the
# OPTIONS section of tmux(1) and implemented in options.c, cmd-set-option.c and
# cmd-show-options.c.
#
# This exercises: global vs session vs window vs pane precedence; -u to remove
# an option (revealing the inherited value); -gu to restore a global option to
# its compiled default; scope inference from the option name (-w/-p and the
# set-window-option alias); show -v (which does NOT walk parents) versus show -A
# (which does, marking inherited values with a trailing *); unknown/ambiguous
# option errors and -q suppression; and user options (@foo) at every scope.
#
# options-values.sh covers value validation and options-array.sh covers arrays.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
# check_value $args $expected
#
# Run show-option with $args and compare the single-line output with $expected.
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# check_ok $cmd...
#
# Run a command and require that it succeeds.
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
# check_fail $expected_error $cmd...
#
# Run a command and require that it fails with the given error message.
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- global vs session precedence -----------------------------------------
#
# status-left is a session option. A value set at the session scope shadows
# the global one; show -v at each scope reports that scope's own value.
check_ok set -g status-left "GLOBAL"
check_ok set status-left "SESSION"
check_value "-v status-left" "SESSION"
check_value "-gv status-left" "GLOBAL"
# show -v does NOT inherit: -u removes the session entry, after which the
# session-scope show -v is empty even though the global value still exists.
check_ok set -u status-left
check_value "-v status-left" ""
check_value "-gv status-left" "GLOBAL"
# show -A walks the parent scopes and marks an inherited value with a "*".
out=$($TMUX show -A 2>/dev/null | grep '^status-left\*')
if [ "$out" != "status-left* GLOBAL" ]; then
echo "show -A did not mark inherited status-left."
echo "But got: '$out'"
exit 1
fi
# --- -gu restores the compiled default ------------------------------------
#
# Removing a global option with -u restores its built-in default rather than
# deleting it; status-left's default is the format "[#{session_name}] ".
check_ok set -g status-left "GLOBAL2"
check_value "-gv status-left" "GLOBAL2"
check_ok set -gu status-left
check_value "-gv status-left" "[#{session_name}] "
# --- scope inference from the option name ---------------------------------
#
# mode-keys is a window option, so a bare set-option infers the window scope;
# set-window-option (setw) is an explicit alias for the same thing, and -g w
# targets the global window options.
check_ok set mode-keys vi
check_value "-wv mode-keys" "vi"
check_ok setw mode-keys emacs
check_value "-wv mode-keys" "emacs"
check_ok set -gw mode-keys vi
check_value "-gwv mode-keys" "vi"
# cursor-colour is a window-and-pane option. A pane-scope value overrides a
# window-scope one for that pane.
check_ok set -w cursor-colour blue
check_ok set -p cursor-colour red
check_value "-pv cursor-colour" "red"
out=$($TMUX show -Ap 2>/dev/null | grep '^cursor-colour ')
if [ "$out" != "cursor-colour red" ]; then
echo "pane cursor-colour did not override window value."
echo "But got: '$out'"
exit 1
fi
# --- -U unsets a window option and clears pane copies ----------------------
#
# When a window option also has per-pane copies, -u on the window scope leaves
# those pane copies in place; -U additionally removes the option from every
# pane in the window, so all panes fall back to inheritance.
$TMUX split-window -t main || exit 1
panes=$($TMUX list-panes -t main -F '#{pane_id}')
set -- $panes
pa=$1
pb=$2
check_ok set -p -t "$pa" cursor-colour red
check_ok set -p -t "$pb" cursor-colour blue
check_ok set -w -t main cursor-colour green
check_value "-pv -t $pa cursor-colour" "red"
check_value "-pv -t $pb cursor-colour" "blue"
check_value "-wv -t main cursor-colour" "green"
check_ok set -Uw -t main cursor-colour
check_value "-pv -t $pa cursor-colour" ""
check_value "-pv -t $pb cursor-colour" ""
check_value "-wv -t main cursor-colour" ""
# --- unknown, ambiguous and -q --------------------------------------------
check_fail "invalid option: no-such-option" set -g no-such-option x
check_fail "ambiguous option: status-l" set -g status-l x
# A unique prefix resolves to the full option name.
check_ok set -g status-inte 5
check_value "-gv status-interval" "5"
# -q suppresses the error and exits successfully.
check_ok set -gq no-such-option x
check_ok show -gqv no-such-option
# --- errors from unresolvable targets -------------------------------------
#
# A -t target that does not resolve produces a scope-specific error from
# options_scope_from_name()/options_scope_from_flags().
check_fail "no such session: nosuch" show -t nosuch status-left
check_fail "no such window: nosuch" show -w -t nosuch mode-keys
check_fail "no such pane: nosuch" set -p -t nosuch cursor-colour red
# --- show with no option name lists every option --------------------------
#
# show without a specific option walks the whole table (cmd_show_options_all).
# Hooks are hidden unless -H is given.
$TMUX set -g @listme "here" || exit 1
if ! $TMUX show -g | grep -q '^@listme here$'; then
echo "show -g did not list @listme."
exit 1
fi
# alert-bell is a hook: only shown with -H.
if $TMUX show -g | grep -q '^alert-bell'; then
echo "show -g listed a hook without -H."
exit 1
fi
if ! $TMUX show -gH | grep -q '^alert-bell'; then
echo "show -gH did not list the alert-bell hook."
exit 1
fi
# --- user options at every scope ------------------------------------------
#
# @-prefixed user options can be created freely at any scope and do not
# inherit type checking.
check_ok set -g @u "global-user"
check_ok set @u "session-user"
check_ok set -w @u "window-user"
check_ok set -p @u "pane-user"
check_value "-gv @u" "global-user"
check_value "-v @u" "session-user"
check_value "-wv @u" "window-user"
check_value "-pv @u" "pane-user"
assert_alive "after options-scope tests"
$TMUX kill-server 2>/dev/null
exit 0

193
regress/options-values.sh Normal file
View File

@@ -0,0 +1,193 @@
#!/bin/sh
# Tests of options engine value validation, as implemented by
# options_from_string() and friends in options.c.
#
# Each option table entry has a type (string, number, key, colour, flag,
# choice, command) with type-specific parsing and validation. This exercises:
# number range limits; choice options rejecting unknown values; flag options
# toggling with no value and rejecting garbage; colour and key options
# rejecting invalid input; string append with -a; -F expansion at set time;
# and -o refusing to overwrite an option that is already set.
#
# options-scope.sh covers scoping/inheritance and options-array.sh covers
# arrays.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- number options -------------------------------------------------------
#
# display-time is a number with a minimum of 0; a negative value and a
# non-numeric value are both rejected via strtonum(3).
check_ok set -g display-time 4000
check_value "-gv display-time" "4000"
check_fail "value is too small: -5" set -g display-time -5
check_fail "value is invalid: abc" set -g display-time abc
# A missing value is rejected for a non-flag, non-choice option.
check_fail "empty value" set -g display-time
# --- choice options -------------------------------------------------------
#
# status-keys accepts only its listed choices (vi/emacs); anything else is an
# "unknown value" error and the option keeps its previous value.
check_ok set -g status-keys vi
check_value "-gv status-keys" "vi"
check_fail "unknown value: bogus" set -g status-keys bogus
check_value "-gv status-keys" "vi"
# --- flag options ---------------------------------------------------------
#
# focus-events is an on/off flag. Setting with no value toggles it; explicit
# on/off/yes/no/1/0 are accepted (case-insensitively); anything else fails.
check_ok set -g focus-events off
check_value "-gv focus-events" "off"
check_ok set -g focus-events # toggle
check_value "-gv focus-events" "on"
check_ok set -g focus-events # toggle back
check_value "-gv focus-events" "off"
check_ok set -g focus-events yes
check_value "-gv focus-events" "on"
check_ok set -g focus-events NO
check_value "-gv focus-events" "off"
check_fail "bad value: maybe" set -g focus-events maybe
# --- colour options -------------------------------------------------------
#
# status-bg is a colour; named colours, numbers and #rrggbb are accepted,
# garbage is rejected.
check_ok set -g status-bg red
check_value "-gv status-bg" "red"
check_ok set -g status-bg colour123
check_value "-gv status-bg" "colour123"
check_ok set -g status-bg "#00ff00"
check_value "-gv status-bg" "#00ff00"
check_fail "bad colour: xxxyyy" set -g status-bg xxxyyy
# --- style options --------------------------------------------------------
#
# status-style is a style string, validated when set; a bogus style keyword is
# rejected and the old value is retained.
check_ok set -g status-style "fg=red,bg=black"
check_value "-gv status-style" "fg=red,bg=black"
check_fail "invalid style: bg=xxxyyy" set -g status-style "bg=xxxyyy"
check_value "-gv status-style" "fg=red,bg=black"
# --- key options ----------------------------------------------------------
#
# prefix is a key; a valid key name is stored in canonical form, a bad one is
# rejected.
check_ok set -g prefix C-a
check_value "-gv prefix" "C-a"
check_fail "bad key: boguskey" set -g prefix boguskey
# --- string options with extra validation ---------------------------------
#
# default-shell is a string but is checked to be an executable shell; a bogus
# path is rejected and the old value kept.
old=$($TMUX show -gv default-shell)
check_fail "not a suitable shell: /not/a/shell" set -g default-shell /not/a/shell
check_value "-gv default-shell" "$old"
# --- user options require a value ------------------------------------------
#
# A user option set with no value at all is an error.
check_fail "empty value" set -g @novalue
# --- command options ------------------------------------------------------
#
# default-client-command is a command option: the value is parsed as a tmux
# command when set and re-printed from the parsed command list. A syntax
# error is reported and the option is left unchanged.
check_ok set -g default-client-command "new-window"
check_value "-gv default-client-command" "new-window"
check_fail "syntax error" set -g default-client-command "if -x {"
check_value "-gv default-client-command" "new-window"
# --- renamed option aliases -----------------------------------------------
#
# Historical option names are mapped to their current spelling, so setting
# cursor-color updates cursor-colour.
check_ok set -w cursor-color red
check_value "-wv cursor-colour" "red"
# --- string append (-a) ---------------------------------------------------
#
# -a appends to the current string value rather than replacing it.
check_ok set -g @str "foo"
check_ok set -ga @str "bar"
check_value "-gv @str" "foobar"
# --- -F expands at set time -----------------------------------------------
#
# With -F the value is expanded as a format once, at set time; without -F it is
# stored literally.
check_ok set -gF @expanded "#{session_name}"
check_value "-gv @expanded" "main"
check_ok set -g @literal "#{session_name}"
check_value "-gv @literal" "#{session_name}"
# --- -o refuses to overwrite ----------------------------------------------
#
# -o makes set-option fail if the option is already set, leaving it unchanged;
# it succeeds for an option that is not yet set.
check_ok set -g @once "first"
check_fail "already set: @once" set -go @once "second"
check_value "-gv @once" "first"
check_ok set -go @fresh "value"
check_value "-gv @fresh" "value"
assert_alive "after options-values tests"
$TMUX kill-server 2>/dev/null
exit 0