57cd3d92dd
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 19s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (web-cache, infra/docker/web.Dockerfile, xiaoxia-saas-web, web, Web, 30) (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 34s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m30s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m30s
CI/CD Pipeline / AI Code Review (pull_request) Successful in 1m48s
CI/CD Pipeline / PR Build API Image (Backend) (pull_request) Successful in 1m35s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / AI Code Review (push) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 2m25s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m39s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m1s
CI/CD Pipeline / PR Build API Image (Backend) (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (Backend) (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (web-cache, infra/docker/web.Dockerfile, xiaoxia-saas-web, web, Web, 30) (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m23s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 25s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 4m37s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m39s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m42s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 21s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (Backend) (pull_request) Successful in 4m24s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m7s
CI/CD Pipeline / Frontend Lint (push) Successful in 2m26s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m36s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m39s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m59s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 4m47s
CI/CD Pipeline / CI Gate (pull_request) Successful in 10s
CI/CD Pipeline / Integration Tests (push) Successful in 1m49s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Successful in 1m15s
CI/CD Pipeline / Build Production Web Image (push) Successful in 1m32s
CI/CD Pipeline / Build Production Worker Image (push) Successful in 1m38s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
164 lines
5.7 KiB
Bash
Executable File
164 lines
5.7 KiB
Bash
Executable File
#!/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=55 > /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 全部通过 ✅ ==="
|