#!/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 ===" REDIS_CONTAINER="ci-redis-${GITHUB_RUN_ID:-$$}" docker rm -f "$REDIS_CONTAINER" 2>/dev/null || true docker run -d --name "$REDIS_CONTAINER" \ -P \ --health-cmd "redis-cli ping" \ --health-interval 2s \ --health-timeout 2s \ --health-retries 10 \ redis:7-alpine # 等待Redis容器内部ready for i in $(seq 1 15); do if docker inspect --format='{{.State.Health.Status}}' "$REDIS_CONTAINER" 2>/dev/null | grep -q healthy; then echo "Redis container is healthy" break fi echo "Waiting for Redis... ($i/15)" sleep 2 done docker inspect --format='{{.State.Health.Status}}' "$REDIS_CONTAINER" | grep -q healthy # 双模式探测:先试端口映射,再试容器IP,哪个通就用哪个 REDIS_MAPPED_PORT=$(docker port "$REDIS_CONTAINER" 6379/tcp | cut -d: -f2) REDIS_CONTAINER_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$REDIS_CONTAINER") echo "Redis mapped port: $REDIS_MAPPED_PORT, container IP: $REDIS_CONTAINER_IP" REDIS_HOST="" REDIS_PORT="" for mode in "mapped:127.0.0.1:$REDIS_MAPPED_PORT" "container:$REDIS_CONTAINER_IP:6379"; do MODE_NAME=$(echo "$mode" | cut -d: -f1) TEST_HOST=$(echo "$mode" | cut -d: -f2) TEST_PORT=$(echo "$mode" | cut -d: -f3) echo "Testing Redis $MODE_NAME mode ($TEST_HOST:$TEST_PORT)..." for i in $(seq 1 15); do if python3 -c "import socket; s=socket.socket(); s.settimeout(2); s.connect(('$TEST_HOST', $TEST_PORT)); s.close()" 2>/dev/null; then REDIS_HOST="$TEST_HOST" REDIS_PORT="$TEST_PORT" echo "✅ Redis $MODE_NAME mode works: $TEST_HOST:$TEST_PORT" break 2 fi sleep 2 done echo "❌ Redis $MODE_NAME mode failed, trying next..." done if [ -z "$REDIS_HOST" ]; then echo "ERROR: 无法通过任何方式连接到Redis容器" exit 1 fi echo "Using Redis: $REDIS_HOST:$REDIS_PORT" export REDIS_URL="redis://$REDIS_HOST:$REDIS_PORT/0" # --- 启动 PostgreSQL --- echo "" echo "=== 启动 PostgreSQL ===" PG_CONTAINER="ci-pg-${GITHUB_RUN_ID:-$$}" docker rm -f "$PG_CONTAINER" 2>/dev/null || true docker run -d --name "$PG_CONTAINER" \ --shm-size=256m \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=xiaoxia_saas \ -P \ --health-cmd "pg_isready -U postgres" \ --health-interval 5s \ --health-timeout 5s \ --health-retries 12 \ postgres:16 # 等待PG容器内部ready for i in $(seq 1 30); do if docker inspect --format='{{.State.Health.Status}}' "$PG_CONTAINER" 2>/dev/null | grep -q healthy; then echo "PostgreSQL container is healthy" break fi echo "Waiting for PostgreSQL... ($i/30)" sleep 2 done docker inspect --format='{{.State.Health.Status}}' "$PG_CONTAINER" | grep -q healthy # 双模式探测:先试端口映射,再试容器IP,哪个通就用哪个 PG_MAPPED_PORT=$(docker port "$PG_CONTAINER" 5432/tcp | cut -d: -f2) PG_CONTAINER_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$PG_CONTAINER") echo "PostgreSQL mapped port: $PG_MAPPED_PORT, container IP: $PG_CONTAINER_IP" PG_HOST="" PG_PORT="" for mode in "mapped:127.0.0.1:$PG_MAPPED_PORT" "container:$PG_CONTAINER_IP:5432"; do MODE_NAME=$(echo "$mode" | cut -d: -f1) TEST_HOST=$(echo "$mode" | cut -d: -f2) TEST_PORT=$(echo "$mode" | cut -d: -f3) echo "Testing PG $MODE_NAME mode ($TEST_HOST:$TEST_PORT)..." for i in $(seq 1 15); do if python3 -c "import socket; s=socket.socket(); s.settimeout(2); s.connect(('$TEST_HOST', $TEST_PORT)); s.close()" 2>/dev/null; then PG_HOST="$TEST_HOST" PG_PORT="$TEST_PORT" echo "✅ PG $MODE_NAME mode works: $TEST_HOST:$TEST_PORT" break 2 fi sleep 2 done echo "❌ PG $MODE_NAME mode failed, trying next..." done if [ -z "$PG_HOST" ]; then echo "ERROR: 无法通过任何方式连接到PostgreSQL容器" echo "端口映射和容器IP两种方式均失败,可能是当前runner的Docker网络配置异常" exit 1 fi echo "Using PostgreSQL: $PG_HOST:$PG_PORT" export DATABASE_URL="postgresql+psycopg://postgres:postgres@$PG_HOST:$PG_PORT/xiaoxia_saas" # --- 执行迁移 --- 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 "=== 清理容器 ===" docker rm -f "$PG_CONTAINER" 2>/dev/null || true docker rm -f "$REDIS_CONTAINER" 2>/dev/null || true echo "✅ 清理完成" # --- 覆盖率汇总 --- echo "" echo "=== 覆盖率汇总 ===" set +e python3 scripts/ci_coverage_summary.py set -e echo "" echo "=== CI Integration Tests 全部通过 ✅ ==="