diff --git a/regress/options-array.sh b/regress/options-array.sh new file mode 100644 index 000000000..3b18ce977 --- /dev/null +++ b/regress/options-array.sh @@ -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 diff --git a/regress/options-scope.sh b/regress/options-scope.sh new file mode 100644 index 000000000..bbc7168aa --- /dev/null +++ b/regress/options-scope.sh @@ -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 diff --git a/regress/options-values.sh b/regress/options-values.sh new file mode 100644 index 000000000..28acd2b47 --- /dev/null +++ b/regress/options-values.sh @@ -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