tmux-cli-test

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

tmux CLI Testing

tmux CLI 测试

Test CLI applications by running them in tmux sessions, waiting for conditions, sending input, and asserting on output. Never sleep - always wait on a condition.
通过在tmux会话中运行命令、等待条件、发送输入并对输出进行断言,来测试CLI应用。绝不使用sleep,始终基于条件等待。

Helpers

辅助工具

Source the helper script for all primitives:
bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh
引入辅助脚本以使用所有基础功能:
bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh

Quick Reference

速查手册

FunctionPurpose
tmux_start <session> <cmd>
Launch command in detached tmux session
tmux_kill <session>
Kill session
tmux_is_alive <session>
Check if session is running
tmux_capture <session>
Get pane text
tmux_capture_ansi <session>
Get pane text with ANSI codes
tmux_wait_for <session> <text> [timeout]
Poll until text appears
tmux_wait_for_regex <session> <pattern> [timeout]
Poll until regex matches
tmux_wait_gone <session> <text> [timeout]
Poll until text disappears
tmux_wait_exit <session> [timeout]
Poll until process exits
tmux_send <session> <keys...>
Send keys (tmux key names)
tmux_type <session> <text>
Type literal text
tmux_assert_contains <session> <text> [label]
Assert text present
tmux_assert_not_contains <session> <text> [label]
Assert text absent
tmux_assert_matches <session> <pattern> [label]
Assert regex matches
tmux_send_and_wait <session> <keys> <text> [timeout]
Send then wait
tmux_test <session> <cmd> <ready_text> <fn>
Full lifecycle test
函数用途
tmux_start <session> <cmd>
在后台tmux会话中启动命令
tmux_kill <session>
终止会话
tmux_is_alive <session>
检查会话是否在运行
tmux_capture <session>
获取面板文本内容
tmux_capture_ansi <session>
获取包含ANSI代码的面板文本
tmux_wait_for <session> <text> [timeout]
轮询直到指定文本出现
tmux_wait_for_regex <session> <pattern> [timeout]
轮询直到正则表达式匹配成功
tmux_wait_gone <session> <text> [timeout]
轮询直到指定文本消失
tmux_wait_exit <session> [timeout]
轮询直到进程退出
tmux_send <session> <keys...>
发送按键(使用tmux按键名称)
tmux_type <session> <text>
输入字面文本内容
tmux_assert_contains <session> <text> [label]
断言指定文本存在
tmux_assert_not_contains <session> <text> [label]
断言指定文本不存在
tmux_assert_matches <session> <pattern> [label]
断言正则表达式匹配成功
tmux_send_and_wait <session> <keys> <text> [timeout]
发送按键后等待指定文本出现
tmux_test <session> <cmd> <ready_text> <fn>
完整生命周期测试

Configuration

配置

Override defaults before calling functions:
bash
TMUX_TEST_POLL_INTERVAL=0.3  # seconds between polls
TMUX_TEST_TIMEOUT=30         # max wait seconds
TMUX_TEST_WIDTH=120          # terminal columns
TMUX_TEST_HEIGHT=30          # terminal rows
在调用函数前可覆盖默认值:
bash
TMUX_TEST_POLL_INTERVAL=0.3  # 轮询间隔(秒)
TMUX_TEST_TIMEOUT=30         # 最大等待时间(秒)
TMUX_TEST_WIDTH=120          # 终端列数
TMUX_TEST_HEIGHT=30          # 终端行数

Workflow

工作流程

Every test follows this pattern:
1. Start session with command
2. Wait for ready signal (text appearing)
3. Interact (send keys, wait for responses)
4. Assert on captured output
5. Kill session
每个测试都遵循以下模式:
1. 使用命令启动会话
2. 等待就绪信号(指定文本出现)
3. 进行交互(发送按键、等待响应)
4. 对捕获的输出进行断言
5. 终止会话

Minimal Example

最简示例

bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh

tmux_start "test-help" "./crates/target/debug/gpu --help"
tmux_wait_for "test-help" "Usage:" 10
tmux_assert_contains "test-help" "run"
tmux_assert_contains "test-help" "dashboard"
tmux_kill "test-help"
bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh

tmux_start "test-help" "./crates/target/debug/gpu --help"
tmux_wait_for "test-help" "Usage:" 10
tmux_assert_contains "test-help" "run"
tmux_assert_contains "test-help" "dashboard"
tmux_kill "test-help"

Using tmux_test for Lifecycle

使用tmux_test进行生命周期管理

bash
test_help_page() {
    local s="$1"
    tmux_assert_contains "$s" "run" "help shows run command"
    tmux_assert_contains "$s" "dashboard" "help shows dashboard command"
    tmux_assert_not_contains "$s" "ERROR" "no errors in help"
}

tmux_test "help-test" "./crates/target/debug/gpu --help" "Usage:" test_help_page
bash
test_help_page() {
    local s="$1"
    tmux_assert_contains "$s" "run" "帮助信息显示run命令"
    tmux_assert_contains "$s" "dashboard" "帮助信息显示dashboard命令"
    tmux_assert_not_contains "$s" "ERROR" "帮助信息中无错误"
}

tmux_test "help-test" "./crates/target/debug/gpu --help" "Usage:" test_help_page

GPU CLI Patterns

GPU CLI 测试模式

Testing the Dashboard (TUI)

测试仪表板(TUI)

bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh
TMUX_TEST_WIDTH=140
TMUX_TEST_HEIGHT=35

GPU_BIN="./crates/target/debug/gpu"

tmux_start "dash" "$GPU_BIN dashboard"
tmux_wait_for "dash" "Pods" 15
bash
source .claude/skills/tmux-cli-test/scripts/tmux_helpers.sh
TMUX_TEST_WIDTH=140
TMUX_TEST_HEIGHT=35

GPU_BIN="./crates/target/debug/gpu"

tmux_start "dash" "$GPU_BIN dashboard"
tmux_wait_for "dash" "Pods" 15

Verify panels rendered

验证面板已渲染

tmux_assert_contains "dash" "Pods" "pods panel visible" tmux_assert_contains "dash" "Jobs" "jobs panel visible"
tmux_assert_contains "dash" "Pods" "Pods面板可见" tmux_assert_contains "dash" "Jobs" "Jobs面板可见"

Navigate with keys

使用键盘导航

tmux_send "dash" j tmux_send "dash" j tmux_wait_for "dash" "▶" 5 # selection cursor
tmux_send "dash" j tmux_send "dash" j tmux_wait_for "dash" "▶" 5 # 选择光标

Switch panel

切换面板

tmux_send "dash" Tab tmux_wait_for_regex "dash" "Jobs.*selected" 5
tmux_send "dash" Tab tmux_wait_for_regex "dash" "Jobs.*selected" 5

Open help overlay

打开帮助浮层

tmux_send "dash" '?' tmux_wait_for "dash" "Help" 5 tmux_assert_contains "dash" "Keybindings" "help shows keybindings"
tmux_send "dash" '?' tmux_wait_for "dash" "Help" 5 tmux_assert_contains "dash" "Keybindings" "帮助信息显示快捷键"

Close help

关闭帮助

tmux_send "dash" Escape tmux_wait_gone "dash" "Keybindings" 5
tmux_send "dash" Escape tmux_wait_gone "dash" "Keybindings" 5

Quit

退出

tmux_send "dash" q tmux_wait_exit "dash" 5
undefined
tmux_send "dash" q tmux_wait_exit "dash" 5
undefined

Testing Interactive Prompts

测试交互式提示

bash
tmux_start "init" "$GPU_BIN init"
tmux_wait_for "init" "project" 10
bash
tmux_start "init" "$GPU_BIN init"
tmux_wait_for "init" "project" 10

Type project name

输入项目名称

tmux_type "init" "my-test-project" tmux_send "init" Enter
tmux_type "init" "my-test-project" tmux_send "init" Enter

Wait for next prompt

等待下一个提示

tmux_wait_for "init" "GPU" 10
tmux_wait_for "init" "GPU" 10

Select GPU with arrow keys

使用方向键选择GPU

tmux_send "init" j j Enter tmux_wait_for "init" "provider" 10
tmux_kill "init"
undefined
tmux_send "init" j j Enter tmux_wait_for "init" "provider" 10
tmux_kill "init"
undefined

Testing Command Output

测试命令输出

bash
tmux_start "status" "$GPU_BIN status"
tmux_wait_for_regex "status" "(No active|Pod)" 10

FRAME=$(tmux_capture "status")
if echo "$FRAME" | grep -q "No active"; then
    echo "No pods running - expected for cold test"
elif echo "$FRAME" | grep -q "Pod"; then
    tmux_assert_matches "status" "Pod.*READY\|ACTIVE" "pod in valid state"
fi

tmux_wait_exit "status" 15
bash
tmux_start "status" "$GPU_BIN status"
tmux_wait_for_regex "status" "(No active|Pod)" 10

FRAME=$(tmux_capture "status")
if echo "$FRAME" | grep -q "No active"; then
    echo "无Pod运行 - 冷启动测试符合预期"
elif echo "$FRAME" | grep -q "Pod"; then
    tmux_assert_matches "status" "Pod.*READY\|ACTIVE" "Pod处于有效状态"
fi

tmux_wait_exit "status" 15

Testing Error Handling

测试错误处理

bash
tmux_start "bad-cmd" "$GPU_BIN run --nonexistent-flag"
tmux_wait_for_regex "bad-cmd" "error|Error|unknown" 10
tmux_assert_not_contains "bad-cmd" "panic" "no panics on bad input"
tmux_wait_exit "bad-cmd" 5
bash
tmux_start "bad-cmd" "$GPU_BIN run --nonexistent-flag"
tmux_wait_for_regex "bad-cmd" "error|Error|unknown" 10
tmux_assert_not_contains "bad-cmd" "panic" "非法输入时无崩溃"
tmux_wait_exit "bad-cmd" 5

Testing Long-Running Commands with Ctrl-C

使用Ctrl-C测试长时间运行的命令

bash
tmux_start "run-job" "$GPU_BIN run python -c 'import time; time.sleep(3600)'"
tmux_wait_for "run-job" "Running" 30
bash
tmux_start "run-job" "$GPU_BIN run python -c 'import time; time.sleep(3600)'"
tmux_wait_for "run-job" "Running" 30

Interrupt

中断命令

tmux_send "run-job" C-c tmux_wait_for_regex "run-job" "cancel|interrupt|stopped" 10 tmux_wait_exit "run-job" 10
undefined
tmux_send "run-job" C-c tmux_wait_for_regex "run-job" "cancel|interrupt|stopped" 10 tmux_wait_exit "run-job" 10
undefined

Docker Containers

Docker容器测试

For testing CLI apps running inside Docker containers (e.g., FTR testing), source the Docker helpers:
bash
source .claude/skills/tmux-cli-test/scripts/tmux_docker_helpers.sh
TMUX_DOCKER_CONTAINER="gpu-ftr-alex-chen-001"
若要测试运行在Docker容器内的CLI应用(如FTR测试),引入Docker辅助脚本:
bash
source .claude/skills/tmux-cli-test/scripts/tmux_docker_helpers.sh
TMUX_DOCKER_CONTAINER="gpu-ftr-alex-chen-001"

TMUX_DOCKER_SESSION="test" # default, override if needed

TMUX_DOCKER_SESSION="test" # 默认值,可按需覆盖


Set `TMUX_DOCKER_CONTAINER` once, then all `docker_tmux_*` calls route through `docker exec` to that container. Same API as the local helpers but without session/container args:

| Function | Purpose |
|----------|---------|
| `docker_tmux_send <keys...>` | Send tmux keys |
| `docker_tmux_type <text>` | Type literal text |
| `docker_tmux_capture` | Get pane text |
| `docker_tmux_capture_ansi` | Get pane text with ANSI codes |
| `docker_tmux_wait_for <text> [timeout]` | Poll until text appears |
| `docker_tmux_wait_regex <pattern> [timeout]` | Poll until regex matches |
| `docker_tmux_wait_gone <text> [timeout]` | Poll until text disappears |
| `docker_tmux_assert_contains <text> [label]` | Assert text present |
| `docker_tmux_assert_not_contains <text> [label]` | Assert text absent |
| `docker_tmux_assert_matches <pattern> [label]` | Assert regex matches |
| `docker_tmux_send_and_wait <keys> <text> [timeout]` | Send then wait |

设置一次`TMUX_DOCKER_CONTAINER`后,所有`docker_tmux_*`调用将通过`docker exec`路由到该容器。API与本地辅助工具相同,但无需会话/容器参数:

| 函数 | 用途 |
|----------|---------|
| `docker_tmux_send <keys...>` | 发送tmux按键 |
| `docker_tmux_type <text>` | 输入字面文本 |
| `docker_tmux_capture` | 获取面板文本 |
| `docker_tmux_capture_ansi` | 获取包含ANSI代码的面板文本 |
| `docker_tmux_wait_for <text> [timeout]` | 轮询直到文本出现 |
| `docker_tmux_wait_regex <pattern> [timeout]` | 轮询直到正则表达式匹配成功 |
| `docker_tmux_wait_gone <text> [timeout]` | 轮询直到文本消失 |
| `docker_tmux_assert_contains <text> [label]` | 断言文本存在 |
| `docker_tmux_assert_not_contains <text> [label]` | 断言文本不存在 |
| `docker_tmux_assert_matches <pattern> [label]` | 断言正则表达式匹配成功 |
| `docker_tmux_send_and_wait <keys> <text> [timeout]` | 发送按键后等待文本出现 |

Docker Example

Docker示例

bash
source .claude/skills/tmux-cli-test/scripts/tmux_docker_helpers.sh
TMUX_DOCKER_CONTAINER="gpu-ftr-alex-chen-001"

