功能: 添加 tmux 消息通知与日志脚本
CI / lint (push) Successful in 6s

This commit is contained in:
2026-04-19 22:23:34 +08:00
parent 34346be862
commit d9f1990884
9 changed files with 1338 additions and 80 deletions
+356
View File
@@ -0,0 +1,356 @@
#!/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