瀏覽代碼

fix(ci): B-40 后端测试软门禁升级两级硬门禁

- 拆分 test-blocking(全绿子集,必须过,红即拦)与 test-observed
  (33 个已知红类 allow_failure,仅产出 junit 趋势)
- backend-blocking-junit 用 -Dtest='!<observed>' 排除已知红类,其余全跑
  (含全绿 integration 类,即首批转入 blocking)
- 修好一个类即从 BACKEND_OBSERVED_CLASSES 移除,下轮自动转入 blocking
- 所有 job 同 test stage,observed 在 blocking 红时仍产出趋势
- langgraph-pytest 转 blocking(B-34/35/37 闭环后 51p/12s/0f)
- e2e-playwright / tests-jest 转 observed(环境依赖)
- 加 B-39 守卫:surefire 0 个 XML 即失败,防 skipTests 假绿
E2E Test Bot 5 天之前
父節點
當前提交
180aebcb0b
共有 1 個文件被更改,包括 98 次插入 和 52 次删除
  1. 98 52
      .gitlab-ci.yml

+ 98 - 52
.gitlab-ci.yml

@@ -3,40 +3,47 @@
 # -----------------------------------------------------------------------------
 # 落地:2026-11-06(最小 CI 管线搭建)
 # 追加:2026-11-07(EVD-6 持续回归:契约 JUnit / langgraph pytest / Playwright e2e / tests jest)
+# 重设计:2026-11-07(B-40 修复——CI 软门禁升级为「两级硬门禁」)
 # 前置:remote = http://git.iwintrue.com/liaoxg/cfc(GitLab 系自托管)
-# 本机实测(2026-11-06~07,processor JDK8+Maven3.9.9/node22):
-#   - 后端 G0 编译  : mvn test-compile -DskipTests=false → BUILD SUCCESS (2046+231 源)
-#   - 后端契约/单测 : mvn test -DskipTests=false -Dtest='!**/integration/**' → 1794 例实测跑通
-#   - cfc-frontend jest : 14 suites / 296 例全绿
-#   - cfc-web jest      : 12 suites / 90 例全绿
-#   - langgraph pytest  : tests/ 11 文件 / 47 方法可拉起(24 pass / 19 fail 为既有 schema 漂移)
-#   - Playwright e2e    : 74 spec 文件 / 2418 用例收集成功;ci-smoke.spec.js headless 冒烟 1 passed
+#
+# ███ B-40 两级门禁设计(2026-11-07 落地,替代原 4 个 allow_failure 软门禁)███
+#   - test-blocking(全绿子集):必须过。任一 blocking job 红 → 流水线红。
+#       含 backend-g0-compile / frontend-jest / web-jest / backend-blocking-junit
+#          / langgraph-blocking-pytest。
+#   - test-observed(已知红 / 环境依赖):allow_failure: true,失败不拦截,
+#       但始终产出 junit 趋势(artifacts when: always),供测试侧逐轮跟踪。
+#       含 backend-observed-junit / e2e-playwright-observed / tests-jest-observed。
+#   - 「integration 包按类白名单逐批转入 blocking」机制:
+#       * 后端 blocking 用 `-Dtest='!${BACKEND_OBSERVED_CLASSES}'` 排除「已知红类」,
+#         其余全跑——即所有**当前全绿**的 integration 类已自动进入 blocking(首批);
+#       * 每修好一个红类,就从 BACKEND_OBSERVED_CLASSES 移除该类 → 下轮自动转入
+#         blocking(逐批增量);blocking 覆盖例数随之上升,直至 observed 清空。
+#   - 覆盖口径:blocking 覆盖 = 全量 − observed(已知红)≥ 当前全量 80%
+#     (当前红类 33 类 / 红例 115 例 [failures+errors,第 69 轮干净 HEAD 实测],
+#      blocking 覆盖例数 ≥92%,满足 B-40 闭环条件)。
 #
 # 🔴 关键坑(必须保留):cfc-backend/pom.xml 第 28 行 <skipTests>true</skipTests>,
 #    直接 `mvn test` 会**静默跳过全部测试(假绿)**。因此后端 job 必须显式
-#    `-DskipTests=false`,否则 CI 绿灯是假的。
+#    `-DskipTests=false`,并加「0 XML 即失败」守卫(B-39)。
 #
 # 🔴 启用前置(本机实测,runner 侧待平台核实):
 #    1) runner 需 JDK8 + Maven + node18/20/22 + npm + Python3.11+ + 可拉依赖网络
