From f645f0abe8ba3d7ca3bc0226c1b6f08910fb6cfb Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 3 Jul 2026 07:13:20 +0100 Subject: [PATCH] A few more tests. --- regress/buffers.sh | 287 +++++++++++++++++++++ regress/pane-ops.sh | 562 +++++++++++++++++++++++++++++++++++++++++ regress/session-ops.sh | 214 ++++++++++++++++ regress/window-ops.sh | 403 +++++++++++++++++++++++++++++ 4 files changed, 1466 insertions(+) create mode 100644 regress/buffers.sh create mode 100644 regress/pane-ops.sh create mode 100644 regress/session-ops.sh create mode 100644 regress/window-ops.sh diff --git a/regress/buffers.sh b/regress/buffers.sh new file mode 100644 index 000000000..0b3267610 --- /dev/null +++ b/regress/buffers.sh @@ -0,0 +1,287 @@ +#!/bin/sh + +# Tests of paste buffer command semantics, as implemented in cmd-set-buffer.c +# (set-buffer and delete-buffer), cmd-paste-buffer.c, cmd-load-buffer.c, +# cmd-save-buffer.c (save-buffer and show-buffer), cmd-list-buffers.c and +# paste.c. +# +# This exercises: +# - set-buffer creating automatic buffers (buffer0, buffer1, ... with the +# most recent first), -b creating/replacing a named buffer, -a appending, +# -n renaming and the error paths (no data, unknown buffer); +# - show-buffer for the top and for named buffers; +# - delete-buffer for the top and named buffers, and when nothing exists; +# - list-buffers -F custom formats and -f filters; +# - paste-buffer into a pane: newline-to-CR translation by default, -r raw, +# -s custom separator, -d delete-after-paste, unknown buffer error; +# - the buffer-limit option evicting the oldest automatic buffers but not +# named buffers; +# - load-buffer/save-buffer round trips including control characters and +# UTF-8, save-buffer -a appending and errors for missing files/buffers. + +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 + +TMP=$(mktemp) +TMP2=$(mktemp) +trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15 + +# 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 +} + +# check_buffers $expected +# +# Compare the buffer list (as "name=content ...", most recent first) with +# $expected. +check_buffers() +{ + out=$(echo $($TMUX list-buffers -F \ + '#{buffer_name}=#{buffer_sample}')) + if [ "$out" != "$1" ]; then + echo "Buffer list wrong." + echo "Expected: '$1'" + echo "But got: '$out'" + exit 1 + fi +} + +# check_show $args $expected +# +# Compare show-buffer output with $expected. +check_show() +{ + out=$($TMUX show-buffer $1 2>&1) + if [ "$out" != "$2" ]; then + echo "show-buffer $1 wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +assert_alive() +{ + if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then + echo "Server died: $1" + exit 1 + fi +} + +check_ok new-session -d -s B -x 80 -y 24 + +# --------------------------------------------------------------------------- +# set-buffer, show-buffer, delete-buffer, list-buffers. + +# Automatic buffers stack with the most recent first. +check_ok set-buffer one +check_ok set-buffer two +check_buffers 'buffer1=two buffer0=one' +check_show '' 'two' +check_show '-b buffer0' 'one' + +# -a only appends to a buffer named with -b: without -b it creates a new +# automatic buffer. Empty data is silently ignored. +check_ok set-buffer -a '!' +check_buffers 'buffer2=! buffer1=two buffer0=one' +check_ok delete-buffer +check_ok set-buffer '' +check_buffers 'buffer1=two buffer0=one' + +# -b names a buffer explicitly; setting it again replaces the content; +# -a appends to it. +check_ok set-buffer -b named abc +check_buffers 'named=abc buffer1=two buffer0=one' +check_ok set-buffer -b named xyz +check_ok set-buffer -a -b named 123 +check_buffers 'named=xyz123 buffer1=two buffer0=one' +check_show '-b named' 'xyz123' + +# -n renames; renaming to a bad source is an error, as is no data at all. +check_ok set-buffer -b named -n other +check_buffers 'other=xyz123 buffer1=two buffer0=one' +check_fail 'unknown buffer: nosuch' set-buffer -b nosuch -n foo +check_fail 'no data specified' set-buffer -b other +check_fail 'no buffer nosuch' show-buffer -b nosuch + +# list-buffers -f filters. +out=$($TMUX list-buffers -f '#{==:#{buffer_name},other}' -F '#{buffer_name}') +if [ "$out" != "other" ]; then + echo "list-buffers -f wrong: '$out'" + exit 1 +fi + +# delete-buffer -b removes one buffer; without -b the most recent automatic +# buffer goes - named buffers are not candidates for the top, for +# show-buffer and delete-buffer alike. +check_ok delete-buffer -b buffer1 +check_buffers 'other=xyz123 buffer0=one' +check_fail 'unknown buffer: buffer1' delete-buffer -b buffer1 +check_ok delete-buffer +check_buffers 'other=xyz123' +check_fail 'no buffers' show-buffer +check_fail 'no buffer' delete-buffer +check_ok delete-buffer -b other +check_fail 'no buffers' show-buffer + +# --------------------------------------------------------------------------- +# buffer-limit. + +# Only automatic buffers count against buffer-limit and the oldest are +# evicted; named buffers survive. (Automatic buffer numbers keep counting +# up over the life of the server, so compare content only.) +check_ok set-option -g buffer-limit 3 +check_ok set-buffer -b keepme precious +check_ok set-buffer a1 +check_ok set-buffer a2 +check_ok set-buffer a3 +check_ok set-buffer a4 +out=$(echo $($TMUX list-buffers -F '#{buffer_sample}')) +if [ "$out" != 'a4 a3 a2 precious' ]; then + echo "buffer-limit eviction wrong: '$out'" + exit 1 +fi +check_ok set-option -g buffer-limit 50 +check_ok delete-buffer -b keepme +check_ok delete-buffer; check_ok delete-buffer; check_ok delete-buffer + +# --------------------------------------------------------------------------- +# paste-buffer. + +# Paste into a raw, echo-free pane running cat -v so control characters are +# visible; a fresh window per paste keeps assertions simple. + +# paste_line $bufdata $pasteargs $expected +# +# Set a buffer, paste it into a fresh cat -v pane and compare the first +# screen line with $expected. +paste_line() +{ + $TMUX kill-window -t B:9 2>/dev/null + check_ok new-window -d -t B:9 'stty raw -echo && exec cat -v' + i=0 + while [ "$($TMUX display-message -p -t B:9.0 \ + '#{pane_current_command}')" != "cat" ]; do + i=$((i + 1)) + [ $i -gt 50 ] && { echo "cat did not start."; exit 1; } + sleep 0.1 + done + check_ok set-buffer -b paste "$1" + check_ok paste-buffer $2 -b paste -t B:9.0 + i=0 + while out=$($TMUX capture-pane -p -t B:9.0 | sed -n 1p) && \ + [ "$out" != "$3" ]; do + i=$((i + 1)) + if [ $i -gt 50 ]; then + echo "Paste of '$1' ($2) wrong." + echo "Expected: '$3'" + echo "But got: '$out'" + exit 1 + fi + sleep 0.1 + done +} + +# By default linefeeds are replaced with carriage returns (shown as ^M by +# cat -v); -r pastes raw and -s sets an explicit separator. +paste_line 'one +two' '' 'one^Mtwo' +paste_line 'one +two' '-r' 'one' +paste_line 'one +two' '-s |' 'one|two' +paste_line 'one +two' '-s XX' 'oneXXtwo' + +# -d deletes the buffer after pasting. +paste_line 'gone' '-d' 'gone' +check_fail 'no buffer paste' show-buffer -b paste + +# Unknown buffer is an error. +check_fail 'no buffer nosuch' paste-buffer -b nosuch -t B:9.0 +check_ok kill-window -t B:9 + +# --------------------------------------------------------------------------- +# load-buffer and save-buffer. + +# Round trip a file with control characters and UTF-8 through load-buffer +# and save-buffer. +printf 'line1\tx\033[31m\001\002\303\251\n' >"$TMP" +check_ok load-buffer -b file "$TMP" +check_ok save-buffer -b file "$TMP2" +if ! cmp -s "$TMP" "$TMP2"; then + echo "load-buffer/save-buffer round trip differs." + exit 1 +fi + +# save-buffer -a appends. +check_ok save-buffer -a -b file "$TMP2" +cat "$TMP" "$TMP" >"$TMP".x +if ! cmp -s "$TMP".x "$TMP2"; then + rm -f "$TMP".x + echo "save-buffer -a did not append." + exit 1 +fi +rm -f "$TMP".x + +# show-buffer prints the loaded content (text form). +check_ok delete-buffer -b file + +# load-buffer of a missing file and save-buffer of a missing buffer or to a +# bad path are errors. +check_fail "No such file or directory: $TMP.nosuch" \ + load-buffer -b x "$TMP.nosuch" +check_fail 'no buffer nosuch' save-buffer -b nosuch "$TMP2" +check_ok set-buffer -b sb data +out=$($TMUX save-buffer -b sb /nonexistent/dir/file 2>&1) +if [ $? -eq 0 ]; then + echo "save-buffer to bad path succeeded." + exit 1 +fi + +# save-buffer - writes to stdout and load-buffer - reads from stdin. +out=$($TMUX save-buffer -b sb -) +if [ "$out" != "data" ]; then + echo "save-buffer - wrong: '$out'" + exit 1 +fi +check_ok delete-buffer -b sb +printf 'from stdin' | $TMUX load-buffer -b stdinbuf - +check_show '-b stdinbuf' 'from stdin' +check_ok delete-buffer -b stdinbuf + +assert_alive + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/pane-ops.sh b/regress/pane-ops.sh new file mode 100644 index 000000000..ee0894830 --- /dev/null +++ b/regress/pane-ops.sh @@ -0,0 +1,562 @@ +#!/bin/sh + +# Tests of pane management command semantics (not parsing), as implemented in +# cmd-split-window.c, cmd-break-pane.c, cmd-join-pane.c (join-pane and +# move-pane), cmd-swap-pane.c, cmd-kill-pane.c, cmd-respawn-pane.c, +# cmd-respawn-window.c, cmd-resize-pane.c and cmd-select-pane.c. +# +# This exercises: +# - split-window -h/-v with -l in cells and percent, -b placing the new pane +# before (left/top of) the target and -f spanning the full window size; +# - break-pane moving a pane into a new window (-d, -n name, -a after, -P -F +# printing the new location); +# - join-pane moving a window's only pane into another window (destroying the +# source window), -b before, -l size, and the identical-panes error; +# - move-pane as an alias for join-pane; +# - swap-pane -U/-D/-s/-t, -d keeping the active pane, and the marked pane +# (select-pane -m/-M) as the default swap source; +# - kill-pane, kill-pane -a keeping only the target; +# - respawn-pane/respawn-window refusing a live pane without -k, working on a +# dead pane (remain-on-exit) and killing with -k; +# - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z +# zoom/unzoom (including implicit unzoom on split). +# +# window-ops.sh covers window-level commands and buffers.sh paste buffers. + +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_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 +} + +# check_fmt $target $format $expected +# +# Expand a format in a target's context and compare with $expected. +check_fmt() +{ + out=$($TMUX display-message -p -t "$1" "$2" 2>&1) + if [ "$out" != "$3" ]; then + echo "Format '$2' for '$1' wrong." + echo "Expected: '$3'" + echo "But got: '$out'" + exit 1 + fi +} + +# check_panes $target $expected +# +# Compare the pane list of a window (as "index:id ...", in index order) with +# $expected. +check_panes() +{ + out=$(echo $($TMUX list-panes -t "$1" -F '#{pane_index}:#{pane_id}')) + if [ "$out" != "$2" ]; then + echo "Pane list of '$1' wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +assert_alive() +{ + if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then + echo "Server died: $1" + exit 1 + fi +} + +# --------------------------------------------------------------------------- +# split-window geometry. + +check_ok new-session -d -s P -x 80 -y 24 -n main +p0=$($TMUX display-message -p -t P:0.0 '#{pane_id}') + +# Horizontal split with -l in cells: new pane gets exactly that width and the +# old pane the rest minus the separator line. +check_ok split-window -d -h -l 20 -t "$p0" +p1=$($TMUX display-message -p -t P:0.1 '#{pane_id}') +check_fmt "$p1" '#{pane_width}x#{pane_height}' '20x24' +check_fmt "$p0" '#{pane_width}x#{pane_height}' '59x24' + +# Vertical split with a percentage of the pane being split. +check_ok split-window -d -v -l 25% -t "$p0" +p2=$($TMUX display-message -p -t P:0.1 '#{pane_id}') +check_fmt "$p2" '#{pane_width}x#{pane_height}' '59x6' +check_fmt "$p0" '#{pane_width}x#{pane_height}' '59x17' + +# -b puts the new pane to the left of the target; -f makes it span the full +# window height. +check_ok split-window -d -h -b -f -l 10 -t "$p0" +p3=$($TMUX display-message -p -t P:0.0 '#{pane_id}') +check_fmt "$p3" '#{pane_width}x#{pane_height}' '10x24' +check_fmt "$p3" '#{pane_left},#{pane_top}' '0,0' +check_fmt "$p0" '#{pane_width}x#{pane_height}' '50x17' +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# The new pane becomes active unless -d is given. +check_ok select-pane -t "$p0" +check_ok split-window -d -v -t "$p0" +check_fmt 'P:0' '#{pane_id}' "$p0" +p4=$($TMUX display-message -p -t P:0.2 '#{pane_id}') +check_ok split-window -v -t "$p4" +p5=$($TMUX display-message -p -t 'P:0' '#{pane_id}') +check_ok kill-pane -t "$p5" +check_ok kill-pane -t "$p4" +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# --------------------------------------------------------------------------- +# break-pane and join-pane. + +# break-pane moves a pane to a new window; -P -F prints where it went and -n +# names the new window. +out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \ + -s "$p1" -t P:) +if [ "$out" != "1:$p1" ]; then + echo "break-pane -P output wrong: '$out'" + exit 1 +fi +check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1' +check_fmt 'P:0' '#{window_panes}' '3' + +# join-pane -v moves it back (the source window, left empty, is destroyed). +check_ok join-pane -d -v -s P:broken.0 -t "$p2" +check_fmt 'P:0' '#{window_panes}' '4' +if $TMUX has-session -t P:broken 2>/dev/null; then + echo "Window 'broken' still exists after join-pane." + exit 1 +fi + +# The joined pane is below the target (-v, no -b). +top=$($TMUX display-message -p -t "$p2" '#{pane_bottom}') +joined=$($TMUX display-message -p -t "$p1" '#{pane_top}') +if [ "$joined" -le "$top" ]; then + echo "Joined pane is not below target ($joined <= $top)." + exit 1 +fi + +# join-pane -h -b puts the source to the left of the target; -l sets size. +check_ok break-pane -d -n broken -s "$p1" -t P: +check_ok join-pane -d -h -b -l 30 -s P:broken.0 -t "$p2" +check_fmt "$p1" '#{pane_width}' '30' +l1=$($TMUX display-message -p -t "$p1" '#{pane_left}') +l2=$($TMUX display-message -p -t "$p2" '#{pane_left}') +if [ "$l1" -ge "$l2" ]; then + echo "Joined pane is not left of target ($l1 >= $l2)." + exit 1 +fi + +# Joining a pane to itself is an error. +check_fail 'source and target panes must be different' \ + join-pane -d -s "$p0" -t "$p0" + +# break-pane to an occupied window index or with an invalid (non-UTF-8) name +# is an error. +check_fail 'index in use: 0' break-pane -d -s "$p1" -t P:0 +check_fail "invalid window name: $(printf 'a\377b')" \ + break-pane -d -n "$(printf 'a\377b')" -s "$p1" -t P: + +# join-pane can move a pane from one window to another without destroying +# the source window if other panes remain. (On this branch move-pane is +# reserved for floating panes, covered by floating-pane-geometry.sh.) +check_ok new-window -d -t P:5 -n other +check_ok join-pane -d -s "$p1" -t P:5.0 +check_fmt 'P:5' '#{window_panes}' '2' +check_fmt 'P:0' '#{window_panes}' '3' +check_ok join-pane -d -v -s "$p1" -t "$p2" +check_fmt 'P:0' '#{window_panes}' '4' +check_fmt 'P:5' '#{window_panes}' '1' + +# --------------------------------------------------------------------------- +# swap-pane. + +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# -s/-t swap two panes. +check_ok swap-pane -d -s "$p3" -t "$p1" +check_panes P:0 "0:$p1 1:$p0 2:$p2 3:$p3" +check_ok swap-pane -d -s "$p3" -t "$p1" +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# -U swaps the target pane with the previous pane, -D with the next; without +# -s the target is the active pane. +check_ok swap-pane -d -U -t "$p0" +check_panes P:0 "0:$p0 1:$p3 2:$p2 3:$p1" +check_ok swap-pane -d -D -t "$p0" +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# Without -d the target pane becomes the active pane (it arrives at the +# source pane's position). +check_ok select-pane -t "$p0" +check_ok swap-pane -s "$p0" -t "$p2" +check_fmt 'P:0' '#{pane_id}' "$p2" +check_panes P:0 "0:$p3 1:$p2 2:$p0 3:$p1" +check_ok swap-pane -s "$p2" -t "$p0" +check_fmt 'P:0' '#{pane_id}' "$p0" +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# With a marked pane and no -s, the marked pane is the swap source. +check_ok select-pane -m -t "$p3" +check_fmt "$p3" '#{pane_marked}' '1' +check_ok swap-pane -d -t "$p1" +check_panes P:0 "0:$p1 1:$p0 2:$p2 3:$p3" +check_ok swap-pane -d -t "$p1" +check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1" + +# select-pane -M clears the mark. +check_ok select-pane -M +check_fmt "$p3" '#{pane_marked}' '0' +check_fmt 'P:0' '#{pane_marked_set}' '0' + +# --------------------------------------------------------------------------- +# resize-pane and zoom. + +# Absolute -x on a horizontal split and percentage. +check_ok resize-pane -t "$p3" -x 20 +check_fmt "$p3" '#{pane_width}' '20' +check_ok resize-pane -t "$p3" -x 25% +check_fmt "$p3" '#{pane_width}' '20' +check_ok resize-pane -t "$p3" -x 10 +check_fmt "$p3" '#{pane_width}' '10' + +# Relative adjustments: -R grows a left pane, -L shrinks it back; a count may +# be given. +check_ok resize-pane -t "$p3" -R +check_fmt "$p3" '#{pane_width}' '11' +check_ok resize-pane -t "$p3" -L +check_fmt "$p3" '#{pane_width}' '10' +check_ok resize-pane -t "$p3" -R 5 +check_fmt "$p3" '#{pane_width}' '15' +check_ok resize-pane -t "$p3" -L 5 +check_fmt "$p3" '#{pane_width}' '10' + +# -y on a vertical split. +check_ok resize-pane -t "$p2" -y 10 +check_fmt "$p2" '#{pane_height}' '10' + +# p2 is the bottom pane, so its bottom border cannot move down: -D instead +# grows it by taking lines from the pane above and -U gives them back. +check_ok resize-pane -t "$p2" -D 2 +check_fmt "$p2" '#{pane_height}' '12' +check_ok resize-pane -t "$p2" -U 2 +check_fmt "$p2" '#{pane_height}' '10' + +# Bad adjustment, width and height are errors. +check_fail 'adjustment invalid' resize-pane -t "$p2" -U nonsense +check_fail 'width invalid' resize-pane -t "$p2" -x nonsense +check_fail 'height invalid' resize-pane -t "$p2" -y nonsense + +# -Z zooms: the pane temporarily fills the window and the flags show it. +check_ok resize-pane -Z -t "$p0" +check_fmt "$p0" '#{window_zoomed_flag}:#{pane_width}x#{pane_height}' \ + '1:80x24' + +# Zoom is transparent to pane commands on other panes, and -Z again unzooms. +check_ok resize-pane -Z -t "$p0" +check_fmt "$p0" '#{window_zoomed_flag}' '0' + +# Splitting while zoomed unzooms first. +check_ok resize-pane -Z -t "$p0" +check_fmt 'P:0' '#{window_zoomed_flag}' '1' +check_ok split-window -d -v -t "$p0" +check_fmt 'P:0' '#{window_zoomed_flag}' '0' +check_fmt 'P:0' '#{window_panes}' '5' +p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}') +check_ok kill-pane -t "$p6" + +# --------------------------------------------------------------------------- +# kill-pane. + +check_fmt 'P:0' '#{window_panes}' '4' +check_ok kill-pane -t "$p3" +check_panes P:0 "0:$p0 1:$p2 2:$p1" + +# -a kills every pane except the target. +check_ok kill-pane -a -t "$p0" +check_panes P:0 "0:$p0" + +# Killing the last pane in a window kills the window. +check_ok new-window -d -t P:7 -n goner +check_ok kill-pane -t P:7.0 +if $TMUX has-session -t P:goner 2>/dev/null; then + echo "Window 'goner' still exists after killing its only pane." + exit 1 +fi + +# --------------------------------------------------------------------------- +# respawn-pane and respawn-window. + +# Respawning a pane whose process is alive fails without -k. +check_fail "respawn pane failed: pane P:0.0 still active" \ + respawn-pane -t P:0.0 +check_fail "respawn window failed: window P:0 still active" \ + respawn-window -t P:0 + +# With remain-on-exit a pane whose command exited stays as a dead pane and +# may be respawned without -k. +check_ok set-option -g remain-on-exit on +check_ok new-window -d -t P:8 -n dead 'true' +i=0 +while [ "$($TMUX display-message -p -t P:8.0 '#{pane_dead}')" != "1" ]; do + i=$((i + 1)) + [ $i -gt 50 ] && { echo "Pane did not die."; exit 1; } + sleep 0.1 +done +check_ok respawn-pane -t P:8.0 'sleep 100' +check_fmt 'P:8.0' '#{pane_dead}' '0' + +# -k kills the live process and respawns. +check_ok respawn-pane -k -t P:8.0 'sleep 200' +check_fmt 'P:8.0' '#{pane_dead}' '0' + +# respawn-window -k replaces the whole window (all panes) with one pane. +check_ok split-window -d -t P:8 +check_fmt 'P:8' '#{window_panes}' '2' +check_ok respawn-window -k -t P:8 'sleep 300' +check_fmt 'P:8' '#{window_panes}' '1' +check_fmt 'P:8.0' '#{pane_dead}' '0' +check_ok set-option -g remain-on-exit off + +# --------------------------------------------------------------------------- +# select-pane. + +# A 2x2-ish arrangement: q0 on top, q1 bottom-left, q2 bottom-right. +check_ok new-window -d -t P:2 -n sel +q0=$($TMUX display-message -p -t P:2.0 '#{pane_id}') +check_ok split-window -d -v -t "$q0" +q1=$($TMUX display-message -p -t P:2.1 '#{pane_id}') +check_ok split-window -d -h -t "$q1" +q2=$($TMUX display-message -p -t P:2.2 '#{pane_id}') + +# Directional selection: -D, -R and -U move by pane position. +check_ok select-pane -t "$q0" +check_ok select-pane -D -t P:2 +check_fmt 'P:2' '#{pane_id}' "$q1" +check_ok select-pane -R -t P:2 +check_fmt 'P:2' '#{pane_id}' "$q2" +check_ok select-pane -U -t P:2 +check_fmt 'P:2' '#{pane_id}' "$q0" + +# -l returns to the previously active pane; a window that never had another +# active pane has no last pane. +check_ok select-pane -l -t P:2 +check_fmt 'P:2' '#{pane_id}' "$q2" +check_fail 'no last pane' select-pane -l -t P:8.0 + +# -d disables input to a pane, -e enables it again and -T sets the title. +check_ok select-pane -d -t "$q0" +check_fmt "$q0" '#{pane_input_off}' '1' +check_ok select-pane -e -t "$q0" +check_fmt "$q0" '#{pane_input_off}' '0' +check_ok select-pane -T mytitle -t "$q0" +check_fmt "$q0" '#{pane_title}' 'mytitle' +check_ok kill-window -t P:2 + +# --------------------------------------------------------------------------- +# more split-window variants. + +check_ok new-window -d -t P:2 -n splits + +# -E splits with an empty pane, running no command; giving one is an error. +check_ok split-window -d -E -t P:2.0 +check_fmt 'P:2' '#{window_panes}' '2' +check_fail 'command cannot be given for empty pane' \ + split-window -d -E -t P:2.0 'sleep 5' + +# -e adds to the new pane's environment. +eid=$($TMUX split-window -d -P -F '#{pane_id}' -e GREETING=hello -t P:2.0 \ + 'echo $GREETING; exec cat') +i=0 +while out=$($TMUX capture-pane -p -t "$eid" | sed -n 1p) && \ + [ "$out" != "hello" ]; do + i=$((i + 1)) + [ $i -gt 50 ] && { echo "split-window -e wrong: '$out'"; exit 1; } + sleep 0.1 +done + +# A bad -l size is an error. +check_fail 'invalid tiled geometry invalid' \ + split-window -d -v -l invalid -t P:2.0 + +# -Z zooms the new pane. +check_ok split-window -d -Z -t P:2.0 +check_fmt 'P:2' '#{window_zoomed_flag}' '1' +check_ok kill-window -t P:2 + +# --------------------------------------------------------------------------- +# more swap-pane: wrapping, cross-window, self and zoomed swaps. + +check_ok new-window -d -t P:3 -n swaps +check_ok split-window -d -v -t P:3.0 +check_ok split-window -d -v -t P:3.0 +r0=$($TMUX display-message -p -t P:3.0 '#{pane_id}') +r1=$($TMUX display-message -p -t P:3.1 '#{pane_id}') +r2=$($TMUX display-message -p -t P:3.2 '#{pane_id}') +o0=$($TMUX display-message -p -t P:5.0 '#{pane_id}') + +# -D on the last pane and -U on the first wrap around to the other end. +check_ok swap-pane -d -D -t "$r2" +check_panes P:3 "0:$r2 1:$r1 2:$r0" +check_ok swap-pane -d -s "$r0" -t "$r2" +check_ok swap-pane -d -U -t "$r0" +check_panes P:3 "0:$r2 1:$r1 2:$r0" +check_ok swap-pane -d -s "$r0" -t "$r2" +check_panes P:3 "0:$r0 1:$r1 2:$r2" + +# Swapping a pane with itself quietly does nothing. +check_ok swap-pane -d -s "$r1" -t "$r1" +check_panes P:3 "0:$r0 1:$r1 2:$r2" + +# Panes can be swapped between different windows. +check_ok swap-pane -d -s "$o0" -t "$r1" +check_panes P:3 "0:$r0 1:$o0 2:$r2" +check_panes P:5 "0:$r1" +check_ok swap-pane -d -s "$r1" -t "$o0" +check_panes P:3 "0:$r0 1:$r1 2:$r2" +check_panes P:5 "0:$o0" + +# -Z keeps the window zoomed across the swap. +check_ok resize-pane -Z -t "$r0" +check_ok swap-pane -d -Z -s "$r0" -t "$r1" +check_fmt 'P:3' '#{window_zoomed_flag}' '1' +check_ok resize-pane -Z -t P:3 +check_panes P:3 "0:$r1 1:$r0 2:$r2" + +# kill-pane -a -f only kills other panes matching the filter. +check_ok kill-pane -a -f '#{==:#{pane_id},'"$r2"'}' -t "$r1" +check_panes P:3 "0:$r1 1:$r0" +check_ok kill-window -t P:3 + +# --------------------------------------------------------------------------- +# split-window -I and -s. + +check_ok new-window -d -t P:3 -n splits2 + +# -I fills the new (empty) pane from standard input. +printf 'stdin-stuff' | $TMUX split-window -d -I -t P:3.0 +if [ $? -ne 0 ]; then + echo "split-window -I failed." + exit 1 +fi +i=0 +while out=$($TMUX capture-pane -p -t P:3.1 | sed -n 1p) && \ + [ "$out" != "stdin-stuff" ]; do + i=$((i + 1)) + [ $i -gt 50 ] && { echo "split-window -I wrong: '$out'"; exit 1; } + sleep 0.1 +done + +# -s sets the new pane's window-style. +sid=$($TMUX split-window -d -P -F '#{pane_id}' -s 'bg=red' -t P:3.0) +out=$($TMUX show-options -v -p -t "$sid" window-style) +if [ "$out" != "bg=red" ]; then + echo "split-window -s style wrong: '$out'" + exit 1 +fi +check_ok kill-window -t P:3 + +# --------------------------------------------------------------------------- +# more break-pane: -a insertion, selection and single-pane windows. + +# check_windows $session $expected +# +# Compare the window list of a session (as "index:name ...") with $expected. +check_windows() +{ + out=$(echo $($TMUX list-windows -t "$1" -F \ + '#{window_index}:#{window_name}')) + if [ "$out" != "$2" ]; then + echo "Window list of '$1' wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +check_ok new-session -d -s Q -x 80 -y 24 -n q0 + +# -a breaks into a new window inserted after the target index, shuffling the +# following windows up. +check_ok new-window -d -t Q:1 -n q1 +check_ok split-window -d -t Q:1 +check_ok break-pane -d -a -s Q:1.1 -n qa -t Q:0 +check_windows Q '0:q0 1:qa 2:q1' + +# Without -d the new window is selected. +check_ok split-window -d -t Q:2 +check_ok select-window -t Q:0 +check_ok break-pane -s Q:2.1 -n qcur -t Q: +check_fmt 'Q:' '#{window_name}' 'qcur' +check_windows Q '0:q0 1:qa 2:q1 3:qcur' + +# Breaking the only pane of a window relinks the window at a new index; -n +# still renames it. +check_ok new-window -d -t Q:5 -n qsolo +out=$($TMUX break-pane -d -P -F '#{window_name}' -s Q:5.0 -n qmoved -t Q:) +if [ "$out" != "qmoved" ]; then + echo "single-pane break-pane output wrong: '$out'" + exit 1 +fi +if $TMUX has-session -t Q:qsolo 2>/dev/null; then + echo "Window 'qsolo' still exists after single-pane break-pane." + exit 1 +fi +check_ok has-session -t Q:qmoved +check_ok kill-session -t Q + +# --------------------------------------------------------------------------- +# resize-pane -T. + +# -T trims the history: lines below the cursor position are removed and the +# cursor moves to the bottom. seq writes 100 lines (leaving 77 in history on +# a 24-line screen) and the escape sequence puts the cursor on line 5. +check_ok new-window -d -t P:2 'seq 1 100; printf "\033[5;1H"; exec cat' +i=0 +while [ "$($TMUX display-message -p -t P:2.0 '#{history_size}')" != "77" ] +do + i=$((i + 1)) + [ $i -gt 50 ] && { echo "History did not fill."; exit 1; } + sleep 0.1 +done +check_fmt 'P:2.0' '#{cursor_y}' '4' +check_ok resize-pane -T -t P:2.0 +check_fmt 'P:2.0' '#{history_size}' '58' +check_fmt 'P:2.0' '#{cursor_y}' '23' +check_ok kill-window -t P:2 + +assert_alive + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/session-ops.sh b/regress/session-ops.sh new file mode 100644 index 000000000..2da14b959 --- /dev/null +++ b/regress/session-ops.sh @@ -0,0 +1,214 @@ +#!/bin/sh + +# Tests of session management command semantics, as implemented in +# cmd-new-session.c, cmd-rename-session.c, cmd-kill-session.c and +# cmd-has-session.c, plus grouped sessions (new-session -t). +# +# This exercises: +# - new-session naming: explicit -s, invalid and duplicate names, automatic +# numeric names, -n naming the initial window and -A attaching to (here: +# not duplicating) an existing session; +# - session_id/session_name/session_windows formats and has-session; +# - rename-session, including duplicate and invalid names, and that the +# session keeps its id when renamed; +# - kill-session, kill-session -a (all but target), the "-f only valid with +# -a" guard, and that killing the last session stops the server; +# - grouped sessions: new-session -t shares the window list (a window made +# in one session appears in the other; killed windows disappear), current +# windows are tracked independently and destroying one grouped session +# leaves the windows in the other. +# +# session-group-resize.sh covers sizing of grouped sessions. + +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_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 +} + +# check_fmt $target $format $expected +# +# Expand a format in a target's context and compare with $expected. +check_fmt() +{ + out=$($TMUX display-message -p -t "$1" "$2" 2>&1) + if [ "$out" != "$3" ]; then + echo "Format '$2' for '$1' wrong." + echo "Expected: '$3'" + echo "But got: '$out'" + exit 1 + fi +} + +# check_sessions $expected +# +# Compare the session list (as "name name ...", sorted by name) with +# $expected. +check_sessions() +{ + out=$(echo $($TMUX list-sessions -F '#{session_name}' | LC_ALL=C sort)) + if [ "$out" != "$1" ]; then + echo "Session list wrong." + echo "Expected: '$1'" + echo "But got: '$out'" + exit 1 + fi +} + +# check_windows $session $expected +# +# Compare the window list of a session (as "index:name ...") with $expected. +check_windows() +{ + out=$(echo $($TMUX list-windows -t "$1" -F \ + '#{window_index}:#{window_name}')) + if [ "$out" != "$2" ]; then + echo "Window list of '$1' wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +# --------------------------------------------------------------------------- +# new-session and has-session. + +check_ok new-session -d -s S1 -x 80 -y 24 -n first +check_fmt 'S1:' '#{session_name}:#{window_name}:#{session_windows}' \ + 'S1:first:1' + +# A duplicate name is an error. Only invalid UTF-8 is rejected as a name: +# colons, periods and even an empty string are allowed (such sessions can +# only be targeted by id). +check_fail 'duplicate session: S1' new-session -d -s S1 +badname=$(printf 'a\377b') +check_fail "invalid session name: $badname" new-session -d -s "$badname" +oddid=$($TMUX new-session -d -s 'a:b.c' -x 80 -y 24 -P -F '#{session_id}') +check_fmt "$oddid" '#{session_name}' 'a:b.c' +check_ok kill-session -t "$oddid" +emptyid=$($TMUX new-session -d -s '' -x 80 -y 24 -P -F '#{session_id}') +check_fmt "$emptyid" '#{session_name}' '' +check_ok kill-session -t "$emptyid" + +# Without -s, sessions get a numeric name matching their id counter. +autoname=$($TMUX new-session -d -x 80 -y 24 -P -F '#{session_name}') +autoid=$($TMUX display-message -p -t "=$autoname:" '#{session_id}') +if [ "\$$autoname" != "$autoid" ]; then + echo "Automatic session name '$autoname' does not match id '$autoid'." + exit 1 +fi +check_ok has-session -t "=$autoname" +check_ok rename-session -t "=$autoname" S2 +check_sessions 'S1 S2' + +# -A creates the session only if it does not exist; if it does, -A means +# attach, which a detached client without a terminal cannot do. +check_ok new-session -d -A -s S3 +check_sessions 'S1 S2 S3' +check_fail 'open terminal failed: not a terminal' new-session -d -A -s S3 +check_sessions 'S1 S2 S3' +check_ok kill-session -t S3 + +# has-session fails for a missing session. +check_fail "can't find session: nosuch" has-session -t nosuch + +# --------------------------------------------------------------------------- +# rename-session. + +# The id survives a rename and the old name is gone. +id=$($TMUX display-message -p -t S2: '#{session_id}') +check_ok rename-session -t S2 newname +check_sessions 'S1 newname' +check_fmt "$id" '#{session_name}' 'newname' +check_fail "can't find session: S2" has-session -t S2 + +# Renaming to an existing or invalid name is an error. +check_fail 'duplicate session: S1' rename-session -t newname S1 +check_fail "invalid session name: $badname" rename-session -t newname \ + "$badname" +check_ok rename-session -t newname S2 + +# --------------------------------------------------------------------------- +# grouped sessions (new-session -t). + +check_ok new-session -d -s G1 -x 80 -y 24 -n shared +check_ok new-session -d -s G2 -t G1 +check_fmt 'G1:' '#{session_grouped}:#{session_group_size}' '1:2' +check_fmt 'G2:' '#{session_grouped}:#{session_group_list}' '1:G1,G2' + +# The window list is shared: windows created or killed in one session +# appear and disappear in the other. +check_ok new-window -d -t G2: -n added +check_windows G1 '0:shared 1:added' +check_windows G2 '0:shared 1:added' +check_ok kill-window -t G1:added +check_windows G2 '0:shared' + +# The current window is tracked per session. +check_ok new-window -d -t G2:1 -n other +check_ok select-window -t G1:0 +check_ok select-window -t G2:1 +check_fmt 'G1:' '#{window_name}' 'shared' +check_fmt 'G2:' '#{window_name}' 'other' + +# Killing one grouped session leaves the windows in the other (the group +# itself survives with a single member). +check_ok kill-session -t G2 +check_windows G1 '0:shared 1:other' +check_fmt 'G1:' '#{session_grouped}:#{session_group_size}' '1:1' +check_ok kill-window -t G1:other + +# --------------------------------------------------------------------------- +# kill-session. + +check_sessions 'G1 S1 S2' +check_fail '-f only valid with -a' kill-session -f 'x' -t S1 + +# -C only clears alerts; the session survives. +check_ok kill-session -C -t S2 +check_ok has-session -t S2 + +# -a kills every other session. +check_ok kill-session -a -t S1 +check_sessions 'S1' + +# Killing the last session stops the server. +check_ok kill-session -t S1 +if $TMUX has-session -t S1 2>/dev/null; then + echo "Server still up after killing the last session." + exit 1 +fi + +exit 0 diff --git a/regress/window-ops.sh b/regress/window-ops.sh new file mode 100644 index 000000000..3dba68edd --- /dev/null +++ b/regress/window-ops.sh @@ -0,0 +1,403 @@ +#!/bin/sh + +# Tests of window management command semantics (not parsing), as implemented +# in cmd-new-window.c, cmd-move-window.c (move-window and link-window), +# cmd-unlink-window.c, cmd-swap-window.c, cmd-rotate-window.c, +# cmd-kill-window.c and cmd-select-window.c. +# +# This exercises: +# - new-window placement: next free index, explicit index, index in use with +# and without -k, -a (after) and -b (before) insertion with shuffling, and +# -S selecting an existing window by name instead of creating; +# - move-window to a free index, to an occupied index with and without -k, +# -a insertion and -r renumbering (including base-index); +# - renumber-windows closing gaps; +# - link-window sharing a window between two sessions (window_linked and +# window_linked_sessions), unlink-window removing one link and refusing to +# unlink the last link without -k; +# - swap-window within and between sessions, -d keeping the active window, +# and the grouped-sessions error; +# - rotate-window -U/-D rotating pane positions; +# - kill-window switching to the last (previously current) window, kill-window +# -a killing all other windows and the "-f only valid with -a" guard. +# +# pane-ops.sh covers pane-level commands and buffers.sh covers paste buffers. + +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_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 +} + +# check_windows $session $expected +# +# Compare the window list of a session (as "index:name index:name ...", in +# index order) with $expected. +check_windows() +{ + out=$(echo $($TMUX list-windows -t "$1" -F \ + '#{window_index}:#{window_name}')) + if [ "$out" != "$2" ]; then + echo "Window list of '$1' wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +# check_fmt $target $format $expected +# +# Expand a format in a target's context and compare with $expected. +check_fmt() +{ + out=$($TMUX display-message -p -t "$1" "$2" 2>&1) + if [ "$out" != "$3" ]; then + echo "Format '$2' for '$1' wrong." + echo "Expected: '$3'" + echo "But got: '$out'" + exit 1 + fi +} + +assert_alive() +{ + if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then + echo "Server died: $1" + exit 1 + fi +} + +# --------------------------------------------------------------------------- +# new-window placement. + +check_ok new-session -d -s W -x 80 -y 24 -n w0 + +# Next free index. +check_ok new-window -d -t W: -n w1 +check_ok new-window -d -t W: -n w2 +check_windows W '0:w0 1:w1 2:w2' + +# Explicit index, then the next new window fills the first free index, not +# one past the highest. +check_ok new-window -d -t W:9 -n w9 +check_ok new-window -d -t W: -n w3 +check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9' + +# Occupied index fails without -k and replaces with -k. +check_fail 'create window failed: index 9 in use' \ + new-window -d -t W:9 -n dup +check_ok new-window -d -k -t W:9 -n w9k +check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9k' + +# -a inserts after the target, shuffling the following windows up. +check_ok new-window -d -a -t W:1 -n wA +check_windows W '0:w0 1:w1 2:wA 3:w2 4:w3 9:w9k' + +# -b inserts before the target, shuffling the target and followers up. +check_ok new-window -d -b -t W:0 -n wB +check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k' + +# -S selects an existing window with the same name instead of creating (with +# -d it would not switch, so no -d here). +check_ok select-window -t W:0 +check_ok new-window -S -t W: -n w3 +check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k' +check_fmt 'W:' '#{window_index}:#{window_name}' '5:w3' + +# Clean up to a known arrangement. +check_ok kill-window -t W:wB +check_ok kill-window -t W:wA +check_ok kill-window -t W:w9k +check_ok move-window -r -t W: +check_windows W '0:w0 1:w1 2:w2 3:w3' + +# --------------------------------------------------------------------------- +# move-window. + +# To a free index. +check_ok move-window -d -s W:2 -t W:7 +check_windows W '0:w0 1:w1 3:w3 7:w2' + +# To an occupied index, without and with -k. +check_fail 'index in use: 7' move-window -d -s W:3 -t W:7 +check_ok move-window -d -k -s W:3 -t W:7 +check_windows W '0:w0 1:w1 7:w3' + +# -a inserts after the target and shuffles. +check_ok move-window -d -a -s W:7 -t W:0 +check_windows W '0:w0 1:w3 2:w1' + +# -r renumbers in order, respecting base-index. +check_ok move-window -d -s W:2 -t W:8 +check_ok set-option -t W base-index 5 +check_ok move-window -r -t W: +check_windows W '5:w0 6:w3 7:w1' +check_ok set-option -t W base-index 0 +check_ok move-window -r -t W: +check_windows W '0:w0 1:w3 2:w1' + +# With the renumber-windows option on, killing a window renumbers the rest +# automatically. +check_ok set-option -t W renumber-windows on +check_ok new-window -d -t W:9 -n wtmp +check_windows W '0:w0 1:w3 2:w1 9:wtmp' +check_ok kill-window -t W:1 +check_windows W '0:w0 1:w1 2:wtmp' +check_ok kill-window -t W:2 +check_ok set-option -t W renumber-windows off +check_ok new-window -d -t W:2 -n w3 +check_windows W '0:w0 1:w1 2:w3' + +# Without -s, the current window of the client/session moves. +check_ok select-window -t W:2 +check_ok move-window -d -t W:6 +check_windows W '0:w0 1:w1 6:w3' +check_ok move-window -r -t W: +check_windows W '0:w0 1:w1 2:w3' + +# --------------------------------------------------------------------------- +# link-window and unlink-window. + +check_ok new-session -d -s L -x 80 -y 24 -n l0 + +# Link a window from W into L and check it is shared. +check_ok link-window -d -s W:w1 -t L:5 +check_windows L '0:l0 5:w1' +check_fmt 'W:w1' '#{window_linked}' '1' +check_fmt 'L:5' '#{window_linked_sessions}' '2' + +# The linked window is the same window: renaming in one session shows in the +# other. +check_ok rename-window -t L:5 shared +check_windows W '0:w0 1:shared 2:w3' +check_ok rename-window -t W:1 w1 + +# Linking again to an occupied index fails without -k. +check_fail 'index in use: 0' link-window -d -s W:w3 -t L:0 + +# Unlink removes one link; the window survives in the other session. +check_ok unlink-window -t L:5 +check_windows L '0:l0' +check_windows W '0:w0 1:w1 2:w3' +check_fmt 'W:w1' '#{window_linked}' '0' + +# Unlinking a window linked to only one session needs -k. +check_fail 'window only linked to one session' unlink-window -t W:w3 +check_ok unlink-window -k -t W:w3 +check_windows W '0:w0 1:w1' + +# --------------------------------------------------------------------------- +# swap-window. + +check_ok new-window -d -t W:2 -n w2 +check_ok new-window -d -t W:3 -n w3 + +# Swap within a session: indices are exchanged. +check_ok swap-window -d -s W:0 -t W:3 +check_windows W '0:w3 1:w1 2:w2 3:w0' +check_ok swap-window -d -s W:0 -t W:3 +check_windows W '0:w0 1:w1 2:w2 3:w3' + +# Without -d the current index does not change, so the window that arrives +# there becomes current; with -d the swapped windows are selected, so the +# source window stays current at its new index. +check_ok select-window -t W:0 +check_ok swap-window -s W:0 -t W:3 +check_fmt 'W:' '#{window_index}:#{window_name}' '0:w3' +check_ok swap-window -s W:3 -t W:0 +check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0' +check_ok swap-window -d -s W:0 -t W:3 +check_fmt 'W:' '#{window_index}:#{window_name}' '3:w0' +check_ok swap-window -d -s W:3 -t W:0 +check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0' + +# Swap between two different sessions. +check_ok swap-window -d -s W:w2 -t L:l0 +check_windows W '0:w0 1:w1 2:l0 3:w3' +check_windows L '0:w2' +check_ok swap-window -d -s W:2 -t L:0 +check_windows W '0:w0 1:w1 2:w2 3:w3' +check_windows L '0:l0' + +# Swapping between two sessions in the same group is an error. +check_ok new-session -d -s WG -t W +check_fail "can't move window, sessions are grouped" \ + swap-window -d -s W:0 -t WG:1 +check_ok kill-session -t WG +check_windows W '0:w0 1:w1 2:w2 3:w3' + +# --------------------------------------------------------------------------- +# rotate-window. + +check_ok new-session -d -s R -x 80 -y 24 +check_ok split-window -d -t R:0 +check_ok split-window -d -t R:0 +p0=$($TMUX display-message -p -t R:0.0 '#{pane_id}') +p1=$($TMUX display-message -p -t R:0.1 '#{pane_id}') +p2=$($TMUX display-message -p -t R:0.2 '#{pane_id}') + +# check_panes $target $expected +# +# Compare the pane list of a window (as "index:id ...") with $expected. +check_panes() +{ + out=$(echo $($TMUX list-panes -t "$1" -F \ + '#{pane_index}:#{pane_id}')) + if [ "$out" != "$2" ]; then + echo "Pane list of '$1' wrong." + echo "Expected: '$2'" + echo "But got: '$out'" + exit 1 + fi +} + +check_panes R:0 "0:$p0 1:$p1 2:$p2" + +# -U rotates panes up (each pane moves to the previous position); -D rotates +# down. -U then -D restores the original order. +check_ok rotate-window -U -t R:0 +check_panes R:0 "0:$p1 1:$p2 2:$p0" +check_ok rotate-window -D -t R:0 +check_panes R:0 "0:$p0 1:$p1 2:$p2" +check_ok rotate-window -D -t R:0 +check_panes R:0 "0:$p2 1:$p0 2:$p1" +check_ok rotate-window -U -t R:0 + +# The active position is preserved across rotation: the pane that arrives at +# the active position becomes the active pane. +check_ok select-pane -t R:0.0 +check_ok rotate-window -U -t R:0 +check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1" +check_ok rotate-window -D -t R:0 +check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0" + +# --------------------------------------------------------------------------- +# kill-window. + +# Killing the current window switches to the last (previously current) +# window. +check_ok select-window -t W:1 +check_ok select-window -t W:3 +check_fmt 'W:' '#{window_index}' '3' +check_ok kill-window -t W:3 +check_fmt 'W:' '#{window_index}' '1' +check_windows W '0:w0 1:w1 2:w2' + +# -f is only valid with -a. +check_fail '-f only valid with -a' kill-window -f 'x' -t W:0 + +# -a kills every window except the target. +check_ok kill-window -a -t W:w1 +check_windows W '1:w1' + +# --------------------------------------------------------------------------- +# select-window, next-window, previous-window. + +# -P prints where the new window went; an invalid (non-UTF-8) name is an +# error. +out=$($TMUX new-window -d -t W:0 -n wa -P -F '#{window_index}:#{window_name}') +if [ "$out" != "0:wa" ]; then + echo "new-window -P output wrong: '$out'" + exit 1 +fi +check_fail "invalid window name: $(printf 'a\377b')" \ + new-window -d -t W: -n "$(printf 'a\377b')" +check_ok new-window -d -t W:2 -n wc +check_windows W '0:wa 1:w1 2:wc' + +# -n and -p select the next and previous window, wrapping at the ends. +check_ok select-window -t W:0 +check_ok select-window -n -t W: +check_fmt 'W:' '#{window_index}' '1' +check_ok select-window -n -t W: +check_fmt 'W:' '#{window_index}' '2' +check_ok select-window -n -t W: +check_fmt 'W:' '#{window_index}' '0' +check_ok select-window -p -t W: +check_fmt 'W:' '#{window_index}' '2' + +# next-window and previous-window are the same code. +check_ok next-window -t W: +check_fmt 'W:' '#{window_index}' '0' +check_ok previous-window -t W: +check_fmt 'W:' '#{window_index}' '2' + +# -l selects the previously current window and select-window -T on the +# already-current window does the same. +check_ok select-window -t W:1 +check_ok select-window -l -t W: +check_fmt 'W:' '#{window_index}' '2' +check_ok select-window -T -t W:2 +check_fmt 'W:' '#{window_index}' '1' +check_ok select-window -T -t W:2 +check_fmt 'W:' '#{window_index}' '2' + +# With -a, next-window looks for a window with an alert and fails if there +# is none; a fresh session has no last window. +check_fail 'no next window' next-window -a -t W: +check_fail 'no previous window' previous-window -a -t W: +check_ok new-session -d -s F -x 80 -y 24 +check_fail 'no last window' select-window -l -t F: +check_fail 'no last window' select-window -T -t F:0 +check_ok kill-session -t F + +# --------------------------------------------------------------------------- +# more kill-window -a: no-op, filters and multiply-linked windows. + +# -a with a single window in the session does nothing. +check_ok kill-window -a -t L:0 +check_windows L '0:l0' + +# -a -f only kills other windows matching the filter. +check_ok kill-window -a -f '#{==:#{window_name},wc}' -t W:0 +check_windows W '0:wa 1:w1' + +# If the current window is linked into the session more than once, -a kills +# it too - taking the whole session with it here. +check_ok new-session -d -s D -x 80 -y 24 -n d0 +check_ok link-window -d -s D:0 -t D:5 +check_ok new-window -d -t D:1 -n dx +check_ok select-window -t D:0 +check_ok kill-window -a -t D:0 +if $TMUX has-session -t D 2>/dev/null; then + echo "Session D survived kill-window -a on multiply-linked window." + exit 1 +fi + +check_fmt 'R:0' '#{window_panes}' '3' +assert_alive + +$TMUX kill-server 2>/dev/null +exit 0