diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 9fbf63f..a115ca2 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -74,12 +74,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: flutter analyze + test (unit/widget) + - name: flutter analyze + test + coverage run: | docker run --rm \ -v "$PWD/client:/app" -w /app \ ghcr.io/cirruslabs/flutter:stable \ - bash -c "flutter pub get && flutter analyze --no-fatal-infos && flutter test test/unit test/widget test/contract" + bash -c "flutter pub get && flutter analyze --no-fatal-infos && flutter test --coverage test/unit test/widget test/contract" + # 覆盖率闸(host 侧解析 lcov):防断崖,低于阈值即失败。 + COV=$(awk -F: '/^LF:/{f+=$2} /^LH:/{h+=$2} END{if(f>0)printf "%.1f",h/f*100}' client/coverage/lcov.info) + echo "flutter 行覆盖 ${COV}%" + awk -v c="$COV" 'BEGIN{ if(c+0 < 28){ print "❌ flutter 覆盖率 "c"% < 阈值 28%"; exit 1 } print "✅ flutter 覆盖率 "c"% ≥ 28%" }' # ── Job 5: Portable SQL Scan (支柱 3:双库可移植)───────────────────────── # server Go 运行时查询不得含 MySQL 专属构造(同一份 SQL 要跑 mysql 与 sqlite)。 @@ -113,7 +117,7 @@ jobs: # ── Job 7: Go 服务端(build + test:含契约快照 + sqlite 真库,跳过 integration)── # 此前 server 测试未进 CI;契约快照(支柱 2)等需在此守门。integration 测试走 - # -tags integration(需 docker 起 mysql/redis),不在本 job,由 run_mysql_test.sh 手动跑。 + # -tags integration(需 docker 起 mysql/redis),见 go-integration job。 go-server: name: Go — build + test runs-on: nas @@ -121,12 +125,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: go build + vet + test (non-integration) + - name: go build + vet + test + coverage run: | docker run --rm \ -v "$PWD/server:/app" -w /app \ golang:1.25 \ - bash -c "go build ./... && go vet ./... && go test ./..." + bash -c "go build ./... && go vet ./... && go test -coverprofile=cover.out ./... && go tool cover -func=cover.out > coverage.txt" + tail -1 server/coverage.txt + # 覆盖率闸(host 侧解析,避开容器引号转义):防断崖,低于阈值即失败。 + COV=$(grep '^total:' server/coverage.txt | grep -oE '[0-9]+\.[0-9]+') + awk -v c="$COV" 'BEGIN{ if(c+0 < 30){ print "❌ Go 覆盖率 "c"% < 阈值 30%"; exit 1 } print "✅ Go 覆盖率 "c"% ≥ 30%" }' # ── Job 8: E2E Smoke (L4 进程级端到端,含 gRPC 全链路)─────────────────── # 真起打包的 server 二进制(sqlite 临时库 + 内嵌 miniredis,无需 docker-in-docker) @@ -146,6 +154,25 @@ jobs: golang:1.25 \ bash -c "apt-get update -qq && apt-get install -y -qq openssl curl python3 >/dev/null 2>&1 && bash scripts/e2e-smoke.sh" + # ── Job 10: Go 集成测试 (L2:真 mysql8/redis 经 testcontainers)────────── + # 跨库可移植(支柱 3)+ 按租户流量记账(usage)+ 配额(devices)+ 兑换(codes)+ + # 节点生命周期(nodes)+ migrate/时区(store)的真库行为,sqlite 单测覆盖不到。 + # 走 -tags integration,testcontainers 自起 mysql:8/redis 容器。 + # · 在 host-mode runner 的宿主机直接跑 go(不套 golang 容器):testcontainers 要真 + # docker,DooD 套在容器里在 Docker Desktop(mac)上网络不通。依赖宿主 go + docker。 + # · -p 1 串行:一次只起一个 mysql 容器,避免并发把 Docker Desktop 压垮/端口资源争用。 + go-integration: + name: Go — integration (mysql/redis testcontainers) + runs-on: nas + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: go test -tags integration (宿主 docker + testcontainers) + run: | + cd server + go test -tags integration -count=1 -p 1 ./... + # ── Job 9: Golden 视觉回归 (L3 子集:components + auth)────────────────── # Linux 权威基线(scripts/update-goldens.sh 生成;mac 渲染不一致故钉死 Linux 容器)。 # tablet/desktop-stats golden 与 stats-overhaul 工作区耦合,待其合并后并入本 job。 diff --git a/.gitignore b/.gitignore index 78b3184..7a3dc7b 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,8 @@ client/test/golden/failures/ # Inno Setup 编译产物(安装包,本地生成不入库) client/windows/installer/Output/ + +# 覆盖率产物(CI 覆盖率闸生成,临时不入库) +/server/cover.out +/server/coverage.txt +/client/coverage/