-#    2) 本文件默认 when: manual(首次先手动触发跑通);确认 runner 能力后
-#       将各 job 的 `when: manual` 改为 `when: on_success`(或删除该行走默认自动)
+#    2) blocking job 已改 `when: on_success`(自动触发,硬门禁);首次跑通前若
+#       runner 能力未备,可临时将 blocking job 改回 `when: manual` 人工验证一次,
+#       但「红即拦」语义不变(blocking 均未设 allow_failure)。
 #    3) 若 runner .m2 已预热且无外网,可在 mvn 命令加 `-o`(离线)
-#    4) e2e-playwright 需 runner 具备 chromium 系统依赖(或 root 跑
+#    4) e2e-playwright-observed 需 runner 具备 chromium 系统依赖(或 root 跑
 #       `npx playwright install --with-deps chromium`)
-#    5) tests-jest 依赖 npm>=10 解析 "npm:@vue/babel-plugin-jsx@^1.1.1"(node22 自带 npm10)
+#    5) tests-jest-observed 依赖 npm>=10 解析 "npm:@vue/babel-plugin-jsx@^1.1.1"(node22 自带 npm10)
 # -----------------------------------------------------------------------------
 # 设计说明(G0/G1 分层基线):
 #   - G0 编译门禁(backend-g0-compile,必绿,最快信号)
-#   - G1 各套件分层回归:
-#       * backend-contract-junit:契约/单测(排除需 DB 的 integration 包);
-#         存在既有契约漂移红(如 StatsControllerTest 500 vs 5001),
-#         allow_failure 兜底,拍板后移除
-#       * langgraph-pytest:schema 漂移既有 19 红(respondent_name 等),allow_failure 兜底
-#       * e2e-playwright:**CI 安全模式**——只做 `--list` 收集校验 + headless 冒烟;
-#         真实 UI e2e 依赖部署环境 + DB(globalSetup 会重置 192.168.16.251 测试库,
-#         **严禁**在 CI 开启 playwright.config.js 默认 globalSetup)
-#       * tests-jest:依赖 cfc-frontend(vue)依赖,npm10 才能装
-#   - 前端/管理端用 jest --ci(已验证全绿,无外部依赖)
+#   - G1 两级回归(B-40):
+#       * test-blocking:全绿子集(前端 jest / 管理端 jest / 后端非红类 / langgraph pytest)
+#       * test-observed:已知红或环境依赖(后端红类 / e2e CI 安全模式 / tests jest)
+#   - e2e-playwright-observed:**CI 安全模式**——只做 `--list` 收集校验 + headless 冒烟;
+#     真实 UI e2e 依赖部署环境 + DB(globalSetup 会重置 192.168.16.251 测试库,
+#     **严禁**在 CI 开启 playwright.config.js 默认 globalSetup)
 # =============================================================================
 
 stages:
@@ -45,6 +52,13 @@ stages:
 
 variables:
   MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+  # ---------------------------------------------------------------------------
+  # 后端「已知红类」(test-observed 白名单,逗号分隔的简单类名)。
+  # 依据:第 69 轮全量实跑(干净 HEAD)红类快照(33 类,surefire-reports 提取)+ B-38 归因;
+  # 修好一个类就从下方列表移除一个 → 该类下轮自动转入 test-blocking(逐批增量)。
+  # 说明:`-Dtest='!${BACKEND_OBSERVED_CLASSES}'` 只排除这些类,其余全跑(含全绿 integration 类)。
+  # ---------------------------------------------------------------------------
+  BACKEND_OBSERVED_CLASSES: "AdminFamilyOverviewControllerTest,ArticleControllerTest,ArticleWorkflowTest,AuthControllerTest,CfCommissionServiceSettleTest,DailyCheckinStreakFlowTest,DimensionRegistryMapContractTest,DimensionScoreServiceImplTest,EnergySystemFlowTest,FamilyAdminOverviewServiceTest,FamilyControllerTest,FamilyJoinRequestServiceReferrerBindTest,FortuneServiceTest,GrowthRecordSyncFlowTest,GuideFamilyTaskControllerTest,GuideModuleIntegrationTest,GuideOrderControllerTest,GuideRolePermissionTest,GuideTeamControllerTest,MembershipPaySwitchTest,MindFortuneControllerTest,OrchestrationEngineTest,OrchestrationIntegrationTest,PaymentControllerTest,PaymentServiceORDRoutingTest,PlannerBindInviteFlowTest,ProductControllerTest,ProductOrderControllerTest,ProductOrderServiceFulfillmentMultiModeTest,ProductOrderServiceUpdateExpressFieldTest,StatsControllerTest,UserServiceFamilyNameLinkTest,WishControllerTest"
 
 cache:
   key: "$CI_COMMIT_REF_SLUG"
@@ -55,11 +69,11 @@ cache:
     - tests/node_modules/
 
 # ---------------------------------------------------------------------------
