#!/bin/bash # CI Integration Tests Job 主脚本 # 包含:依赖安装、ffmpeg安装、Redis启动、PG启动、迁移、测试、清理、覆盖率 set -eu echo "=== CI Integration Tests 开始 ===" # --- 安装依赖 --- echo "" echo "=== 安装 Python 依赖 ===" python3 -m pip install -q -r requirements-base.txt python3 -m pip install -q -r requirements.txt python3 -m pip install -q -r requirements-dev.txt python3 -m pip install -q pytest-rerunfailures pytest --version # --- 安装 ffmpeg --- echo "" echo "=== 安装 ffmpeg ===" bash scripts/ci/step_install_ffmpeg.sh # --- 启动 Redis --- echo "" echo "=== 启动 Redis ===" start_redis() { local name="ci-redis-${GITHUB_RUN_ID:-$$}" docker rm -f "$name" 2>/dev/null || true docker run -d --name "$name" \ -p 0:6379 \ --health-cmd "redis-cli ping" \ --health-interval 2s \ --health-timeout 2s \ --health-retries 15 \ redis:7-alpine > /dev/null 2>&1 # 等几秒让容器起来 sleep 3 # 方式1: 端口映射 local port="" port=$(docker inspect --format='{{if (index .NetworkSettings.Ports "6379/tcp")}}{{(index (index .NetworkSettings.Ports "6379/tcp") 0).HostPort}}{{end}}' "$name" 2>/dev/null || true) if [ -n "$port" ]; then echo " 端口映射: 127.0.0.1:$port" # 等健康 for i in $(seq 1 20); do if docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null | grep -q healthy; then break fi sleep 2 done # TCP验证 for i in $(seq 1 15); do if (echo > /dev/tcp/127.0.0.1/$port) 2>/dev/null; then echo "✅ Redis端口映射模式可用" export REDIS_URL="redis://127.0.0.1:$port/0" return 0 fi sleep 1 done echo " ⚠️ 端口映射TCP不通,尝试容器IP" else echo " ⚠️ 无映射端口,尝试容器IP" fi # 方式2: 容器IP local ip="" ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$name" 2>/dev/null || true) if [ -n "$ip" ] && [ "$ip" != "null" ]; then echo " 容器IP: $ip" for i in $(seq 1 20); do if docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null | grep -q healthy; then break fi sleep 2 done for i in $(seq 1 15); do if (echo > /dev/tcp/$ip/6379) 2>/dev/null; then echo "✅ Redis容器IP模式可用" export REDIS_URL="redis://$ip:6379/0" return 0 fi sleep 1 done fi echo "❌ Redis启动失败" docker ps -a | head -5 docker logs --tail=10 "$name" 2>/dev/null || true return 1 } start_redis # --- 启动 PostgreSQL --- echo "" echo "=== 启动 PostgreSQL ===" start_pg() { local name="ci-pg-${GITHUB_RUN_ID:-$$}" docker rm -f "$name" 2>/dev/null || true docker run -d --name "$name" \ -p 0:5432 \ --shm-size=256m \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=xiaoxia_saas \ --health-cmd "pg_isready -U postgres" \ --health-interval 3s \ --health-timeout 3s \ --health-retries 20 \ postgres:16-alpine > /dev/null 2>&1 sleep 3 # 方式1: 端口映射 local port="" port=$(docker inspect --format='{{if (index .NetworkSettings.Ports "5432/tcp")}}{{(index (index .NetworkSettings.Ports "5432/tcp") 0).HostPort}}{{end}}' "$name" 2>/dev/null || true) if [ -n "$port" ]; then echo " 端口映射: 127.0.0.1:$port" # 等健康 for i in $(seq 1 30); do if docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null | grep -q healthy; then break fi sleep 2 done # TCP验证 for i in $(seq 1 20); do if (echo > /dev/tcp/127.0.0.1/$port) 2>/dev/null; then echo "✅ PG端口映射模式可用" export DATABASE_URL="postgresql+psycopg://postgres:postgres@127.0.0.1:$port/xiaoxia_saas" return 0 fi sleep 1 done echo " ⚠️ 端口映射TCP不通,尝试容器IP" else echo " ⚠️ 无映射端口,尝试容器IP" fi # 方式2: 容器IP local ip="" ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$name" 2>/dev/null || true) if [ -n "$ip" ] && [ "$ip" != "null" ]; then echo " 容器IP: $ip" for i in $(seq 1 30); do if docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null | grep -q healthy; then break fi sleep 2 done for i in $(seq 1 20); do if (echo > /dev/tcp/$ip/5432) 2>/dev/null; then echo "✅ PG容器IP模式可用" export DATABASE_URL="postgresql+psycopg://postgres:postgres@$ip:5432/xiaoxia_saas" return 0 fi sleep 1 done fi echo "❌ PostgreSQL启动失败" echo " 容器状态:" docker inspect --format='状态:{{.State.Status}} 健康:{{.State.Health.Status}} 退出码:{{.State.ExitCode}}' "$name" 2>/dev/null || true docker logs --tail=20 "$name" 2>/dev/null || true return 1 } start_pg echo "" echo "=== 执行 Alembic 迁移 ===" PYTHONPATH="$PWD/apps/api:$PWD" python3 -m alembic upgrade head echo "✅ 迁移完成" # --- 运行集成测试 --- echo "" echo "=== 运行集成测试 ===" PYTHONPATH="$PWD/apps/api:$PWD" python3 -m coverage run \ --source=apps/api/app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ --branch \ -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" python3 -m coverage report --show-missing python3 -m coverage xml -o coverage.xml python3 -m coverage report --fail-under=40 > /dev/null echo "✅ 集成测试通过" # --- API 性能基线测试(仅告警) --- echo "" echo "=== API 性能基线测试(仅告警) ===" set +e PERF_OUTPUT=$(mktemp) PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration/test_api_performance.py \ -v --timeout=120 -p no:cacheprovider 2>&1 | tee "$PERF_OUTPUT" echo "" echo "=== 性能测试摘要 ===" grep "PERF_STATS:" "$PERF_OUTPUT" || echo "PERF_STATS: 未找到统计数据" grep "PERF_RESULT:" "$PERF_OUTPUT" || echo "PERF_RESULT: 未找到详细结果" TOTAL=$(grep -c "PERF_RESULT:" "$PERF_OUTPUT" || echo 0) PASSED=$(grep "PERF_RESULT: PASS" "$PERF_OUTPUT" | wc -l) FAILED=$(grep "PERF_RESULT: FAIL" "$PERF_OUTPUT" | wc -l) echo "" echo "性能测试结果: $PASSED/$TOTAL 通过, $FAILED 未达标" if [ "$FAILED" -gt 0 ]; then echo "" echo "⚠️ 警告: $FAILED 个接口性能未达标" fi rm -f "$PERF_OUTPUT" set -e # --- 覆盖率汇总 --- echo "" echo "=== 覆盖率汇总 ===" set +e python3 scripts/ci_coverage_summary.py set -e echo "" echo "=== CI Integration Tests 全部通过 ✅ ==="