From 56ea9ff7b8b66633ced0ed50aaecf88d0f3d211b Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 11:56:23 +0800 Subject: [PATCH 1/2] =?UTF-8?q?ci:=20=E4=BC=98=E5=8C=96PR=E9=97=A8?= =?UTF-8?q?=E7=A6=81=20-=20Build=20Staging=E7=A7=BB=E5=87=BAPR=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=20+=20Unit=20Tests=E7=8B=AC=E7=AB=8B=E5=B9=B6?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改动: 1. Build Staging 从 PR 流程移出,仅在 develop/main 的 push 事件异步执行 2. Unit Tests 从 Validate 中拆出为独立 job,与 Validate/Frontend Lint 并行 3. PR 门禁从 3 项(含 Integration Tests)改为 3 项快检查(Validate + Frontend Lint + Unit Tests) 4. Integration Tests 保留执行但不再作为 PR 合并门禁 5. 目标: PR 全绿时间从 15-30min 降至 8min 以内 --- .gitea/workflows/ci-cd.yml | 129 +++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index dc732d20a..f3711fc55 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -158,57 +158,72 @@ jobs: python3 scripts/check_migration_safety.py --allow-medium-risk fi - - name: Debug coverage paths - shell: sh - run: | - set +e - echo "=== PWD ===" - pwd - echo "=== check source dirs ===" - ls -d apps/api/app packages - echo "=== python import check ===" - python3 - <<'PY' - import sys, os - os.environ["PYTHONPATH"] = f"{os.getcwd()}/apps/api:{os.getcwd()}" - sys.path.insert(0, f"{os.getcwd()}/apps/api") - sys.path.insert(0, os.getcwd()) - print(f"cwd: {os.getcwd()}") - print(f"sys.path[:5]: {sys.path[:5]}") - try: - import app - print(f"app.__file__: {app.__file__}") - except Exception as e: - print(f"import app failed: {e}") - try: - import packages - print(f"packages.__file__: {packages.__file__}") - except Exception as e: - print(f"import packages failed: {e}") - PY - echo "=== coverage debug ===" - python3 - <<'PY' - import os, sys - sys.path.insert(0, f"{os.getcwd()}/apps/api") - sys.path.insert(0, os.getcwd()) - import coverage - cov = coverage.Coverage(source=["apps/api/app", "packages"]) - print(f"source: {cov.config.source}") - for src in cov.config.source or []: - abspath = os.path.abspath(src) - print(f" {src} -> {abspath} exists={os.path.exists(src)}") - if os.path.isdir(src): - pyfiles = [] - for root, dirs, files in os.walk(src): - for f in files: - if f.endswith('.py'): - pyfiles.append(os.path.join(root, f)) - print(f" .py files: {len(pyfiles)}") - PY + unit-tests: + name: Unit Tests + runs-on: host + timeout-minutes: 8 - - name: Run unit tests + env: + USE_IN_MEMORY_DB: "true" + + steps: + - name: Checkout code shell: sh env: - USE_IN_MEMORY_DB: "true" + GITHUB_TOKEN: ${{ github.token }} + run: | + set -eu + python3 - <<'PY' + import io, os, tarfile, time, urllib.request, urllib.error + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + last_err = None + for attempt in range(5): + try: + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + break + except urllib.error.HTTPError as e: + last_err = e + if e.code >= 500 and attempt < 4: + wait = 2 ** attempt + print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + except Exception as e: + last_err = e + if attempt < 4: + wait = 2 ** attempt + print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + else: + raise last_err + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Install dependencies + shell: sh + run: | + set -eu + 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 + pytest --version + + - name: Run unit tests with coverage + shell: sh run: | set -eu PYTHONPATH="$PWD/apps/api:$PWD" python3 -m coverage run \ @@ -220,16 +235,16 @@ jobs: python3 -m coverage xml -o coverage.xml python3 -m coverage report --fail-under=60 > /dev/null - - name: Build summary - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' + - name: CI failure notification + if: failure() shell: sh + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + CI_WEBHOOK_URL: ${{ secrets.CI_WEBHOOK_URL }} run: | - set -eu - echo "Build completed successfully!" - echo "Branch: ${GITHUB_REF_NAME}" - echo "Commit: ${GITHUB_SHA}" - # 输出最终覆盖率 - python3 scripts/ci_coverage_summary.py + set +e + FAILED_JOB="Unit Tests" python3 scripts/ci_notify_failure.py + integration-tests: name: Integration Tests @@ -578,7 +593,7 @@ jobs: timeout-minutes: 30 needs: [validate, frontend-lint] - if: github.ref_name == 'main' || github.ref_name == 'develop' || startsWith(github.ref_name, 'feature/') + if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop') steps: - name: Checkout code -- 2.54.0 From 4e76dadd9849cc72e4c66010f6ff6e3c1b81a380 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 13 Jul 2026 12:03:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(test):=20=E4=BF=AE=E5=A4=8DFFmpeg?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E4=BF=9D=E6=8A=A4=E5=8D=95=E6=B5=8B=20-=20pr?= =?UTF-8?q?obe=20mock=E6=95=B0=E6=8D=AE=E7=BC=BA=E5=B0=91codec=5Ftype?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_probe_success 的 fake_output 缺少 codec_type: video 字段, 导致 probe_video_info 找不到视频流,返回默认值 1280 而非预期的 1920。 --- tests/unit/test_ffmpeg_timeout_protection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_ffmpeg_timeout_protection.py b/tests/unit/test_ffmpeg_timeout_protection.py index 04ecbac22..446d14fab 100644 --- a/tests/unit/test_ffmpeg_timeout_protection.py +++ b/tests/unit/test_ffmpeg_timeout_protection.py @@ -79,7 +79,7 @@ class TestProbeVideoInfoTimeout: """正常情况应解析 ffprobe JSON 输出。""" fake_output = """ { - "streams": [{"width": 1920, "height": 1080, "r_frame_rate": "30/1", "duration": "10.5"}], + "streams": [{"width": 1920, "height": 1080, "codec_type": "video", "r_frame_rate": "30/1", "duration": "10.5"}], "format": {"duration": "10.5"} } """ -- 2.54.0