-# Job 1 — 后端 G0 编译门禁
+# Job 1 — 后端 G0 编译门禁(test-blocking)
 # ---------------------------------------------------------------------------
 backend-g0-compile:
   stage: build
-  when: manual                 # 首次手动;确认 runner 能力后改 on_success
+  when: on_success              # B-40:硬门禁自动触发(红即拦)
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -73,14 +87,13 @@ backend-g0-compile:
     expire_in: 7 days
     paths:
       - cfc-backend/target/classes/
-  # 注:契约/单测复验(G1)暂由本地轮次承担(P-2 3 红未拍板,见 CI_READINESS)
 
 # ---------------------------------------------------------------------------
-# Job 2 — 小程序前端(cfc-frontend)jest 单测
+# Job 2 — 小程序前端(cfc-frontend)jest 单测(test-blocking)
 # ---------------------------------------------------------------------------
 frontend-jest:
   stage: test
-  when: manual                 # 首次手动;确认 runner 能力后改 on_success
+  when: on_success              # B-40:硬门禁自动触发(红即拦)
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -99,11 +112,11 @@ frontend-jest:
       - cfc-frontend/coverage/
 
 # ---------------------------------------------------------------------------
-# Job 3 — 管理端(cfc-web)jest 单测
+# Job 3 — 管理端(cfc-web)jest 单测(test-blocking)
 # ---------------------------------------------------------------------------
 web-jest:
   stage: test
-  when: manual                 # 首次手动;确认 runner 能力后改 on_success
+  when: on_success              # B-40:硬门禁自动触发(红即拦)
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -122,26 +135,55 @@ web-jest:
       - cfc-web/coverage/
 
 # ---------------------------------------------------------------------------
-# Job 4 — 后端契约/单测 JUnit(G1,G0 编译门禁的测试侧补充)
+# Job 4 — 后端契约/单测 JUnit · test-blocking(全绿子集,必须过)
 # ---------------------------------------------------------------------------
 #   - 必须 -DskipTests=false 覆盖 pom:28 假绿(否则 mvn test 静默跳测 = 假绿)
-#   - 排除 **/integration/**(46 个 @SpringBootTest 中需内网 MySQL 的类在 CI 无法跑)
-#   - 既有失败:契约漂移(expected 500 vs 5001 等)为 P-2 已知红,allow_failure 兜底
+#   - 仅排除 BACKEND_OBSERVED_CLASSES(已知红类);其余全跑,**含全绿 integration 类**,
+#     即「integration 按类白名单逐批转入 blocking」的首批。
+#   - B-39 守卫:surefire 0 个 XML 视为假绿,直接失败(红即拦)。
+# ---------------------------------------------------------------------------
+backend-blocking-junit:
+  stage: test
+  when: on_success              # B-40:硬门禁自动触发(红即拦)
+  cache:
+    key: "$CI_COMMIT_REF_SLUG"
+    paths:
+      - .m2/repository
+  script:
+    - cd cfc-backend
+    # 必须 -DskipTests=false(pom.xml:28 默认 true = 假绿);只排除已知红类,其余全跑
+    - mvn -B test -DskipTests=false "-Dtest=!${BACKEND_OBSERVED_CLASSES}"
+    # B-39 守卫:0 个 surefire XML = 零执行假绿 → 失败
+    - test "$(find target/surefire-reports -name 'TEST-*.xml' | wc -l)" -gt 0 || (echo "B-39 假绿守卫:surefire 未产出任何 TEST-*.xml" && exit 1)
+  artifacts:
+    when: always
+    expire_in: 7 days
+    reports:
+      junit:
+        - cfc-backend/target/surefire-reports/TEST-*.xml
+    paths:
+      - cfc-backend/target/surefire-reports/
+
+# ---------------------------------------------------------------------------
+# Job 4b — 后端「已知红类」JUnit · test-observed(allow_failure,产出趋势)
+# ---------------------------------------------------------------------------
+#   - 只跑 BACKEND_OBSERVED_CLASSES(红类),allow_failure 不拦截;
+#     每轮产生 junit 供测试侧看红例数是否下降(趋势)。
+#   - 修好一个类 → 从 BACKEND_OBSERVED_CLASSES 移除 → 下轮自动转入 blocking。
 # ---------------------------------------------------------------------------
-backend-contract-junit:
+backend-observed-junit:
   stage: test
-  when: manual                 # 首次手动;确认 runner 能力后改 on_success
-  allow_failure: true          # 既有契约漂移红(StatsControllerTest 500 vs 5001 等)未拍板
+  when: on_success
+  allow_failure: true           # 已知红,失败不拦截,仅产出趋势
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
       - .m2/repository
   script:
     - cd cfc-backend
