#!/bin/bash # CI Unit Tests Job 主脚本 # 包含:依赖安装、增量测试选择、覆盖率测试、diff覆盖率门禁 set -eu # 测试环境必须的密钥变量 export JWT_SECRET_KEY=${JWT_SECRET_KEY:-test-jwt-secret-for-ci-only-2026} JOB_NAME="${1:-Unit Tests}" echo "=== CI Unit Tests 开始 ===" # --- 配置 pip 国内源(加速下载,减少网络失败)--- python3 -m pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ python3 -m pip config set global.timeout 120 python3 -m pip config set global.retries 5 # --- 安装依赖 --- echo "" echo "=== 安装 Python 依赖 ===" # pip install 带重试(网络不稳定时自动重试) for i in 1 2 3; do python3 -m pip install -q -r requirements-base.txt && break echo "pip install requirements-base.txt 失败,重试 $i/3..." [ $i -eq 3 ] && exit 1 sleep 5 done for i in 1 2 3; do python3 -m pip install -q -r requirements.txt && break echo "pip install requirements.txt 失败,重试 $i/3..." [ $i -eq 3 ] && exit 1 sleep 5 done for i in 1 2 3; do python3 -m pip install -q -r requirements-worker.txt && break echo "pip install requirements-worker.txt 失败,重试 $i/3..." [ $i -eq 3 ] && exit 1 sleep 5 done for i in 1 2 3; do python3 -m pip install -q -r requirements-dev.txt && break echo "pip install requirements-dev.txt 失败,重试 $i/3..." [ $i -eq 3 ] && exit 1 sleep 5 done pytest --version # --- 安装 ffmpeg(视频处理相关测试依赖)--- bash scripts/ci/step_install_ffmpeg.sh # 双保险:确保numpy已安装 python3 -m pip install -q numpy==1.26.4 || true # --- 增量测试选择(仅PR) --- UNIT_TEST_MODE="full" SELECTED_TEST_FILES="tests/unit" if [ "${GITHUB_EVENT_NAME:-}" = "pull_request" ] && [ -n "${GITHUB_TOKEN:-}" ]; then echo "" echo "=== 增量测试选择 ===" PR_NUMBER=$(echo "$GITHUB_REF" | sed 's|refs/pull/||; s|/.*||') API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=300" CHANGED_FILES=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" | python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin) if f['status'] != 'removed']") echo "改动文件数: $(echo "$CHANGED_FILES" | grep -c . || echo 0)" set +e CHANGED_FILES="$CHANGED_FILES" \ SELECTED_TESTS_OUTPUT=/tmp/selected_tests.txt \ python3 scripts/ci/select_unit_tests.py SELECT_EXIT=$? set -e if [ $SELECT_EXIT -eq 0 ]; then UNIT_TEST_MODE="incremental" TEST_FILES=$(cat /tmp/selected_tests.txt | tr '\n' ' ') SELECTED_TEST_FILES="$TEST_FILES" echo "增量模式: $(cat /tmp/selected_tests.txt | wc -l) 个测试文件" else echo "全量模式" fi fi # --- 运行单元测试 + 覆盖率 --- echo "" echo "=== 运行单元测试 (模式: $UNIT_TEST_MODE) ===" if [ "$UNIT_TEST_MODE" = "incremental" ]; then echo "=== 增量测试模式 ===" PYTHONPATH="$PWD/apps/api:$PWD/apps/worker:$PWD/packages:$PWD" python3 -m coverage run \ --source=apps/api/app,apps/worker/worker_app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ --branch \ -m pytest $SELECTED_TEST_FILES -q python3 -m coverage report --show-missing python3 -m coverage xml -o coverage.xml python3 -m coverage report --fail-under=10 > /dev/null || true else PYTHONPATH="$PWD/apps/api:$PWD/apps/worker:$PWD/packages:$PWD" python3 -m coverage run \ --source=apps/api/app,apps/worker/worker_app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ --branch \ -m pytest tests/unit -q python3 -m coverage report --show-missing python3 -m coverage xml -o coverage.xml python3 -m coverage report --fail-under=65 > /dev/null fi # --- Diff 覆盖率检查(仅PR) --- if [ "${GITHUB_EVENT_NAME:-}" = "pull_request" ] && [ -n "${GITHUB_TOKEN:-}" ]; then echo "" echo "=== Diff 覆盖率检查 ===" BASE_BRANCH="${GITHUB_BASE_REF:-develop}" echo "Base branch: $BASE_BRANCH" PR_CODE_DIR="/tmp/pr-code-$$" mkdir -p "$PR_CODE_DIR" # 备份PR代码(含coverage.xml,diff-cover需要用到 find . -maxdepth 1 -mindepth 1 ! -name 'diff_coverage.html' -exec cp -r {} "$PR_CODE_DIR/" \; rm -rf .git git init > /dev/null 2>&1 git remote add origin https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas.git > /dev/null 2>&1 git config user.email "ci@local" git config user.name "CI" git fetch origin "$BASE_BRANCH" --depth=200 # 先清理工作目录,避免未跟踪文件导致checkout失败 find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + git checkout -b ci-pr-branch "origin/$BASE_BRANCH" > /dev/null 2>&1 # 清除base分支源码,用PR代码覆盖 find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + cp -r "$PR_CODE_DIR"/. . rm -rf "$PR_CODE_DIR" git add -A > /dev/null 2>&1 git commit -m "ci-tmp" > /dev/null 2>&1 if [ "$UNIT_TEST_MODE" = "incremental" ]; then THRESHOLD=40 echo "增量测试模式,增量覆盖率门槛: ${THRESHOLD}%" else THRESHOLD=60 echo "全量测试模式,增量覆盖率门槛: ${THRESHOLD}%" fi set +e python3 -m diff_cover.diff_cover_tool coverage.xml \ --compare-branch="origin/$BASE_BRANCH" \ --fail-under=$THRESHOLD \ --html-report diff_coverage.html \ 2>&1 DIFF_EXIT=$? set -e if [ $DIFF_EXIT -ne 0 ]; then echo "" echo "❌ 增量覆盖率未达到门槛 (${THRESHOLD}%)" echo " 请为改动的代码添加单元测试后再提交" echo "" echo "=== 覆盖率报告 ===" python3 -m diff_cover.diff_cover_tool coverage.xml \ --compare-branch="origin/$BASE_BRANCH" 2>&1 | tail -30 exit 1 fi echo "✅ 增量覆盖率达标" fi echo "" echo "=== CI Unit Tests 全部通过 ✅ ==="