#!/bin/bash # test-tmux-msg.sh — tmux-msg.sh / tmux-msg-notify.sh 单元测试 # # 用法: bash scripts/test-tmux-msg.sh # # 通过 mock tmux 命令隔离测试,覆盖消息协议的 12 个场景 set -euo pipefail MSG_SCRIPT="/workspace/scripts/tmux-msg.sh" NOTIFY_SCRIPT="/workspace/scripts/tmux-msg-notify.sh" LOG_SCRIPT="/workspace/scripts/tmux-msg-log.sh" SEND_SCRIPT="/workspace/scripts/tmux-send-prompt.sh" TEST_DIR=$(mktemp -d) MOCK_DIR="${TEST_DIR}/mock-bin" MSG_DIR="${TEST_DIR}/.tmux-messages" LOG_FILE="${TEST_DIR}/test-output.log" # 替代 /workspace/.tmux-messages 为测试目录 export TMUX_MSG_DIR_OVERRIDE="$MSG_DIR" # ─── 颜色输出 ─── GREEN='\033[32m' RED='\033[31m' YELLOW='\033[33m' RESET='\033[0m' pass=0 fail=0 skip=0 result() { local label="$1" ok="$2" detail="${3:-}" if [[ "$ok" == "PASS" ]]; then ((pass++)) || true echo -e " ${GREEN}✓ PASS${RESET} ${label}${detail:+ — ${detail}}" elif [[ "$ok" == "SKIP" ]]; then ((skip++)) || true echo -e " ${YELLOW}⊘ SKIP${RESET} ${label}${detail:+ — ${detail}}" else ((fail++)) || true echo -e " ${RED}✗ FAIL${RESET} ${label}${detail:+ — ${detail}}" fi } cleanup() { rm -rf "$TEST_DIR" } trap cleanup EXIT # ─── 创建 mock tmux ─── create_mock_tmux() { mkdir -p "$MOCK_DIR" cat > "${MOCK_DIR}/tmux" <<'MOCK' #!/bin/bash LOG="$(dirname "$0")/../tmux-calls.log" echo "$@" >> "$LOG" if [[ "$1" == "capture-pane" ]]; then echo "Some output" echo "❯ " fi if [[ "$1" == "display-message" ]]; then echo "✳ MockPane" fi if [[ "$1" == "send-keys" ]]; then # 模拟发送成功 exit 0 fi MOCK chmod +x "${MOCK_DIR}/tmux" } clear_log() { : > "${TEST_DIR}/tmux-calls.log" } read_log() { cat "${TEST_DIR}/tmux-calls.log" 2>/dev/null || true } # 创建测试用的消息目录 setup_msg_dir() { mkdir -p "${MSG_DIR}/prompts" mkdir -p "${MSG_DIR}/$(date +%Y/%m/%d)" : > "${MSG_DIR}/index.jsonl" } # ═══════════════════════════════════════════ echo "" echo "═══ tmux-msg 协议单元测试 ═══" echo "" # ─── 1. send 文本字符串 ─── echo "── 1. send 文本字符串 ──" create_mock_tmux setup_msg_dir clear_log output=$(PATH="${MOCK_DIR}:${PATH}" TMUX_MSG_DIR="$MSG_DIR" TMUX_SEND_LOG="${TEST_DIR}/send.log" TMUX_MSG_NO_VERIFY=1 bash "$MSG_SCRIPT" send %2 "测试任务描述" --from %1 2>&1 || true) msg_id=$(echo "$output" | grep -oE '^msg-[0-9]+-[0-9]+-[0-9a-f]+$' | head -1) if [[ -n "$msg_id" ]]; then result "send → 返回有效 msg_id" "PASS" "$msg_id" else result "send → 返回有效 msg_id" "FAIL" "实际: $msg_id" fi # 验证信封文件创建 envelope=$(find "$MSG_DIR" -name "${msg_id}.json" -type f 2>/dev/null | head -1) if [[ -n "$envelope" ]]; then result "send → 信封文件已创建" "PASS" else result "send → 信封文件已创建" "FAIL" fi # 验证索引更新 if [[ -f "${MSG_DIR}/index.jsonl" ]] && grep -q "$msg_id" "${MSG_DIR}/index.jsonl"; then result "send → index.jsonl 已更新" "PASS" else result "send → index.jsonl 已更新" "FAIL" fi # ─── 2. send 包含回复指令 ─── echo "" echo "── 2. send 包含回复指令 ──" # 检查提示词文件包含 notify 指令 prompt_file="${MSG_DIR}/prompts/${msg_id}.md" if [[ -f "$prompt_file" ]] && grep -q "tmux-msg-notify" "$prompt_file"; then result "send → 提示词包含通知指令" "PASS" else result "send → 提示词包含通知指令" "FAIL" fi if [[ -f "$prompt_file" ]] && grep -q "跨面板任务" "$prompt_file"; then result "send → 提示词包含任务头" "PASS" else result "send → 提示词包含任务头" "FAIL" fi if [[ -f "$prompt_file" ]] && grep -q "%1" "$prompt_file"; then result "send → 提示词包含来源 pane" "PASS" else result "send → 提示词包含来源 pane" "FAIL" fi # ─── 3. 消息 ID 格式 ─── echo "" echo "── 3. 消息 ID 格式 ──" output2=$(PATH="${MOCK_DIR}:${PATH}" TMUX_MSG_DIR="$MSG_DIR" TMUX_SEND_LOG="${TEST_DIR}/send.log" TMUX_MSG_NO_VERIFY=1 bash "$MSG_SCRIPT" send %3 "第二个任务" 2>&1 || true) msg_id2=$(echo "$output2" | grep -oE '^msg-[0-9]+-[0-9]+-[0-9a-f]+$' | head -1) if [[ -n "$msg_id2" ]]; then result "消息 ID 格式正确" "PASS" "$msg_id2" else result "消息 ID 格式正确" "FAIL" "实际: $msg_id2" fi if [[ "$msg_id" != "$msg_id2" ]]; then result "消息 ID 唯一" "PASS" else result "消息 ID 唯一" "FAIL" "两次 send 返回相同 ID" fi # ─── 4. pane 标题解析 ─── echo "" echo "── 4. pane 标题解析 ──" if [[ -f "$envelope" ]]; then from_title=$(python3 -c "import json; print(json.load(open('$envelope'))['from_title'])" 2>/dev/null) if [[ "$from_title" == "✳ MockPane" ]]; then result "pane 标题从 mock 正确解析" "PASS" "$from_title" else result "pane 标题从 mock 正确解析" "FAIL" "实际: $from_title" fi fi # ─── 5. notify 成功 ─── echo "" echo "── 5. notify 成功 ──" setup_msg_dir # 先手动创建一个消息信封用于 notify 测试 test_msg_id="msg-20260419-120000-test" day_dir=$(date +%Y/%m/%d) mkdir -p "${MSG_DIR}/${day_dir}" cat > "${MSG_DIR}/${day_dir}/${test_msg_id}.json" <> "${MSG_DIR}/index.jsonl" clear_log output=$(PATH="${MOCK_DIR}:${PATH}" TMUX_MSG_DIR="$MSG_DIR" TMUX_SEND_LOG="${TEST_DIR}/send.log" TMUX_PANE="%2" bash "$NOTIFY_SCRIPT" "$test_msg_id" "%1" "已完成测试" 2>&1 || true) # 验证原消息状态更新 updated_status=$(python3 -c "import json; print(json.load(open('${MSG_DIR}/${day_dir}/${test_msg_id}.json'))['status'])" 2>/dev/null) if [[ "$updated_status" == "completed" ]]; then result "notify → 原消息状态更新为 completed" "PASS" else result "notify → 原消息状态更新为 completed" "FAIL" "实际: $updated_status" fi # 验证回复信封创建 reply_envelope="${MSG_DIR}/${day_dir}/reply-${test_msg_id}.json" if [[ -f "$reply_envelope" ]]; then result "notify → 回复信封已创建" "PASS" else result "notify → 回复信封已创建" "FAIL" fi # 验证 notify 调用了 send-prompt 发送回复 log=$(read_log) if echo "$log" | grep -q "send-keys"; then result "notify → 调用了 tmux send-keys" "PASS" else result "notify → 调用了 tmux send-keys" "FAIL" fi # ─── 6. notify 未知 msg_id 容错 ─── echo "" echo "── 6. notify 未知 msg_id ──" clear_log output=$(PATH="${MOCK_DIR}:${PATH}" TMUX_MSG_DIR="$MSG_DIR" TMUX_SEND_LOG="${TEST_DIR}/send.log" TMUX_PANE="%2" bash "$NOTIFY_SCRIPT" "msg-nonexistent" "%1" "测试" 2>&1 || true) if echo "$output" | grep -q "未找到消息"; then result "notify 未知 msg_id → 宽容处理" "PASS" else result "notify 未知 msg_id → 宽容处理" "FAIL" "输出: $output" fi # ─── 7. 目录结构 ─── echo "" echo "── 7. 目录结构 ──" today_dir=$(date +%Y/%m/%d) if [[ -d "${MSG_DIR}/${today_dir}" ]]; then result "日期分区目录已创建" "PASS" "${MSG_DIR}/${today_dir}" else result "日期分区目录已创建" "FAIL" fi if [[ -d "${MSG_DIR}/prompts" ]]; then result "prompts 目录已创建" "PASS" else result "prompts 目录已创建" "FAIL" fi # ─── 8. index.jsonl 一致性 ─── echo "" echo "── 8. index.jsonl 一致性 ──" line_count=$(wc -l < "${MSG_DIR}/index.jsonl" | tr -d ' ') if [[ "$line_count" -ge 1 ]]; then result "index.jsonl 有内容" "PASS" "$line_count 行" else result "index.jsonl 有内容" "FAIL" "空文件" fi # 验证每行都是有效 JSON invalid_lines=0 while IFS= read -r line; do [[ -z "$line" ]] && continue if ! echo "$line" | python3 -c "import sys,json; json.loads(sys.stdin.read())" 2>/dev/null; then invalid_lines=$((invalid_lines + 1)) fi done < "${MSG_DIR}/index.jsonl" if [[ "$invalid_lines" -eq 0 ]]; then result "index.jsonl 所有行都是有效 JSON" "PASS" else result "index.jsonl 所有行都是有效 JSON" "FAIL" "${invalid_lines} 行无效" fi # ─── 9. log 工具查询 ─── echo "" echo "── 9. log 工具查询 ──" output=$(TMUX_MSG_DIR="$MSG_DIR" bash "$LOG_SCRIPT" recent 2>&1) if echo "$output" | grep -q "消息"; then result "log recent → 返回消息列表" "PASS" else result "log recent → 返回消息列表" "FAIL" fi output=$(TMUX_MSG_DIR="$MSG_DIR" bash "$LOG_SCRIPT" show "$test_msg_id" 2>&1) if echo "$output" | grep -q "$test_msg_id"; then result "log show → 返回消息详情" "PASS" else result "log show → 返回消息详情" "FAIL" fi output=$(TMUX_MSG_DIR="$MSG_DIR" bash "$LOG_SCRIPT" stats 2>&1) if echo "$output" | grep -q "总计"; then result "log stats → 返回统计" "PASS" else result "log stats → 返回统计" "FAIL" fi # ─── 10. list 过滤 ─── echo "" echo "── 10. list 过滤 ──" output=$(TMUX_MSG_DIR="$MSG_DIR" bash "$LOG_SCRIPT" list --from %1 2>&1) if echo "$output" | grep -q "%1"; then result "list --from → 过滤生效" "PASS" else result "list --from → 过滤生效" "FAIL" fi output=$(TMUX_MSG_DIR="$MSG_DIR" bash "$LOG_SCRIPT" by-pane %2 2>&1) if echo "$output" | grep -q "%2"; then result "by-pane → 过滤生效" "PASS" else result "by-pane → 过滤生效" "FAIL" fi # ─── 11. send 参数校验 ─── echo "" echo "── 11. send 参数校验 ──" output=$(PATH="${MOCK_DIR}:${PATH}" TMUX_MSG_DIR="$MSG_DIR" TMUX_SEND_LOG="${TEST_DIR}/send.log" bash "$MSG_SCRIPT" send 2>&1 || true) if echo "$output" | grep -q "用法\|需要"; then result "send 无参数 → 提示用法" "PASS" else result "send 无参数 → 提示用法" "FAIL" fi # ─── 12. 信封字段完整性 ─── echo "" echo "── 12. 信封字段完整性 ──" required_fields="msg_id direction from_pane from_title to_pane to_title task_summary status created_at" missing_fields="" for field in $required_fields; do if ! python3 -c "import json; d=json.load(open('$envelope')); assert '$field' in d" 2>/dev/null; then missing_fields="${missing_fields} ${field}" fi done if [[ -z "$missing_fields" ]]; then result "信封字段完整" "PASS" else result "信封字段完整" "FAIL" "缺少:${missing_fields}" fi # ═══════════════════════════════════════════ echo "" echo "════════════════════════════════════" echo -e " ${GREEN}通过: ${pass}${RESET} ${RED}失败: ${fail}${RESET} ${YELLOW}跳过: ${skip}${RESET} 总计: $((pass + fail + skip))" echo "════════════════════════════════════" echo "" if [[ "$fail" -gt 0 ]]; then exit 1 fi