-    # 必须 -DskipTests=false(pom.xml:28 默认 true = 假绿);排除需 DB 的 integration 包
-    - mvn -B test -DskipTests=false -Dtest='!**/integration/**'
+    - mvn -B test -DskipTests=false "-Dtest=${BACKEND_OBSERVED_CLASSES}"
   after_script:
-    - echo "契约/单测结果见 surefire-reports" || true
+    - echo "observed 红类趋势见 surefire-reports(红例数应逐轮下降)" || true
   artifacts:
     when: always
     expire_in: 7 days
@@ -152,17 +194,17 @@ backend-contract-junit:
       - cfc-backend/target/surefire-reports/
 
 # ---------------------------------------------------------------------------
-# Job 5 — cfc-langgraph pytest(G1,AI 服务侧)
+# Job 5 — cfc-langgraph pytest · test-blocking(B-34 闭环后全绿,硬门禁)
 # ---------------------------------------------------------------------------
 #   - conftest 自动 import app.main(需全量依赖,requirements_frozen.txt 锁版本)
 #   - --ignore=tests/qna(test_graph.py 引用不存在的 src.qna → collection error)
-#   - 既有失败 19 红(schema 漂移:respondent_name/member_name、MemoryManager 签名等)
-#     allow_failure 兜底,待测试侧修复后移除
+#   - B-34/B-35/B-37 修复后 67 轮起实测 51 passed / 12 skipped / 0 failed → 转硬门禁。
+#   - 注意:tests_contract/ 下的 B-41 flaky(TestGrowthReportInterpretB04)不在本 job
+#     的 `tests/` 范围内,不影响门禁;该 flaky 由测试侧单独跟踪(B-41)。
 # ---------------------------------------------------------------------------
-langgraph-pytest:
+langgraph-blocking-pytest:
   stage: test
-  when: manual
-  allow_failure: true          # 既有 schema 漂移 19 红(respondent_name 等)待修复
+  when: on_success              # B-40:硬门禁自动触发(红即拦)
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -183,17 +225,20 @@ langgraph-pytest:
       - cfc-langgraph/junit.xml
 
 # ---------------------------------------------------------------------------
-# Job 6 — Playwright e2e(G1,前端侧,CI 安全模式)
+# Job 6 — Playwright e2e(CI 安全模式)· test-observed(allow_failure,产出趋势)
 # ---------------------------------------------------------------------------
 #   ⚠️ 严禁在 CI 使用 tests/playwright.config.js(其 globalSetup 会重置远程
 #      192.168.16.251 测试库)。CI 采用安全模式:
 #      1) `npx playwright test --list`                 — 校验 74 spec / 2418 用例可收集
 #      2) `-c playwright.config.smoke.js ci-smoke`      — headless chromium 真实拉起冒烟
 #   真实 UI e2e 依赖部署环境 + DB(cfc.iwintrue.com),由测试负责人手动跑。
+#   B-40:真实 e2e 未授权/环境依赖 → 归 test-observed(allow_failure),
+#         但 test-results 每轮归档供趋势跟踪。
 # ---------------------------------------------------------------------------
-e2e-playwright:
+e2e-playwright-observed:
   stage: test
-  when: manual                 # 首次手动;确认 runner 能力后改 on_success
+  when: on_success
+  allow_failure: true           # 环境依赖(部署环境+DB),失败不拦截,仅产出趋势
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths:
@@ -212,17 +257,18 @@ e2e-playwright:
       - tests/test-results/
 
 # ---------------------------------------------------------------------------
-# Job 7 — tests/ 目录 jest(unit + integration,G1 补充)
+# Job 7 — tests/ 目录 jest(unit + integration)· test-observed(allow_failure)
 # ---------------------------------------------------------------------------
 #   - tests/jest.config.js 依赖 cfc-frontend/__tests__/setup.js(vue),
 #     需先装 cfc-frontend 依赖;npm>=10 才能解析 "npm:@vue/babel-plugin-jsx@^1.1.1"
 #     本机 npm9.2.0 验证失败(node22 自带 npm10 可装)
-#   - integration 部分需后端 API(localhost:9082)在 CI 无后端时仅跑能跑的
+#   - integration 部分需后端 API(localhost:9082)在 CI 无后端时仅跑能跑的;
+#     当前 B-36 下根级黑盒 23 例全 skip → 归 test-observed(allow_failure)。
 # ---------------------------------------------------------------------------
-tests-jest:
+tests-jest-observed:
   stage: test
-  when: manual
-  allow_failure: true          # 需 npm10 + cfc-frontend(vue) 依赖;integration 需后端
+  when: on_success
+  allow_failure: true           # 需 npm10 + cfc-frontend(vue) 依赖;integration 需后端;B-36 全 skip
   cache:
     key: "$CI_COMMIT_REF_SLUG"
     paths: