diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 5a3bffdcc..1ee5f0c20 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -165,7 +165,8 @@ jobs: run: | set -eu PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -q \ - --cov=apps --cov-report=term --cov-report=xml --cov-fail-under=50 + --cov=apps --cov-report=term --cov-report=term-missing --cov-report=xml \ + --cov-fail-under=60 - name: Start PostgreSQL for integration tests shell: sh @@ -211,7 +212,7 @@ jobs: set -eu pip install -q pytest-rerunfailures PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" \ - --cov=apps --cov-append --cov-report=term --cov-report=xml --cov-fail-under=50 + --cov=apps --cov-append --cov-report=term --cov-report=term-missing --cov-report=xml --cov-fail-under=65 - name: Run API performance baseline tests shell: sh @@ -262,6 +263,98 @@ jobs: docker rm -f "${PG_CONTAINER:-ci-pg-validate}" 2>/dev/null || true echo "PostgreSQL container cleaned up" + - name: Coverage summary + if: always() + shell: sh + run: | + set +e + echo "=== 覆盖率汇总 ===" + if [ -f coverage.xml ]; then + python3 -c " +import xml.etree.ElementTree as ET +tree = ET.parse('coverage.xml') +root = tree.getroot() +line_rate = float(root.get('line-rate', 0)) * 100 +branch_rate = float(root.get('branch-rate', 0)) * 100 +lines_covered = int(root.get('lines-covered', 0)) +lines_valid = int(root.get('lines-valid', 0)) +print(f'行覆盖率: {line_rate:.2f}% ({lines_covered}/{lines_valid})') +print(f'分支覆盖率: {branch_rate:.2f}%') +print(f'门槛: 65%') +print(f'状态: {"PASS ✅" if line_rate >= 65 else "FAIL ❌"}') +" + else + echo "coverage.xml 不存在,跳过汇总" + fi + + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + + # 收集失败信息 + FAILED_JOB="Validate Code Quality And Tests" + BRANCH="${GITHUB_REF_NAME:-unknown}" + COMMIT="${GITHUB_SHA:0:8}" + ACTOR="${GITHUB_ACTOR:-unknown}" + RUN_ID="${GITHUB_RUN_ID:-unknown}" + REPO="${GITHUB_REPOSITORY:-unknown}" + RUN_URL="https://git.xiaoxiajianji.com/${REPO}/actions/runs/${RUN_ID}" + + # 构造通知消息 + PAYLOAD=$(cat < /dev/null 2>&1 && echo "通知已发送" || echo "通知发送失败" + else + echo "未配置 CI_NOTIFY_WEBHOOK,跳过通知" + echo "如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK" + fi + - name: Build summary if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' shell: sh @@ -270,6 +363,16 @@ jobs: echo "Build completed successfully!" echo "Branch: ${GITHUB_REF_NAME}" echo "Commit: ${GITHUB_SHA}" + # 输出最终覆盖率 + if [ -f coverage.xml ]; then + python3 -c " +import xml.etree.ElementTree as ET +tree = ET.parse('coverage.xml') +root = tree.getroot() +line_rate = float(root.get('line-rate', 0)) * 100 +print(f'Total coverage: {line_rate:.2f}%') +" + fi frontend-lint: name: Frontend Lint diff --git a/pyproject.toml b/pyproject.toml index 16a9e8248..2d06cc424 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,35 @@ extend_skip_glob = [ "out/**", "coverage/**", ] + +[tool.coverage.run] +source = ["apps", "packages"] +omit = [ + "*/migrations/*", + "*/tests/*", + "*/test_*.py", + "*/site-packages/*", + "*/.cache/*", +] +branch = true + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if __name__ == .__main__.:", + "raise NotImplementedError", + "pass", + "if TYPE_CHECKING:", + "class .*Protocol", + "@abstractmethod", + "raise AssertionError", + "raise RuntimeError", + "if 0:", + "if __debug__:", +] +show_missing = true +skip_covered = false + +[tool.coverage.xml] +output = "coverage.xml"