357 lines
11 KiB
Bash
Executable File
357 lines
11 KiB
Bash
Executable File
#!/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" <<ENVEOF
|
||
{"msg_id":"${test_msg_id}","direction":"request","from_pane":"%1","from_title":"PM","to_pane":"%2","to_title":"后端","task_summary":"测试任务","task_file":null,"status":"sent","created_at":"2026-04-19T12:00:00+08:00","delivered_at":"2026-04-19T12:00:01+08:00","replied_at":null,"reply_msg_id":null,"reply_summary":null,"error":null}
|
||
ENVEOF
|
||
echo "{\"msg_id\":\"${test_msg_id}\",\"direction\":\"request\",\"from\":\"%1\",\"to\":\"%2\",\"status\":\"sent\",\"summary\":\"测试任务\",\"ts\":\"2026-04-19T12:00:00+08:00\"}" >> "${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
|