tmux-cli-test

📁 gpu-cli/skills 📅 5 days ago
3
总安装量
3
周安装量
#57356
全站排名
安装命令
npx skills add https://github.com/gpu-cli/skills --skill tmux-cli-test

Agent 安装分布

opencode 3
claude-code 3
github-copilot 3
codex 3
kimi-cli 3
gemini-cli 3

Skill 文档

tmux CLI Testing

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.

Helpers

Source the helper script for all primitives:

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

Quick Reference

Function Purpose
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

Configuration

Override defaults before calling functions:

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

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

Minimal Example

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

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

GPU CLI Patterns

Testing the Dashboard (TUI)

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"

# Navigate with keys
tmux_send "dash" j
tmux_send "dash" j
tmux_wait_for "dash" "▶" 5  # selection cursor

# Switch panel
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"

# Close help
tmux_send "dash" Escape
tmux_wait_gone "dash" "Keybindings" 5

# Quit
tmux_send "dash" q
tmux_wait_exit "dash" 5

Testing Interactive Prompts

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

# Wait for next prompt
tmux_wait_for "init" "GPU" 10

# Select GPU with arrow keys
tmux_send "init" j j Enter
tmux_wait_for "init" "provider" 10

tmux_kill "init"

Testing Command Output

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

Testing Error Handling

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

Testing Long-Running Commands with Ctrl-C

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

Docker Containers

For testing CLI apps running inside Docker containers (e.g., FTR testing), source the Docker helpers:

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

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

Docker Example

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

Writing Tests Inline

When no helper script is needed (quick one-off checks), use tmux commands directly:

# Start
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

# Assert
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

Anti-Patterns

Bad Good Why
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 only Unicode width != byte length

Debugging Failed Tests

When a test fails, capture the frame for diagnosis:

# Capture what's actually on screen
tmux_capture "session-name"

# Save to file for comparison
tmux_capture_to_file "session-name" "/tmp/failed-frame.txt"

# Capture with colors to verify styling
tmux_capture_ansi "session-name"

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