blob: 8401d0f4f62bde0ccf2e93a5d692295509aa2e64 [file] [log] [blame] [raw]
#!/bin/bash
# 覆盖率测试快速启动脚本
# 简化版本,适合日常使用
set -e
# 脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
echo "🚀 Claude Agent 覆盖率测试脚本"
echo "项目根目录: $PROJECT_ROOT"
echo ""
# 设置环境变量
export PYTHONDONTWRITEBYTECODE=1
# 进程管理变量
PYTEST_PID=""
HTML_PID=""
# 信号处理函数
cleanup_processes() {
echo ""
echo "🛑 收到中断信号,正在清理进程..."
# 强制终止pytest进程
if [ -n "$PYTEST_PID" ] && kill -0 "$PYTEST_PID" 2>/dev/null; then
echo "⚠️ 正在终止pytest进程 (PID: $PYTEST_PID)..."
kill -TERM "$PYTEST_PID" 2>/dev/null || true
sleep 2
if kill -0 "$PYTEST_PID" 2>/dev/null; then
echo "🔨 强制终止pytest进程..."
kill -KILL "$PYTEST_PID" 2>/dev/null || true
fi
fi
# 强制终止HTML报告进程
if [ -n "$HTML_PID" ] && kill -0 "$HTML_PID" 2>/dev/null; then
echo "⚠️ 正在终止HTML报告进程 (PID: $HTML_PID)..."
kill -TERM "$HTML_PID" 2>/dev/null || true
sleep 1
if kill -0 "$HTML_PID" 2>/dev/null; then
kill -KILL "$HTML_PID" 2>/dev/null || true
fi
fi
# 清理pytest子进程
echo "🧹 清理pytest相关子进程..."
pkill -f "python -m pytest" 2>/dev/null || true
pkill -f "coverage" 2>/dev/null || true
echo "✅ 进程清理完成"
exit 130
}
# 注册信号处理器
trap cleanup_processes SIGINT SIGTERM
# 激活虚拟环境
if [ -d "$PROJECT_ROOT/venv" ]; then
source "$PROJECT_ROOT/venv/bin/activate"
echo "✅ 虚拟环境已激活"
else
echo "❌ 虚拟环境不存在: $PROJECT_ROOT/venv"
exit 1
fi
# 清理测试缓存
echo "🧹 清理测试缓存..."
find "$PROJECT_ROOT/tests" -name "*.pyc" -type f -delete 2>/dev/null || true
find "$PROJECT_ROOT/tests" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
# 解析命令行参数
DETAILED=""
HTML=false
ANALYSIS=false
TIMEOUT=180
VERBOSE=true # 默认显示详细输出
QUIET=false
LIVE=false
SHOW_NAMES=true # 默认显示测试用例名称
while [[ $# -gt 0 ]]; do
case $1 in
-d|--detailed)
DETAILED="--cov-report=term-missing"
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-q|--quiet)
VERBOSE=false
QUIET=true
shift
;;
--live)
VERBOSE=true
LIVE=true
shift
;;
--names)
VERBOSE=true
SHOW_NAMES=true
shift
;;
-h|--html)
HTML=true
shift
;;
-a|--analysis)
ANALYSIS=true
shift
;;
-t|--timeout)
if [ -z "$2" ] || [[ "$2" =~ ^--.*$ ]]; then
echo "错误: --timeout 需要一个数字参数"
exit 1
fi
TIMEOUT="$2"
shift 2
;;
--help)
echo "使用方法: $0 [选项]"
echo ""
echo "选项:"
echo " -d, --detailed 显示详细覆盖率报告 (缺失行号)"
echo " -v, --verbose 显示详细测试执行过程 (文件级别)"
echo " -q, --quiet 静默模式,不显示测试用例详情"
echo " --names 显示每个测试用例名称 (默认开启)"
echo " --live 显示实时测试输出"
echo " -h, --html 生成HTML报告"
echo " -a, --analysis 运行模块覆盖率分析"
echo " -t, --timeout N 设置超时时间 (默认: 180秒)"
echo " --help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 # 基本覆盖率测试 (显示测试用例名称)"
echo " $0 -q # 静默模式覆盖率测试"
echo " $0 -v # 显示文件级别进度"
echo " $0 --live # 实时显示测试过程"
echo " $0 -d -h -a # 全功能测试"
exit 0
;;
*)
echo "未知选项: $1"
echo "使用 --help 查看帮助"
exit 1
;;
esac
done
cd "$PROJECT_ROOT"
# 主要覆盖率测试
echo "🔍 运行覆盖率测试 (超时: ${TIMEOUT}s)..."
# 动态查找tests/unit下的所有测试目录
TEST_MODULES=()
declare -A found_modules=()
if [ -d "$PROJECT_ROOT/tests/unit" ]; then
# 直接查找包含测试文件的子目录,避免包含父目录
while IFS= read -r -d '' dir; do
# 跳过tests/unit根目录本身
if [ "$dir" != "$PROJECT_ROOT/tests/unit" ]; then
# 只包含直接包含Python测试文件的目录
if find "$dir" -maxdepth 1 -name "test_*.py" -o -name "*_test.py" | grep -q .; then
# 获取目录名
dir_name=$(basename "$dir")
# 去掉test_前缀来获取模块名
if [[ "$dir_name" == test_* ]]; then
module_name="${dir_name#test_}"
else
module_name="$dir_name"
fi
# 如果这个模块还没有被添加过,或者当前目录名更规范(不带test_前缀)
if [[ -z "${found_modules[$module_name]}" ]] || [[ "$dir_name" == "$module_name" ]]; then
found_modules["$module_name"]="${dir#$PROJECT_ROOT/}"
fi
fi
fi
done < <(find "$PROJECT_ROOT/tests/unit" -maxdepth 1 -mindepth 1 -type d -print0 | sort -z)
# 将选中的目录添加到TEST_MODULES数组
for module in "${!found_modules[@]}"; do
TEST_MODULES+=("${found_modules[$module]}")
done
# 排序输出
IFS=$'\n' TEST_MODULES=($(sort <<<"${TEST_MODULES[*]}"))
echo "发现测试模块:"
for module in "${TEST_MODULES[@]}"; do
echo " ✓ $module"
done
echo ""
else
echo "❌ 测试目录不存在: $PROJECT_ROOT/tests/unit"
exit 1
fi
# 构建pytest参数
PYTEST_ARGS=("--cov=src/claude_agent" "--cov-report=term")
if [ "$QUIET" = true ]; then
PYTEST_ARGS+=("--tb=no" "-q")
echo "执行命令: python -m pytest ${TEST_MODULES[*]} ${PYTEST_ARGS[*]} $DETAILED (静默模式)"
elif [ "$SHOW_NAMES" = true ]; then
PYTEST_ARGS+=("-vv" "--tb=short")
echo "执行命令: python -m pytest ${TEST_MODULES[*]} ${PYTEST_ARGS[*]} $DETAILED (显示测试用例名称)"
elif [ "$VERBOSE" = true ]; then
PYTEST_ARGS+=("-v" "--tb=short")
echo "执行命令: python -m pytest ${TEST_MODULES[*]} ${PYTEST_ARGS[*]} $DETAILED (详细模式 - 显示文件进度)"
else
PYTEST_ARGS+=("--tb=line")
echo "执行命令: python -m pytest ${TEST_MODULES[*]} ${PYTEST_ARGS[*]} $DETAILED (标准模式)"
fi
if [ -n "$DETAILED" ]; then
PYTEST_ARGS+=("$DETAILED")
fi
echo ""
# 运行测试
echo "⏳ 启动覆盖率测试进程 (超时: ${TIMEOUT}s)..."
# 使用后台进程方式执行pytest,便于进程管理
python -m pytest "${TEST_MODULES[@]}" "${PYTEST_ARGS[@]}" &
PYTEST_PID=$!
echo "📊 pytest进程已启动 (PID: $PYTEST_PID)"
# 等待pytest进程完成或超时
WAIT_COUNT=0
PYTEST_RESULT=""
while [ $WAIT_COUNT -lt $TIMEOUT ]; do
if ! kill -0 "$PYTEST_PID" 2>/dev/null; then
# 进程已结束,获取退出码
wait "$PYTEST_PID"
PYTEST_RESULT=$?
break
fi
sleep 1
WAIT_COUNT=$((WAIT_COUNT + 1))
# 每10秒显示一次进度
if [ $((WAIT_COUNT % 10)) -eq 0 ]; then
echo "⏱️ 测试运行中... (已运行 ${WAIT_COUNT}/${TIMEOUT}s)"
fi
done
# 检查是否超时
if [ $WAIT_COUNT -ge $TIMEOUT ]; then
echo "⏰ 测试运行超时 (${TIMEOUT}s),正在强制终止..."
kill -TERM "$PYTEST_PID" 2>/dev/null || true
sleep 3
if kill -0 "$PYTEST_PID" 2>/dev/null; then
echo "🔨 强制杀死pytest进程..."
kill -KILL "$PYTEST_PID" 2>/dev/null || true
fi
echo "❌ 覆盖率测试超时"
exit 1
fi
# 检查pytest结果
if [ "$PYTEST_RESULT" -eq 0 ]; then
echo ""
echo "✅ 覆盖率测试完成"
else
echo ""
echo "❌ 覆盖率测试失败 (退出码: $PYTEST_RESULT)"
exit 1
fi
# 重置PID变量
PYTEST_PID=""
# 生成HTML报告
if [ "$HTML" = true ]; then
echo ""
echo "📄 生成HTML覆盖率报告..."
# 使用后台进程方式执行HTML报告生成
python -m pytest "${TEST_MODULES[@]}" --cov=src/claude_agent --cov-report=html:htmlcov --tb=no -q &
HTML_PID=$!
echo "📊 HTML报告进程已启动 (PID: $HTML_PID)"
# 等待HTML报告进程完成或超时
HTML_WAIT_COUNT=0
HTML_TIMEOUT=120
HTML_RESULT=""
while [ $HTML_WAIT_COUNT -lt $HTML_TIMEOUT ]; do
if ! kill -0 "$HTML_PID" 2>/dev/null; then
# 进程已结束,获取退出码
wait "$HTML_PID"
HTML_RESULT=$?
break
fi
sleep 1
HTML_WAIT_COUNT=$((HTML_WAIT_COUNT + 1))
# 每15秒显示一次进度
if [ $((HTML_WAIT_COUNT % 15)) -eq 0 ]; then
echo "⏱️ HTML报告生成中... (已运行 ${HTML_WAIT_COUNT}/${HTML_TIMEOUT}s)"
fi
done
# 检查是否超时
if [ $HTML_WAIT_COUNT -ge $HTML_TIMEOUT ]; then
echo "⏰ HTML报告生成超时,正在终止..."
kill -TERM "$HTML_PID" 2>/dev/null || true
sleep 2
if kill -0 "$HTML_PID" 2>/dev/null; then
kill -KILL "$HTML_PID" 2>/dev/null || true
fi
echo "❌ HTML报告生成超时"
elif [ "$HTML_RESULT" -eq 0 ]; then
HTML_PATH="$PROJECT_ROOT/htmlcov/index.html"
echo "✅ HTML报告生成成功: $HTML_PATH"
echo "🌐 打开浏览器查看: file://$HTML_PATH"
else
echo "❌ HTML报告生成失败 (退出码: $HTML_RESULT)"
fi
# 重置PID变量
HTML_PID=""
fi
# 模块分析
if [ "$ANALYSIS" = true ]; then
echo ""
echo "🔬 分析各模块覆盖率..."
# 动态构建模块映射
declare -A modules=()
# 遍历源码目录获取所有模块
if [ -d "$PROJECT_ROOT/src/claude_agent" ]; then
while IFS= read -r -d '' src_dir; do
# 获取模块名(去除路径前缀)
module_name=$(basename "$src_dir")
# 跳过__pycache__等目录
if [[ "$module_name" != "__pycache__" && "$module_name" != "*.egg-info" ]]; then
# 查找对应的测试目录
test_dir=""
# 尝试几种可能的测试目录命名模式
for pattern in "tests/unit/${module_name}/" "tests/unit/test_${module_name}/" "tests/unit/${module_name}_test/"; do
if [ -d "$PROJECT_ROOT/$pattern" ] && find "$PROJECT_ROOT/$pattern" -maxdepth 2 -name "test_*.py" -o -name "*_test.py" | grep -q .; then
test_dir="$pattern"
break
fi
done
# 如果找到对应的测试目录,添加到映射中
if [ -n "$test_dir" ]; then
modules["$module_name"]="$test_dir"
fi
fi
done < <(find "$PROJECT_ROOT/src/claude_agent" -maxdepth 1 -type d -print0)
fi
echo "模块覆盖率汇总:"
echo "------------------------"
for module in "${!modules[@]}"; do
echo -n " $module: "
# 构建覆盖率命令,根据模块名确定覆盖范围
cov_target="src/claude_agent/$module"
# 使用后台进程避免hang住
python -m pytest "${modules[$module]}" --cov="$cov_target" --cov-report=term --tb=no -q </dev/null &
MODULE_PID=$!
# 等待30秒或进程完成
MODULE_WAIT=0
MODULE_RESULT=""
while [ $MODULE_WAIT -lt 30 ]; do
if ! kill -0 "$MODULE_PID" 2>/dev/null; then
wait "$MODULE_PID"
if [ $? -eq 0 ]; then
MODULE_RESULT="success"
fi
break
fi
sleep 1
MODULE_WAIT=$((MODULE_WAIT + 1))
done
# 如果超时,强制终止
if [ $MODULE_WAIT -ge 30 ]; then
kill -TERM "$MODULE_PID" 2>/dev/null || true
sleep 1
if kill -0 "$MODULE_PID" 2>/dev/null; then
kill -KILL "$MODULE_PID" 2>/dev/null || true
fi
echo "❌ 超时"
elif [ "$MODULE_RESULT" = "success" ]; then
# 重新运行获取结果(因为后台运行无法获取输出)
result=$(python -m pytest "${modules[$module]}" --cov="$cov_target" --cov-report=term --tb=no -q 2>/dev/null | grep "TOTAL" | awk '{print $NF}')
if [[ $result =~ [0-9]+% ]]; then
echo "✅ $result"
else
echo "⚠️ 无法解析覆盖率"
fi
else
echo "❌ 测试失败"
fi
done
# 如果没有找到任何模块,显示提示
if [ ${#modules[@]} -eq 0 ]; then
echo "⚠️ 未找到可分析的模块"
fi
fi
echo ""
echo "🎉 覆盖率测试脚本执行完成!"