mirror of
https://github.com/tmux/tmux.git
synced 2026-07-03 10:12:31 +00:00
Format tests.
This commit is contained in:
@@ -43,6 +43,29 @@ test_format()
|
||||
fi
|
||||
}
|
||||
|
||||
# test_expand $format $expected
|
||||
#
|
||||
# Expand $format in a plain format_expand context (list-windows -F on the
|
||||
# single-window "tf" session) rather than the format_expand_time context of
|
||||
# display-message. This matters for t/f: display-message runs the whole format
|
||||
# through strftime(3), so a strftime specifier there must be doubled (%%H); in a
|
||||
# format_expand context a single specifier (%H) is applied directly to the
|
||||
# variable's time.
|
||||
test_expand()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
|
||||
out=$($TMUX list-windows -t tf -F "$fmt")
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# assert_alive
|
||||
#
|
||||
# Check that the server is still responding (used after operations that could
|
||||
@@ -58,15 +81,22 @@ assert_alive()
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# Single-window session used by test_expand for format_expand-context tests.
|
||||
$TMUX new-session -d -s tf || exit 1
|
||||
|
||||
# User options used as inputs. Modifiers operate on variable names, so plain
|
||||
# literals must be provided via options (or a nested #{l:...}).
|
||||
$TMUX set @s 'abcdefghij' || exit 1
|
||||
$TMUX set @path '/usr/local/bin/foo' || exit 1
|
||||
$TMUX set @name 'window-name' || exit 1
|
||||
$TMUX set @greek 'αβγ' || exit 1 # 6 bytes, 3 columns wide
|
||||
$TMUX set @cjk '中文' || exit 1 # 6 bytes, 4 columns wide
|
||||
$TMUX set @host 'myhost' || exit 1
|
||||
$TMUX set @ts '1000000000' || exit 1 # 2001-09-09 01:46:40 UTC
|
||||
# literals must be provided via options (or a nested #{l:...}). They are set
|
||||
# globally (-g) so they are visible from every session, including the "tf"
|
||||
# session used by test_expand.
|
||||
$TMUX set -g @s 'abcdefghij' || exit 1
|
||||
$TMUX set -g @path '/usr/local/bin/foo' || exit 1
|
||||
$TMUX set -g @name 'window-name' || exit 1
|
||||
$TMUX set -g @greek 'αβγ' || exit 1 # 6 bytes, 3 columns wide
|
||||
$TMUX set -g @cjk '中文' || exit 1 # 6 bytes, 4 columns wide
|
||||
$TMUX set -g @host 'myhost' || exit 1
|
||||
$TMUX set -g @ts '1000000000' || exit 1 # 2001-09-09 01:46:40 UTC
|
||||
$TMUX set -g @sp 'a b$c' || exit 1 # shell-special characters for q:
|
||||
$TMUX set -g @hash 'a#b' || exit 1 # a "#" for q/e:
|
||||
|
||||
|
||||
# --- Comparisons and matching --------------------------------------------
|
||||
@@ -84,6 +114,15 @@ test_format "#{m/r:^[0-9]+\$,12a45}" "0"
|
||||
# m/ri: regular expression, ignore case.
|
||||
test_format "#{m/ri:^ab+\$,ABBB}" "1"
|
||||
test_format "#{m/ri:^ab+\$,ACCC}" "0"
|
||||
# m/z: fuzzy match, returns a boolean.
|
||||
test_format "#{m/z:foo,foobar}" "1"
|
||||
test_format "#{m/z:xyz,foobar}" "0"
|
||||
# m/p: fuzzy match, returns the matched (0-based) column positions.
|
||||
test_format "#{m/p:ac,abc}" "0,2"
|
||||
test_format "#{m/p:xyz,abc}" ""
|
||||
# Fuzzy match against empty text.
|
||||
test_format "#{m/p:x,}" ""
|
||||
test_format "#{m/z:x,}" "0"
|
||||
|
||||
# String comparisons.
|
||||
test_format "#{==:#{@host},myhost}" "1"
|
||||
@@ -107,6 +146,29 @@ test_format "#{!!:0}" "0"
|
||||
test_format "#{!!:non-empty}" "1"
|
||||
|
||||
|
||||
# --- Quoting (q) ---------------------------------------------------------
|
||||
|
||||
# q: escapes shell special characters with a backslash.
|
||||
test_format "#{q:@sp}" 'a\ b\$c'
|
||||
# q/e and q/h escape "#" for the format/style parser by doubling it.
|
||||
test_format "#{q/e:@hash}" 'a##b'
|
||||
test_format "#{q/h:@hash}" 'a##b'
|
||||
# q/a quotes the value as a single shell argument.
|
||||
test_format "#{q/a:@sp}" '"a b\$c"'
|
||||
|
||||
|
||||
# --- Name existence (N) --------------------------------------------------
|
||||
|
||||
# N/w is true if a window with the (expanded) name exists in the session, N/s
|
||||
# if a session with that name exists. The default (no argument) is /w.
|
||||
$TMUX rename-window -t main:0 knownwin
|
||||
test_format "#{N/s:main}" "1"
|
||||
test_format "#{N/s:nosuchsession}" "0"
|
||||
test_format "#{N/w:knownwin}" "1" "main:"
|
||||
test_format "#{N/w:nosuchwindow}" "0" "main:"
|
||||
test_format "#{N:nosuchwindow}" "0" "main:"
|
||||
|
||||
|
||||
# --- Numeric operations (e) ----------------------------------------------
|
||||
|
||||
# Integer operators.
|
||||
@@ -145,9 +207,14 @@ assert_alive "division by zero"
|
||||
# a: numeric value to its ASCII character.
|
||||
test_format "#{a:98}" "b"
|
||||
test_format "#{a:65}" "A"
|
||||
# a: out-of-range or non-numeric input yields an empty string.
|
||||
test_format "#{a:200}" ""
|
||||
test_format "#{a:notanumber}" ""
|
||||
# R: repeat first argument second-argument times.
|
||||
test_format "#{R:a,3}" "aaa"
|
||||
test_format "#{R:ab,2}" "abab"
|
||||
# A long repeat exercises output-buffer growth during expansion.
|
||||
test_format "#{n:#{R:x,300}}" "300"
|
||||
|
||||
|
||||
# --- Width, padding and truncation ---------------------------------------
|
||||
@@ -155,15 +222,23 @@ test_format "#{R:ab,2}" "abab"
|
||||
# =N truncates from the start, =-N from the end.
|
||||
test_format "#{=5:@s}" "abcde"
|
||||
test_format "#{=-5:@s}" "fghij"
|
||||
# = with no width, or a non-numeric width, does not truncate.
|
||||
test_format "#{=:@s}" "abcdefghij"
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
# A marker is appended/prepended only when trimming actually occurs.
|
||||
test_format "#{=/5/...:@s}" "abcde..."
|
||||
test_format "#{=/5/...:@name}" "windo..."
|
||||
test_format "#{=/20/...:@s}" "abcdefghij"
|
||||
# Truncation is display-width (UTF-8) aware.
|
||||
# Truncation is display-width (UTF-8) aware: a wide (2-column) character is only
|
||||
# included if it fits entirely within the limit.
|
||||
test_format "#{=3:@greek}" "αβγ"
|
||||
test_format "#{=2:@greek}" "αβ"
|
||||
test_format "#{=2:@cjk}" "中"
|
||||
test_format "#{=1:@cjk}" ""
|
||||
# Markers with wide characters: the marker is added when trimming occurs, and a
|
||||
# limit that splits a wide character drops it entirely.
|
||||
test_format "#{=/2/x:@cjk}" "中x"
|
||||
test_format "#{=/1/x:@cjk}" "x"
|
||||
|
||||
# p pads to a width: a positive width left-aligns (pads on the right), a
|
||||
# negative width right-aligns (pads on the left).
|
||||
@@ -171,6 +246,12 @@ test_format "#{p12:@name}" "window-name "
|
||||
test_format "#{p-12:@name}" " window-name"
|
||||
# No padding once the value already meets the width.
|
||||
test_format "#{p3:@name}" "window-name"
|
||||
# p with no width does nothing.
|
||||
test_format "#{p:@name}" "window-name"
|
||||
# Padding is display-width aware: @cjk is 4 columns wide, so p6/p-6 add two
|
||||
# spaces (not four).
|
||||
test_format "#{p6:@cjk}" "中文 "
|
||||
test_format "#{p-6:@cjk}" " 中文"
|
||||
|
||||
# n is byte length, w is display width.
|
||||
test_format "#{n:@s}" "10"
|
||||
@@ -199,34 +280,40 @@ if [ -z "$($TMUX display-message -p '#{t/r:@ts}')" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# t/f: custom strftime format applied to the variable's time. The % specifiers
|
||||
# are doubled because display-message also expands the format through strftime;
|
||||
# the colon inside the format is escaped as '#:'.
|
||||
test_format "#{t/f/%%Y:@ts}" "2001"
|
||||
test_format "#{t/f/%%Y-%%m-%%d:@ts}" "2001-09-09"
|
||||
test_format "#{t/f/%%H#:%%M#:%%S:@ts}" "01:46:40"
|
||||
# t/f: custom strftime format applied to the variable's time. Tested in a
|
||||
# format_expand context (list-windows -F), where a single strftime specifier is
|
||||
# applied directly. (In display-message, which additionally expands the format
|
||||
# through strftime, these would need to be doubled - %%Y etc.) The colon in the
|
||||
# format is escaped as '#:' because it is otherwise the modifier separator.
|
||||
test_expand "#{t/f/%Y:@ts}" "2001"
|
||||
test_expand "#{t/f/%Y-%m-%d:@ts}" "2001-09-09"
|
||||
test_expand "#{t/f/%H#:%M#:%S:@ts}" "01:46:40"
|
||||
# An escaped comma in the custom format is unescaped before strftime.
|
||||
test_expand "#{t/f/%Y#,end:@ts}" "2001,end"
|
||||
|
||||
# T: expands its argument and then runs the result through strftime with the
|
||||
# current time. A value with no strftime specifier is returned unchanged.
|
||||
test_format "#{T:@ts}" "1000000000"
|
||||
|
||||
# --- Loops (S, W, P) -----------------------------------------------------
|
||||
|
||||
# Windows in the session, iterated in index order.
|
||||
$TMUX set -g automatic-rename off
|
||||
$TMUX rename-window -t main:0 w0
|
||||
$TMUX new-window -t main: -n w1
|
||||
$TMUX new-window -t main: -n w2
|
||||
test_format "#{W:#{window_index}}" "012" "main:"
|
||||
test_format "#{W:[#{window_name}]}" "[w0][w1][w2]" "main:"
|
||||
|
||||
# Panes: iteration order depends on layout, so assert a per-item constant to
|
||||
# check the count/iteration only.
|
||||
$TMUX split-window -t main:0 -d
|
||||
$TMUX split-window -t main:0 -d
|
||||
test_format "#{P:x}" "xxx" "main:0"
|
||||
|
||||
# Sessions: assert a per-session constant (order independent).
|
||||
$TMUX new-session -d -s alpha
|
||||
$TMUX new-session -d -s beta
|
||||
test_format "#{S:s}" "sss"
|
||||
# t/p (pretty) and t/r (relative) format times by age relative to now, with a
|
||||
# different branch per age band. Build options a known number of seconds in the
|
||||
# past and check each yields a non-empty result (the exact text depends on the
|
||||
# wall clock, so only non-emptiness is asserted).
|
||||
now=$(date +%s)
|
||||
for age in 30 300 4000 90000 200000 3000000 40000000; do
|
||||
$TMUX set -g @age "$((now - age))"
|
||||
if [ -z "$($TMUX display-message -p '#{t/r:@age}')" ]; then
|
||||
echo "Empty #{t/r:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$($TMUX display-message -p '#{t/p:@age}')" ]; then
|
||||
echo "Empty #{t/p:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# A time in the future has no relative form.
|
||||
$TMUX set -g @future "$((now + 100000))"
|
||||
test_format "#{t/r:@future}" ""
|
||||
|
||||
|
||||
# --- Content search (C) --------------------------------------------------
|
||||
@@ -254,6 +341,10 @@ test_format "#{c:#7f7f7f}" "7f7f7f"
|
||||
test_format "#{c/f:red}" "${ESC}[31m"
|
||||
test_format "#{c/b:red}" "${ESC}[41m"
|
||||
test_format "#{c/b:colour4}" "${ESC}[48;5;4m"
|
||||
# "none" gives a reset; an unknown colour gives an empty string.
|
||||
test_format "#{c/f:none}" "${ESC}[0m"
|
||||
test_format "#{c:notacolour}" ""
|
||||
test_format "#{c/f:notacolour}" ""
|
||||
|
||||
|
||||
# --- Nesting and limits --------------------------------------------------
|
||||
@@ -267,9 +358,206 @@ test_format "#{n:#{b:@path}}" "3"
|
||||
# Nested l: literal expanded then truncated.
|
||||
test_format "#{=5:#{l:abcdefghij}}" "abcde"
|
||||
|
||||
# Deeper nesting: basename -> pad to 10 -> truncate to 5.
|
||||
test_format "#{=5:#{p10:#{b:@path}}}" "foo "
|
||||
# A substitution applied to a nested basename.
|
||||
test_format "#{s/o/O/:#{b:@path}}" "fOO"
|
||||
|
||||
# Unbounded self-recursion must hit the loop limit rather than crash.
|
||||
$TMUX set @rec '#{E:@rec}'
|
||||
$TMUX display-message -p '#{E:@rec}' >/dev/null 2>&1
|
||||
assert_alive "recursive expansion"
|
||||
|
||||
|
||||
# --- Missing, malformed and limit inputs ---------------------------------
|
||||
|
||||
# An undefined variable expands to empty; modifiers on it behave sensibly.
|
||||
test_format "#{@undefined}" ""
|
||||
test_format "#{=5:@undefined}" ""
|
||||
test_format "#{b:@undefined}" ""
|
||||
test_format "#{n:@undefined}" "0"
|
||||
|
||||
# Malformed numeric expressions expand to empty rather than erroring out.
|
||||
test_format "#{e|+|:notanumber,2}" "" # invalid left operand
|
||||
test_format "#{e|+|:2,notanumber}" "" # invalid right operand
|
||||
test_format "#{e|badop|:1,2}" "" # unknown operator
|
||||
test_format "#{e|+|f|x:1,2}" "" # invalid precision
|
||||
test_format "#{e|+|:1}" "" # too few operands
|
||||
test_format "#{e|+|f|2|extra:1,2}" "" # too many arguments (limit is 3)
|
||||
|
||||
# Repeat with a non-numeric or zero count yields an empty string.
|
||||
test_format "#{R:a,notanumber}" ""
|
||||
test_format "#{R:a,0}" ""
|
||||
|
||||
# Comparisons with too few arguments expand to empty.
|
||||
test_format "#{==:a}" ""
|
||||
test_format "#{<:a}" ""
|
||||
|
||||
# A substitution with fewer than two arguments is a no-op.
|
||||
test_format "#{s/onlyone:@s}" "abcdefghij"
|
||||
|
||||
# A non-numeric width for = or p is treated as no width (no change).
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
test_format "#{p/x:@s}" "abcdefghij"
|
||||
|
||||
# The I (client terminal) modifier with no attached client is empty; this also
|
||||
# exercises its argument parsing (/c termcap, /f feature, default). The
|
||||
# non-empty terminal cases are covered with a real client in format-variables.sh.
|
||||
test_format "#{I/c:RGB}" ""
|
||||
test_format "#{I/f:overline}" ""
|
||||
test_format "#{I:x}" ""
|
||||
|
||||
|
||||
# --- Escaping inside modifiers -------------------------------------------
|
||||
|
||||
# A "," or "#" inside a modifier argument is escaped with "#".
|
||||
test_format "#{s/#,/-/:#{l:a,b,c}}" "a-b-c" # escaped comma in the pattern
|
||||
test_format "#{=/3/#,:@s}" "abc," # escaped comma in the marker
|
||||
# The truncation marker is itself expanded as a format.
|
||||
test_format "#{=/3/#{l:>}:@s}" "abc>"
|
||||
|
||||
# Substitution flags: a third argument of "i" is case-insensitive; an invalid
|
||||
# regular expression leaves the text unchanged.
|
||||
test_format "#{s/A/X/i:@s}" "Xbcdefghij"
|
||||
test_format "#{s/[/X/:@s}" "abcdefghij"
|
||||
|
||||
|
||||
# --- Unicode in modifier arguments ---------------------------------------
|
||||
|
||||
# Wide (CJK) and emoji text: matching, substitution, repeat and markers all
|
||||
# operate on characters, and n/w report bytes/columns.
|
||||
$TMUX set -g @emoji '😀😀' || exit 1 # 8 bytes, 4 columns
|
||||
test_format "#{m:*中*,#{@cjk}}" "1"
|
||||
test_format "#{s/文/X/:@cjk}" "中X"
|
||||
test_format "#{R:中,3}" "中中中"
|
||||
test_format "#{=/1/中:@s}" "a中"
|
||||
test_format "#{w:@emoji}" "4"
|
||||
test_format "#{n:@emoji}" "8"
|
||||
test_format "#{=2:@emoji}" "😀"
|
||||
|
||||
|
||||
# --- Server messages (show-messages) -------------------------------------
|
||||
|
||||
# show-messages formats each logged message (this exercises the message-time
|
||||
# formatting path); just check the server survives producing it.
|
||||
$TMUX show-messages >/dev/null 2>&1
|
||||
assert_alive "show-messages"
|
||||
|
||||
|
||||
# --- Verbose expansion (logging) -----------------------------------------
|
||||
|
||||
# display-message -v turns on format logging, so re-expanding a representative
|
||||
# set of formats with -v exercises the logging code paths. Only survival is
|
||||
# checked; the log text itself is not asserted.
|
||||
for f in \
|
||||
'#{=3:@s}' \
|
||||
'#{e|+|:2,3}' \
|
||||
'#{e|*|f|2:2.5,2}' \
|
||||
'#{m:*a*,abc}' \
|
||||
'#{<:3,5}' \
|
||||
'#{s/a/X/:@s}' \
|
||||
'#{b:@path}' \
|
||||
'#{t:@ts}' \
|
||||
'#{p6:@name}' \
|
||||
'#{=3:#{b:@path}}'; do
|
||||
$TMUX display-message -v -p "$f" >/dev/null 2>&1
|
||||
done
|
||||
assert_alive "verbose expansion"
|
||||
|
||||
|
||||
# --- Loops and sorting (S, W, P, L) --------------------------------------
|
||||
#
|
||||
# These need a fully controlled server so the set of sessions, windows and
|
||||
# panes (and their order) is known, so start from a clean server. This must be
|
||||
# the last section as it discards the setup above.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# Sessions, created in this order, so session ids (and hence creation order)
|
||||
# are zeta=$0, alpha=$1, mike=$2.
|
||||
$TMUX new-session -d -s zeta -x 80 -y 24 || exit 1
|
||||
$TMUX new-session -d -s alpha || exit 1
|
||||
$TMUX new-session -d -s mike || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
|
||||
# S loops over every session. The default order is by session id (SORT_INDEX),
|
||||
# /i is the same, /n is by name, and the r suffix reverses.
|
||||
test_format "#{S:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/i:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/n:#{session_name} }" "alpha mike zeta "
|
||||
test_format "#{S/nr:#{session_name} }" "zeta mike alpha "
|
||||
test_format "#{S/ir:#{session_name} }" "mike alpha zeta "
|
||||
# /t sorts by activity time; the exact order is timing-dependent, so just check
|
||||
# every session is still iterated (this exercises the activity-sort branch).
|
||||
test_format "#{S/t:x}" "xxx"
|
||||
# An unrecognised sort letter falls back to the default order; /r on its own
|
||||
# reverses that default (this covers the fall-through branch).
|
||||
test_format "#{S/r:#{session_name} }" "mike alpha zeta "
|
||||
|
||||
# Windows in session zeta: window 0 renamed charlie, then alpha at 1, bravo at
|
||||
# 2. The default order is by index (SORT_ORDER), /n is by name, r reverses.
|
||||
$TMUX rename-window -t zeta:0 charlie
|
||||
$TMUX new-window -d -t zeta:1 -n alpha
|
||||
$TMUX new-window -d -t zeta:2 -n bravo
|
||||
test_format "#{W:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/n:#{window_name} }" "alpha bravo charlie " "zeta:"
|
||||
test_format "#{W/nr:#{window_name} }" "charlie bravo alpha " "zeta:"
|
||||
test_format "#{W/ir:#{window_index}}" "210" "zeta:"
|
||||
# /i (by index) and /t (by activity); /i matches the default order here.
|
||||
test_format "#{W/i:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/t:x}" "xxx" "zeta:"
|
||||
# An unrecognised sort letter falls back to the default order; /r reverses it.
|
||||
test_format "#{W/r:#{window_name} }" "bravo alpha charlie " "zeta:"
|
||||
|
||||
# Panes in window zeta:charlie. Splitting the active (newest) pane each time
|
||||
# makes pane index match creation order (0,1,2 left to right). The default
|
||||
# order is by creation (SORT_CREATION), r reverses.
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
test_format "#{P:#{pane_index}}" "012" "zeta:charlie"
|
||||
test_format "#{P/r:#{pane_index}}" "210" "zeta:charlie"
|
||||
# A pane-sort argument is accepted; for panes only the r (reverse) suffix has an
|
||||
# effect, so these all keep the count and exercise the argument branch.
|
||||
test_format "#{P/i:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/n:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/t:x}" "xxx" "zeta:charlie"
|
||||
|
||||
# Verbose expansion of the loops, to exercise their logging paths.
|
||||
$TMUX display-message -v -p "#{S:#{session_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta: -p "#{W:#{window_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta:charlie -p "#{P:#{pane_index}}" >/dev/null 2>&1
|
||||
assert_alive "verbose loop expansion"
|
||||
|
||||
# L loops over attached clients. Attach two control-mode clients, each held
|
||||
# open by a background process keeping a FIFO's write end open.
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||
# Hold the write ends open so the control clients stay attached.
|
||||
sleep 30 >"$FIFO1" &
|
||||
HOLD1=$!
|
||||
sleep 30 >"$FIFO2" &
|
||||
HOLD2=$!
|
||||
$TMUX -C attach -t zeta <"$FIFO1" >/dev/null 2>&1 &
|
||||
CC1=$!
|
||||
$TMUX -C attach -t alpha <"$FIFO2" >/dev/null 2>&1 &
|
||||
CC2=$!
|
||||
sleep 1
|
||||
# Two clients attached: L emits one item per client.
|
||||
test_format "#{L:x}" "xx"
|
||||
# The client sort orders (default, index, name, activity, reversed) are all
|
||||
# accepted; assert only the count so the test does not depend on client names or
|
||||
# timing.
|
||||
test_format "#{L/i:x}" "xx"
|
||||
test_format "#{L/n:x}" "xx"
|
||||
test_format "#{L/t:x}" "xx"
|
||||
test_format "#{L/nr:x}" "xx"
|
||||
test_format "#{L/r:x}" "xx"
|
||||
# Now detach one and confirm the count drops to one.
|
||||
kill $HOLD2 2>/dev/null
|
||||
sleep 1
|
||||
test_format "#{L:x}" "x"
|
||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
|
||||
exit 0
|
||||
|
||||
143
regress/format-mouse.sh
Normal file
143
regress/format-mouse.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the mouse format variables (mouse_x, mouse_y, mouse_word,
|
||||
# mouse_line, ...). These are only populated while a mouse key binding is being
|
||||
# dispatched, so the test drives a real mouse event:
|
||||
#
|
||||
# - an inner client is attached inside a pane of a second ("outer") tmux
|
||||
# server, giving the inner server a genuine terminal;
|
||||
# - mouse mode is on and a MouseDown1Pane binding records the mouse format
|
||||
# variables into an option;
|
||||
# - an SGR mouse sequence is written to the outer pane, so the inner client
|
||||
# receives it as a real mouse click.
|
||||
#
|
||||
# This exercises the mouse callbacks and the grid word/line lookup code that
|
||||
# display-message cannot otherwise reach.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# click COL ROW
|
||||
#
|
||||
# Write an SGR mouse press then release (button 0) at 1-based COL/ROW to the
|
||||
# outer pane holding the inner client.
|
||||
click()
|
||||
{
|
||||
col="$1"
|
||||
row="$2"
|
||||
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' "$col" "$row" "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
# Inner session with a single pane running cat, so its content is exactly what
|
||||
# we send it.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 'cat' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:0.0 'alpha beta gamma' Enter
|
||||
sleep 1
|
||||
|
||||
# Record every pane mouse variable when the pane is clicked.
|
||||
$TMUX bind -n MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @m 'x=#{mouse_x} y=#{mouse_y} word=#{mouse_word} line=#{mouse_line} pane=#{mouse_pane} hl=[#{mouse_hyperlink}]'"
|
||||
|
||||
# Attach a real client inside an outer tmux pane. Clicks all target the first
|
||||
# row, which lines up with the inner client regardless of the outer status line.
|
||||
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Click column 3, row 1: over the first word ("alpha") of the first line.
|
||||
click 3 1
|
||||
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
[ -n "$M" ] || fail "Mouse binding did not fire (no @m)."
|
||||
|
||||
# mouse_x is 0-based column (SGR column 3 -> x 2); mouse_y is 0-based row 0.
|
||||
case "$M" in
|
||||
*"x=2 "*) ;;
|
||||
*) fail "Unexpected mouse_x in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"y=0 "*) ;;
|
||||
*) fail "Unexpected mouse_y in: $M" ;;
|
||||
esac
|
||||
# mouse_word is the word under the cursor, mouse_line the whole line.
|
||||
case "$M" in
|
||||
*"word=alpha "*) ;;
|
||||
*) fail "Unexpected mouse_word in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"line=alpha beta gamma "*) ;;
|
||||
*) fail "Unexpected mouse_line in: $M" ;;
|
||||
esac
|
||||
|
||||
# A click in a different column selects a different word.
|
||||
click 8 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
case "$M" in
|
||||
*"word=beta "*) ;;
|
||||
*) fail "Unexpected mouse_word for second click in: $M" ;;
|
||||
esac
|
||||
|
||||
# The same variables have a separate path when the pane is in a mode (the word
|
||||
# and line come from the mode, not the live grid). A binding in the copy-mode
|
||||
# key table fires while copy mode is active.
|
||||
$TMUX bind -T copy-mode MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @cm 'x=#{mouse_x} word=#{mouse_word} line=#{mouse_line}'"
|
||||
$TMUX copy-mode -t cov:0.0
|
||||
sleep 1
|
||||
click 8 1
|
||||
CM=$($TMUX show -gv @cm 2>/dev/null)
|
||||
case "$CM" in
|
||||
*"word=beta"*) ;;
|
||||
*) fail "Unexpected copy-mode mouse_word in: $CM" ;;
|
||||
esac
|
||||
$TMUX send-keys -t cov:0.0 -X cancel
|
||||
sleep 1
|
||||
|
||||
# Hyperlinks: a new window whose pane emits an OSC 8 hyperlink over the text
|
||||
# "LINKED". Clicking it reports the target URL via mouse_hyperlink (this drives
|
||||
# the grid hyperlink lookup). The emitter is written to a small script to keep
|
||||
# the escape sequence readable.
|
||||
LINKSH="${TMPDIR:-/tmp}/fmt-mouse-link-$$.sh"
|
||||
cat >"$LINKSH" <<'EOF'
|
||||
#!/bin/sh
|
||||
printf '\033]8;;http://example.com\033\\LINKED\033]8;;\033\\\n'
|
||||
exec cat
|
||||
EOF
|
||||
chmod +x "$LINKSH"
|
||||
$TMUX neww -t cov: -n link "$LINKSH"
|
||||
sleep 1
|
||||
$TMUX select-window -t cov:link
|
||||
sleep 1
|
||||
click 3 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
rm -f "$LINKSH"
|
||||
case "$M" in
|
||||
*"hl=[http://example.com]"*) ;;
|
||||
*) fail "Unexpected mouse_hyperlink in: $M" ;;
|
||||
esac
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
388
regress/format-variables.sh
Normal file
388
regress/format-variables.sh
Normal file
@@ -0,0 +1,388 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests that every format variable listed in tmux(1) (the format_table in
|
||||
# format.c) can be expanded without crashing the server, and checks the value
|
||||
# of a stable subset.
|
||||
#
|
||||
# The main point is coverage and crash-safety: each variable is expanded in a
|
||||
# rich context - a real attached client (from a nested tmux), a control-mode
|
||||
# client, a grouped session, two windows with a bell alert, a window with two
|
||||
# panes running cat, a paste buffer and options - so the per-variable callbacks
|
||||
# actually run. format-modifiers.sh covers the modifier machinery; this covers
|
||||
# the variable callbacks.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
# A second server on its own socket provides a real terminal (an inner client
|
||||
# attached inside one of its panes) so client terminal variables are populated.
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
# Every variable name in format_table[]. Kept as a plain word list so it can be
|
||||
# iterated with normal shell word splitting.
|
||||
NAMES="
|
||||
active_window_index
|
||||
alternate_on
|
||||
alternate_saved_x
|
||||
alternate_saved_y
|
||||
bracket_paste_flag
|
||||
buffer_created
|
||||
buffer_full
|
||||
buffer_mode_format
|
||||
buffer_name
|
||||
buffer_sample
|
||||
buffer_size
|
||||
client_activity
|
||||
client_cell_height
|
||||
client_cell_width
|
||||
client_colours
|
||||
client_control_mode
|
||||
client_created
|
||||
client_discarded
|
||||
client_flags
|
||||
client_height
|
||||
client_key_table
|
||||
client_last_session
|
||||
client_mode_format
|
||||
client_name
|
||||
client_pid
|
||||
client_prefix
|
||||
client_readonly
|
||||
client_session
|
||||
client_termfeatures
|
||||
client_termname
|
||||
client_termtype
|
||||
client_theme
|
||||
client_tty
|
||||
client_uid
|
||||
client_user
|
||||
client_utf8
|
||||
client_width
|
||||
client_written
|
||||
config_files
|
||||
cursor_blinking
|
||||
cursor_character
|
||||
cursor_colour
|
||||
cursor_flag
|
||||
cursor_shape
|
||||
cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_bytes
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
host_short
|
||||
insert_flag
|
||||
keypad_cursor_flag
|
||||
keypad_flag
|
||||
last_window_index
|
||||
mouse_all_flag
|
||||
mouse_any_flag
|
||||
mouse_button_flag
|
||||
mouse_hyperlink
|
||||
mouse_line
|
||||
mouse_pane
|
||||
mouse_sgr_flag
|
||||
mouse_standard_flag
|
||||
mouse_status_line
|
||||
mouse_status_range
|
||||
mouse_utf8_flag
|
||||
mouse_word
|
||||
mouse_x
|
||||
mouse_y
|
||||
next_session_id
|
||||
origin_flag
|
||||
pane_active
|
||||
pane_at_bottom
|
||||
pane_at_left
|
||||
pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
pane_dead_signal
|
||||
pane_dead_status
|
||||
pane_dead_time
|
||||
pane_fg
|
||||
pane_flags
|
||||
pane_floating_flag
|
||||
pane_format
|
||||
pane_height
|
||||
pane_id
|
||||
pane_in_mode
|
||||
pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_mode
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
pane_start_path
|
||||
pane_synchronized
|
||||
pane_tabs
|
||||
pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
pane_z
|
||||
pane_zoomed_flag
|
||||
pid
|
||||
scroll_region_lower
|
||||
scroll_region_upper
|
||||
server_sessions
|
||||
session_active
|
||||
session_activity
|
||||
session_activity_flag
|
||||
session_alert
|
||||
session_alerts
|
||||
session_attached
|
||||
session_attached_list
|
||||
session_bell_flag
|
||||
session_created
|
||||
session_format
|
||||
session_group
|
||||
session_group_attached
|
||||
session_group_attached_list
|
||||
session_group_list
|
||||
session_group_many_attached
|
||||
session_group_size
|
||||
session_grouped
|
||||
session_id
|
||||
session_last_attached
|
||||
session_many_attached
|
||||
session_marked
|
||||
session_name
|
||||
session_path
|
||||
session_silence_flag
|
||||
session_stack
|
||||
session_windows
|
||||
sixel_support
|
||||
socket_path
|
||||
start_time
|
||||
synchronized_output_flag
|
||||
tree_mode_format
|
||||
uid
|
||||
user
|
||||
version
|
||||
window_active
|
||||
window_active_clients
|
||||
window_active_clients_list
|
||||
window_active_sessions
|
||||
window_active_sessions_list
|
||||
window_activity
|
||||
window_activity_flag
|
||||
window_bell_flag
|
||||
window_bigger
|
||||
window_cell_height
|
||||
window_cell_width
|
||||
window_end_flag
|
||||
window_flags
|
||||
window_format
|
||||
window_height
|
||||
window_id
|
||||
window_index
|
||||
window_last_flag
|
||||
window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_marked_flag
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
window_panes
|
||||
window_raw_flags
|
||||
window_silence_flag
|
||||
window_stack_index
|
||||
window_start_flag
|
||||
window_visible_layout
|
||||
window_width
|
||||
window_zoomed_flag
|
||||
wrap_flag
|
||||
"
|
||||
|
||||
# test_var $name $expected [$extra_args...]
|
||||
#
|
||||
# Expand a single #{name} and compare against $expected. Any extra arguments
|
||||
# are passed straight to display-message (e.g. -c or -t).
|
||||
test_var()
|
||||
{
|
||||
name="$1"
|
||||
exp="$2"
|
||||
shift 2
|
||||
|
||||
out=$($TMUX display-message "$@" -p "#{$name}")
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Variable test failed for '#{$name}'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server did not survive: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
FIFO="${TMPDIR:-/tmp}/fmt-vars-$$"
|
||||
HOLD=""
|
||||
CC=""
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$HOLD" ] && kill $HOLD 2>/dev/null
|
||||
[ -n "$CC" ] && kill $CC 2>/dev/null
|
||||
rm -f "$FIFO"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# A session "cov" with a window "win0" holding two panes running cat, plus a
|
||||
# second window, an option and a paste buffer.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 -n win0 'cat' || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
$TMUX set -g monitor-bell on
|
||||
$TMUX set -g monitor-activity on
|
||||
$TMUX split-window -t cov:win0 -d 'cat' || exit 1
|
||||
$TMUX new-window -d -t cov:1 -n win1 'cat' || exit 1
|
||||
$TMUX set -g @opt 'optionvalue' || exit 1
|
||||
$TMUX set-buffer -b buf0 'somebuffer' || exit 1
|
||||
|
||||
# A second session grouped with cov, so the session_group_* variables have real
|
||||
# data to report.
|
||||
$TMUX new-session -d -s cov2 -t cov || exit 1
|
||||
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:win0.0 'some pane content' Enter
|
||||
# Ring the bell in the non-current window so a bell alert is raised on the
|
||||
# session (this populates session_alert/session_alerts and window_bell_flag).
|
||||
$TMUX send-keys -t cov:win1.0 C-g
|
||||
sleep 1
|
||||
|
||||
# Attach a control-mode client, held open by a background process keeping the
|
||||
# write end of a FIFO open, so client_* variables have a client to read.
|
||||
rm -f "$FIFO"
|
||||
mkfifo "$FIFO" || exit 1
|
||||
sleep 30 >"$FIFO" &
|
||||
HOLD=$!
|
||||
$TMUX -C attach -t cov <"$FIFO" >/dev/null 2>&1 &
|
||||
CC=$!
|
||||
|
||||
# Attach a real client too: an inner tmux running inside a pane of the second
|
||||
# server gets a genuine terminal, which populates the terminal-dependent client
|
||||
# variables (client_termname, cursor_shape, the I modifier, ...).
|
||||
$TMUX2 new-session -d -x 90 -y 30 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
|
||||
# The real (terminal) client, identified by not being in control mode.
|
||||
RC=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==0 { print $2; exit }')
|
||||
# The control client.
|
||||
CLIENT=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==1 { print $2; exit }')
|
||||
[ -n "$RC" ] || fail "No real client attached."
|
||||
[ -n "$CLIENT" ] || fail "No control client attached."
|
||||
|
||||
# Expand every variable at once, with the real terminal client and a target
|
||||
# pane in context, and confirm the server survives. This runs every callback.
|
||||
FMT=""
|
||||
for n in $NAMES; do
|
||||
FMT="$FMT#{$n}"
|
||||
done
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "$FMT" >/dev/null 2>&1
|
||||
assert_alive "expanding all variables together"
|
||||
|
||||
# Expand each variable on its own too, so a crash can be pinned to one name.
|
||||
for n in $NAMES; do
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "#{$n}" >/dev/null 2>&1
|
||||
assert_alive "expanding #{$n}"
|
||||
done
|
||||
|
||||
# Deterministic checks on stable variables (targeting pane 0 of window 0).
|
||||
TGT="cov:win0.0"
|
||||
test_var session_name "cov" -t "$TGT"
|
||||
test_var window_name "win0" -t "$TGT"
|
||||
test_var window_index "0" -t "$TGT"
|
||||
test_var window_panes "2" -t "$TGT"
|
||||
test_var session_windows "2" -t "$TGT"
|
||||
test_var pane_index "0" -t "$TGT"
|
||||
test_var pane_in_mode "0" -t "$TGT"
|
||||
test_var pane_at_top "1" -t "$TGT"
|
||||
test_var pane_at_left "1" -t "$TGT"
|
||||
test_var last_window_index "1" -t "$TGT"
|
||||
test_var pid "$($TMUX display-message -p '#{pid}')" -t "$TGT"
|
||||
|
||||
# The grouped session is reported as such.
|
||||
test_var session_grouped "1" -t "cov:"
|
||||
test_var session_group_size "2" -t "cov:"
|
||||
|
||||
# list-buffers -F formats each paste buffer (this fills in the paste-buffer
|
||||
# format defaults).
|
||||
if [ "$($TMUX list-buffers -F '#{buffer_name}=#{buffer_sample}')" != \
|
||||
"buf0=somebuffer" ]; then
|
||||
fail "Unexpected list-buffers format output."
|
||||
fi
|
||||
|
||||
# Version reported by the variable matches tmux -V.
|
||||
VER=$($TMUX -V | sed 's/^tmux //')
|
||||
test_var version "$VER" -t "$TGT"
|
||||
|
||||
# Client variables from each kind of client.
|
||||
test_var client_name "$CLIENT" -c "$CLIENT"
|
||||
test_var client_control_mode "1" -c "$CLIENT"
|
||||
test_var client_control_mode "0" -c "$RC"
|
||||
test_var socket_path "$($TMUX display-message -p '#{socket_path}')" -c "$CLIENT"
|
||||
# The real client has a terminal, so termcap/feature/environ queries work.
|
||||
test_var "I/e:TERM" "$($TMUX display-message -c "$RC" -p '#{client_termname}')" \
|
||||
-c "$RC"
|
||||
# Termcap and feature queries against a real terminal return a boolean.
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/c:colors}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/c:colors} for real client." ;;
|
||||
esac
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/f:256}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/f:256} for real client." ;;
|
||||
esac
|
||||
|
||||
# Time variables through the pretty and relative modifiers: start_time is the
|
||||
# recent server start, exercising the "last 24 hours" and "just now" paths.
|
||||
[ -n "$($TMUX display-message -p '#{t/p:start_time}')" ] ||
|
||||
fail "Empty #{t/p:start_time}."
|
||||
[ -n "$($TMUX display-message -p '#{t/r:start_time}')" ] ||
|
||||
fail "Empty #{t/r:start_time}."
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
Reference in New Issue
Block a user