diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 4226933a..a6cabe69 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -179,7 +179,7 @@ jobs: # 2026-04-22 ogt: DATABASE_URL 改為必填後,單元測試需要此 env var 讓 Settings 通過驗證 # 單元測試不連 DB,此 CI placeholder 僅供 Pydantic 驗證,不產生真實連線 DATABASE_URL="${DATABASE_URL:-postgresql+asyncpg://ci:ci@localhost/ci}" \ - PYTHONFAULTHANDLER=1 python3.11 -m pytest tests/ -v --tb=short -x \ + PYTHONFAULTHANDLER=1 python3.11 -m pytest tests/ -v --tb=short -x -p no:cacheprovider \ --ignore=tests/integration \ --ignore=tests/test_anomaly_counter.py \ --ignore=tests/test_global_repair_cooldown.py \ @@ -254,12 +254,13 @@ jobs: # 2026-04-22 ogt: DATABASE_URL 改為必填後,import chain 需要此 env var 讓 Settings 通過驗證 DATABASE_URL="postgresql+asyncpg://awoooi:awoooi_test_2026@pg-test-b5:5432/awoooi_test?ssl=disable" \ TEST_DATABASE_URL="postgresql+asyncpg://awoooi:awoooi_test_2026@pg-test-b5:5432/awoooi_test?ssl=disable" \ - /opt/api-venv/bin/pytest tests/integration/test_b5_core_flows.py -v --tb=short -m integration || PYTEST_EXIT=$? + /opt/api-venv/bin/pytest tests/integration/test_b5_core_flows.py -v --tb=short -m integration -p no:cacheprovider || PYTEST_EXIT=$? # 清理 docker rm -f pg-test-b5 || true # 2026-05-20 Codex: B5 imports shared tests helpers, so cleanup the # whole tests tree to avoid root-owned __pycache__ act-runner noise. find tests -type d -name __pycache__ -prune -exec rm -rf {} + 2>/dev/null || true + rm -rf .pytest_cache 2>/dev/null || true exit "${PYTEST_EXIT:-0}" CI_SCRIPT docker run --rm \ @@ -274,6 +275,12 @@ jobs: "${{ env.CI_IMAGE }}" \ bash /tmp/awoooi-b5-tests.sh + - name: Clean Test Workspace Artifacts + if: always() + env: + HOST_RUNNER_CLEANUP_IMAGE: ${{ env.CI_IMAGE }} + run: bash scripts/ci/cleanup-host-runner-workspace.sh + - name: Notify Pipeline Failure # 2026-04-30 Codex: tests job failure notifier; no jq dependency for host parity. if: failure() @@ -1145,6 +1152,9 @@ jobs: /workspace/apps/web/test-results \ /workspace/apps/web/playwright-report \ 2>/dev/null || true + find /workspace/apps /workspace/packages \ + -mindepth 2 -maxdepth 2 -type d -name node_modules -prune -exec rm -rf {} + \ + 2>/dev/null || true } trap cleanup_smoke_workspace_artifacts EXIT @@ -1268,3 +1278,9 @@ jobs: -d "parse_mode=HTML" \ --data-urlencode "text=${MSG}" || echo "TG notify failed (non-fatal): exit=$?" fi + + - name: Clean Post-Deploy Workspace Artifacts + if: always() + env: + HOST_RUNNER_CLEANUP_IMAGE: ${{ env.CI_IMAGE }} + run: bash scripts/ci/cleanup-host-runner-workspace.sh diff --git a/scripts/ci/cleanup-host-runner-workspace.sh b/scripts/ci/cleanup-host-runner-workspace.sh new file mode 100755 index 00000000..ee379f4b --- /dev/null +++ b/scripts/ci/cleanup-host-runner-workspace.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 2026-05-21 Codex: act-runner host jobs bind-mount the checkout into +# root-running CI containers. Clean generated cache/symlink trees before the +# runner's post-job cleanup, otherwise successful jobs can end with misleading +# permission-denied or errSymlink noise. + +ROOT="${1:-${GITHUB_WORKSPACE:-$PWD}}" +ROOT="$(cd "$ROOT" && pwd)" +CLEANUP_IMAGE="${HOST_RUNNER_CLEANUP_IMAGE:-${CI_IMAGE:-}}" + +cleanup_artifacts() { + local root="$1" + cd "$root" + + rm -rf \ + .pytest_cache \ + apps/api/.pytest_cache \ + apps/api/tests/.pytest_cache \ + apps/web/tests/e2e/.auth \ + apps/web/test-results \ + apps/web/playwright-report \ + test-results \ + playwright-report \ + .awoooi-smoke-output \ + 2>/dev/null || true + + find apps/api/tests apps/api/src \ + -type d -name __pycache__ -prune -exec rm -rf {} + \ + 2>/dev/null || true + + find apps packages \ + -mindepth 2 -maxdepth 2 -type d -name node_modules -prune -exec rm -rf {} + \ + 2>/dev/null || true + + rm -rf node_modules apps/web/node_modules 2>/dev/null || true +} + +has_leftovers() { + local root="$1" + [ -d "$root/.pytest_cache" ] && return 0 + [ -d "$root/apps/api/.pytest_cache" ] && return 0 + [ -d "$root/apps/web/tests/e2e/.auth" ] && return 0 + [ -d "$root/node_modules" ] && return 0 + [ -d "$root/apps/web/node_modules" ] && return 0 + find "$root/apps" "$root/packages" \ + -mindepth 2 -maxdepth 2 -type d -name node_modules -print -quit \ + 2>/dev/null | grep -q . +} + +cleanup_artifacts "$ROOT" + +if has_leftovers "$ROOT" && [ -n "$CLEANUP_IMAGE" ] && command -v docker >/dev/null 2>&1; then + echo "host cleanup left root-owned artifacts; retrying through ${CLEANUP_IMAGE}" + if ! docker run --rm \ + -v "$ROOT:/workspace" \ + "$CLEANUP_IMAGE" \ + bash -lc ' + set -euo pipefail + cd /workspace + rm -rf \ + .pytest_cache \ + apps/api/.pytest_cache \ + apps/api/tests/.pytest_cache \ + apps/web/tests/e2e/.auth \ + apps/web/test-results \ + apps/web/playwright-report \ + test-results \ + playwright-report \ + .awoooi-smoke-output \ + node_modules \ + apps/web/node_modules + find apps/api/tests apps/api/src \ + -type d -name __pycache__ -prune -exec rm -rf {} + \ + 2>/dev/null || true + find apps packages \ + -mindepth 2 -maxdepth 2 -type d -name node_modules -prune -exec rm -rf {} + \ + 2>/dev/null || true + '; then + echo "WARNING: docker-based workspace artifact cleanup failed" + fi +fi + +if has_leftovers "$ROOT"; then + echo "WARNING: host runner workspace artifacts remain after cleanup" + find "$ROOT" -maxdepth 4 \ + \( -name .pytest_cache -o -name node_modules -o -name test-results -o -name .auth \) \ + -print 2>/dev/null | head -20 +else + echo "host runner workspace artifacts cleaned" +fi