test(ci): 第6刀 — CI gate 补全(覆盖率闸 A1 + 集成测试 job A2)

A 类「闸的完整性」:

- A1 覆盖率闸(防断崖,非追高):
  · go-server job 加 -coverprofile,host 侧解析 total,< 30% 失败(当前 34.9%)。
  · flutter-client job 加 --coverage,host 侧 awk 解析 lcov,< 28% 失败(当前 32.7%)。
  阈值留 ~5% 缓冲,只拦回归断崖,不卡日常波动。

- A2 集成测试进 CI(新 go-integration job,Job 10):
  真 mysql:8/redis 经 testcontainers 跑 -tags integration(跨库可移植/按租户
  记账/配额/兑换/节点生命周期/migrate·时区——sqlite 单测覆盖不到的真库行为)。
  · 在 host-mode runner 宿主机直接跑 go(不套 golang 容器):testcontainers 要真
    docker,DooD 在 Docker Desktop(mac)上网络不通;依赖宿主 go + docker。
  · -p 1 串行:一次只起一个 mysql 容器,避免并发压垮 Docker Desktop。

covers/ 覆盖率产物入 .gitignore。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-25 21:19:34 +08:00
parent 0fd3bce7a5
commit f8f4ee0ce8
2 changed files with 37 additions and 5 deletions
+32 -5
View File
@@ -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。
+5
View File
@@ -76,3 +76,8 @@ client/test/golden/failures/
# Inno Setup 编译产物(安装包,本地生成不入库)
client/windows/installer/Output/
# 覆盖率产物(CI 覆盖率闸生成,临时不入库)
/server/cover.out
/server/coverage.txt
/client/coverage/