docker_tmux_send "gpu dashboard" Enter
docker_tmux_wait_for "Pods" 15
docker_tmux_capture
docker_tmux_send j
docker_tmux_send "?"
docker_tmux_wait_for "Help" 5
docker_tmux_assert_contains "Keybindings" "help shows keybindings"
docker_tmux_send Escape
docker_tmux_wait_gone "Help" 5
docker_tmux_send q
bash
source .claude/skills/tmux-cli-test/scripts/tmux_docker_helpers.sh
TMUX_DOCKER_CONTAINER="gpu-ftr-alex-chen-001"

docker_tmux_send "gpu dashboard" Enter
docker_tmux_wait_for "Pods" 15
docker_tmux_capture
docker_tmux_send j
docker_tmux_send "?"
docker_tmux_wait_for "Help" 5
docker_tmux_assert_contains "Keybindings" "帮助信息显示快捷键"
docker_tmux_send Escape
docker_tmux_wait_gone "Help" 5
docker_tmux_send q

Writing Tests Inline

编写内联测试

When no helper script is needed (quick one-off checks), use tmux commands directly:
bash
undefined
当不需要辅助脚本时(快速一次性检查),可直接使用tmux命令:
bash
undefined

Start

启动会话

tmux new-session -d -s test -x 120 -y 30 "./crates/target/debug/gpu dashboard"
tmux new-session -d -s test -x 120 -y 30 "./crates/target/debug/gpu dashboard"

Poll for ready (inline wait loop)

轮询等待就绪(内联等待循环)

for i in $(seq 1 100); do tmux capture-pane -t test -p | grep -q "Pods" && break sleep 0.3 done
for i in $(seq 1 100); do tmux capture-pane -t test -p | grep -q "Pods" && break sleep 0.3 done

Assert

断言验证

FRAME=$(tmux capture-pane -t test -p) echo "$FRAME" | grep -q "Pods" && echo "PASS" || echo "FAIL"
FRAME=$(tmux capture-pane -t test -p) echo "$FRAME" | grep -q "Pods" && echo "PASS" || echo "FAIL"

Cleanup

清理

tmux send-keys -t test q tmux kill-session -t test 2>/dev/null
undefined
tmux send-keys -t test q tmux kill-session -t test 2>/dev/null
undefined

Anti-Patterns

反模式

BadGoodWhy
sleep 3
tmux_wait_for s "Ready"
Sleeps are flaky and slow
sleep 5 && tmux capture-pane
tmux_wait_for s "expected" && tmux_capture s
Condition, not duration
Hardcoded binary path
GPU_BIN=./crates/target/debug/gpu
Easy to switch debug/release
No cleanup on failure
tmux_test
or explicit
tmux_kill
Leftover sessions break next run
grep -q
without timeout loop
tmux_wait_for
Text may not be rendered yet
Checking
.len()
of TUI text
Check display content onlyUnicode width != byte length
不良做法正确做法原因
sleep 3
tmux_wait_for s "Ready"
Sleep不可靠且会拖慢测试
sleep 5 && tmux capture-pane
tmux_wait_for s "expected" && tmux_capture s
应基于条件而非时长等待
硬编码二进制路径
GPU_BIN=./crates/target/debug/gpu
便于切换debug/release版本
测试失败时不清理
tmux_test
或显式调用
tmux_kill
残留会话会影响后续测试运行
无超时循环的
grep -q
tmux_wait_for
文本可能尚未渲染完成
检查TUI文本的
.len()
仅检查显示内容Unicode宽度不等于字节长度

Debugging Failed Tests

调试失败的测试

When a test fails, capture the frame for diagnosis:
bash
undefined
测试失败时,捕获界面帧用于诊断:
bash
undefined

Capture what's actually on screen

捕获当前屏幕内容

tmux_capture "session-name"
tmux_capture "session-name"

Save to file for comparison

保存到文件以便对比

tmux_capture_to_file "session-name" "/tmp/failed-frame.txt"
tmux_capture_to_file "session-name" "/tmp/failed-frame.txt"

Capture with colors to verify styling

捕获带颜色的内容以验证样式

tmux_capture_ansi "session-name"
undefined
tmux_capture_ansi "session-name"
undefined

Session Naming Convention

会话命名规范

Use descriptive, prefixed session names to avoid collisions:
gpu-test-dashboard
gpu-test-init
gpu-test-run-basic
gpu-test-status
gpu-test-error-handling
使用描述性的前缀会话名称以避免冲突:
gpu-test-dashboard
gpu-test-init
gpu-test-run-basic
gpu-test-status
gpu-test-error-handling