| #!/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 "🎉 覆盖率测试脚本执行完成!" |