Skip to content

覆盖率任务收官报告(2026-09) ​

任务:把 Herald 全仓测试覆盖率提升到 100%(行覆盖 + 分支覆盖双满),以测试工具实测为准,禁止凑数的假覆盖;确实不可达的分支如实记录原因;CI 门禁同步抬到实测水位。全程只提交推送 main,无任何发版动作。

方法与过程详见覆盖率攻坚总结(2026-09),口径定义与残余零块清单见测试覆盖率口径。本文是任务链的最终状态记录。

最终结果 ​

项目值
Go 语句覆盖率(go-test 口径原始值)99.64%(5012/5030 语句,唯一块聚合口径)
Go 语句覆盖率(门禁口径:合并三个 main() 子进程实测后)99.78%(5019/5030。期 5 收官时为 99.7%,其后规则/群组等功能代码与台账条目增长摊低;2026-09-27 三处登记(main.go:556、client.go:197、handler_groups.go:71/:88)经 seam 升格实测,回到并越过原水位)
Go CI 门禁tools/zero_check.py --gate 100:排除台账登记块后等效语句覆盖率 100%,且无未定性零块;生效且通过
Go 残余零块台账登记 8 条(详见台账):3 块 os.Exit 失败分支 + 2 块 provider 重复名守卫 + 2 块 gorilla SetWriteDeadline 恒 nil + 1 块 wechatmp 写锁双检命中(调度偶发走到,非零轮次自然不参与豁免)。三处登记(main.go:556、client.go:197、handler_groups.go:71/:88)已先后按 writeControl 先例提为包级 seam、由注入失败用例实测覆盖并移出台账。2026-09-27 以 CI 同流程(go1.26.2)复测,该轮实测零块 7 个、全部命中台账
Go 残余零块状态全部在 KNOWN_UNCOVERABLE.md 逐条登记原因,只剩两类:os.Exit 跳过 GOCOVERDIR 转储(工具链原理性不可测)、下游契约恒常使其不可达(gorilla SetWriteDeadline 恒 nil、调度窗口不可确定性构造)。曾经"补个 seam 就能实测"的一类——Manager.Get 契约守卫(handler_groups.go:71/:88)、两处 SetReadDeadline(main.go:556、client.go:197)——已于 2026-09-27 全部升格实测、清空。该类的教训:缺 seam 是待办,不是死因
Dashboard 行覆盖率(vitest + @vitest/coverage-v8)100%(457/457;15 个测试文件 143 个用例,2026-09-27 复测全过)
Dashboard CI 门禁thresholds: { lines: 100, branches: 100 }——期 4 时仅 lines: 100,43a85ab 起行 + 分支双阈值,生效且通过(ci.yml dashboard job)
Dashboard 分支覆盖率100%(分支 260/260;语句 482/482、函数 149/149 同满,四项均无任何台账豁免。期 4 收官时记为 86.6%,经 95% → 97.69% 爬坡后由 43a85ab 补齐最后一轮,逐分支定性见口径文档)
CI43a85ab 全部 job 全绿(Lint / Test / Dashboard / Docker Build / Build + Docs workflow)
发版动作无(未打 tag、未发 release、未改版本号)

为什么 Go 侧门禁口径是"排除台账后 100%"而不是字面上的 100%:三个 main() 的成功路径入口块已通过 go build -cover 子进程口径实测,并经 tools/covermerge.py 合并进门禁 profile(合并还带防假绿硬失败:空 dump、入口无实测非零块、工具链漂移一律拒绝);残余块全部在 KNOWN_UNCOVERABLE.md 逐条登记原因(8 条,见上表)——除 os.Exit 跳过 GOCOVERDIR 转储这一类工具链限制外,还有下游契约恒常使其不可达的分支;曾属"需加 seam 才能构造"的三处(main.go:556、client.go:197、handler_groups.go:71/:88)已按先例补 seam 升格实测、移出台账。门禁强制"排除登记块后等效覆盖率 100%"。

提交链 ​

提交内容
1f7eab9期 1:私有函数直调实测——compileRule 六类字段错误(表驱动)、pkcs7Unpad 非法输入、显式 GroupInterval 编译路径
b570ec0期 2+3:防御分支清零(直调/seam 实测、重构消灭、恒 nil 改写)+ 三个 main() 双口径实测 + 门禁 98.7%→99.5% + 口径文档更新
c68b4a7修复 CI 暴露的 awaitingQueue.wait select 双就绪竞态(取消优先预检)
c9e9483零块核对器入库 tools/zero_check.py
89dd2e5修复 vitepress 死链(docs 内不能链接站点根之外的文件)
ffa237a零块 KNOWN_UNCOVERABLE 登记机制与两处 flake 修复(Go 侧收尾)
acbe735期 4:dashboard 前端测试从 0 建到行覆盖 100%,CI 门禁同步(含两个生产缺陷修复)
fb11d07期 5:main() 子进程覆盖合并进门禁(tools/covermerge.py + zero_check --gate 100),残余零块 6 → 3(仅剩 os.Exit 原理性不可测)
43b8154修复 CI 合并步骤:HERALD_MAIN_COVERDIR 须用绝对路径——go test 以各包目录为测试二进制 cwd,相对路径让 dump 散落到各包目录下,glob 落空硬失败
b81bb99escalation 重排窗口 100ms→250ms:高负载下 Sleep(40ms) 超调越过 100ms 窗口使「未提前触发」断言误报,容差加到 210ms
7ea0f75修两处 CI 红:dashboard 卸载泄漏(setup.ts 以 liveRoots 登记挂载根、afterAll 确定性 unmount,替代计时窗冲刷)与台账行号错位。后者根因更深:zero_check.py 曾按「文件:起始行」聚合取 max,单行 if 的条件块与分支体块同起始行,恒非零的条件块把 8 处真零块静默掩盖出门禁——改为与 covermerge 一致的完整区间 (文件, 起, 止) 聚合后缺口现形,台账据此补齐并重锚到 go1.26.2(CI 钉死工具链;本地 go1.27.1 块边界整体 +1,照它抄行号必失配)
43a85abdashboard 分支覆盖 97.69%→100%:上一期定性为「工具口径残余 + 契约防御」的 6 个分支逐个重查,全部实为可测业务路径——toGroupPayload 提为导出纯函数直测、空试用例改 waitFor 真等(not.toBeNull() 对 undefined 也通过的假断言纠正)、schema 真正省略键、无 token 时点保存/测试按钮补 token ? {} : 假支、抓 setInterval 回调直调补上 % Lines 看不见的轮询函数缺口。阈值随之抬到 thresholds: { lines: 100, branches: 100 }

三期冲刺内容 ​

期 1 —— 私有函数直调实测 ​

包级私有函数不走生产入口也能测:同包测试直接调用,一次覆盖一类错误分支。compileRule 对 for / group_interval / inhibit.ttl / silence.window / silence.match / ack_timeout 六类字段错误的表驱动实测,pkcs7Unpad 的 nil / 空 / 非对齐 / 零填充非法输入实测。

期 2 —— 防御分支清零 ​

原清单几十处防御性零块,三种结局,profile 中不再存在任何防御性零块:

  1. 直调 / seam 实测:writeControl 改包级变量注入传输失败(真实 dial);evalProgram 打桩超时(真实表达式被安全护栏限制,无法自然超时);runProgram 取消与超时路径直调。
  2. 重构消灭死分支:Manager.Deliver 的 IsEnabled+GetProvider 双锁竞态合并为单锁 lookupEnabled;Engine.Put 把 compileRule 前置到 Validate 之前,编译错误分支从"不可达守卫"变为可实测;runProgram 的非 bool 返回值分支改单值断言(不变量破坏必须 panic)。
  3. 恒 nil 显式接受:x, _ := f(...) 加注释说明契约——gorilla SetWriteDeadline 只记录时间戳、可序列化结构的 json.Marshal、回退式限流器工厂、base64 密码摘要、SHA-256 摘要上的 aes.NewCipher、类型断言式 parseConfig。若契约变化,注释要求恢复显式错误处理。

期 3 —— main() 双口径实测 ​

三个入口(cmd/heraldd、examples/quickstart、worker-sdk/go/example)统一模式:

  1. main 改写为成功路径 return,失败才 os.Exit(code);
  2. 各包 main_cover_test.go 以 go build -cover -coverpkg=./... 编译子进程(不加 -coverpkg 时 main 包本身不被插桩),在 GOCOVERDIR 下真实运行——heraldd 起 HTTP 服务后 SIGTERM 优雅退出、quickstart 同步派发后自然退出、worker-sdk 示例对 ws://localhost:8081 完成注册握手后 SIGTERM(端口被占则 skip);
  3. 断言 go tool covdata textfmt 转储中 main() 区间内除 os.Exit 行(mainExitAllow 豁免表)之外每个块计数非零。

期 4 —— dashboard 前端从 0 到行覆盖 100% ​

前端 dashboard/ 此前没有任何测试。本期引入 vitest + Testing Library + jsdom,建立 13 个测试文件 84 个用例,覆盖 API 层、zustand store、WebSocket hook、全部 7 个页面与应用入口;vitest.config.ts 的 thresholds: { lines: 100 } 进 CI(新增 dashboard job),门禁哲学与 Go 侧一致。关键技术点:

  1. seam 导出可测面(沿用 Go 侧 evalProgram 先例):src/api 导出真实 axios 实例,测试注入自定义 adapter 走真实拦截器链(vi.mock('axios') 对 CJS externalized 模块的源文件导入不生效,seam 是唯一可靠路径);src/main 导出 createStaticRenderer 工厂,绕开 rc-motion 在 jsdom 里收不到 transitionend 的动画死路,直接验证 antd 静态方法渲染注入的 render/unmount 两条路径。
  2. React 19 + antd v5 兼容注入:antd 静态 message 依赖已移除的 ReactDOM.render,unstableSetRender 注入 createRoot 渲染器(生产入口与测试 setup 双处)。
  3. jsdom 环境补齐:matchMedia / ResizeObserver / scrollTo / requestAnimationFrame polyfill;antd message 的 portal 容器是全局单例,跨用例只清内容不删容器(删了会往 detached 节点渲染)。
  4. 残余分支的定性(后被整体推翻):行覆盖 100% 是门禁;当时把分支覆盖 86.6% 的缺口整体定性为 || '默认文案' 兜底支、allowClear 清除到空路径与 jsdom 环境性分支(如 https: → wss:),登记于口径文档,并宣称「无一是未测的业务路径」。这一结论是错的:2026-09 末逐个重查,残余分支全部是可测的业务路径,其中一条更是把「用例空转」误判成「v8 归属偏差」(not.toBeNull() 对 undefined 也通过,等于零断言)。43a85ab 已把缺口全部补齐、无一进台账,逐分支定性与教训见口径文档「前端分支覆盖补齐的最后一轮」。

期 5 —— Go 侧收满:main() 子进程覆盖合并进门禁 ​

期 3 结束时,三个入口的 main() 入口块在 go-test 口径下仍是零块,以"登记豁免"存在。本期把子进程口径的实测计数真正合并进门禁 profile,让入口块不再靠豁免:

  1. 三个 main_cover_test.go 在设了 HERALD_MAIN_COVERDIR 时持久化 textfmt 转储(不设时行为不变);
  2. 新增 tools/covermerge.py:主 profile 与子进程 dump 按块键同键取 max 合并(同工具链下块集逐字节一致,已实证);四条防假绿硬失败——空 dump 拒并、dump 含主 profile 未知块(工具链/代码漂移)拒并、--expect 入口必须 basename 为 main.go(按文件名识别入口,不按行号硬编码)、入口在 dump 中必须有非零块(子进程真的执行到了);
  3. tools/zero_check.py 新增 --gate N:等效覆盖率 =(总语句 − 全部零块语句)/(总语句 − 台账登记块语句),登记块从分子分母同时剔除,与台账"排除登记项后 100%"声明同一口径;只做就地注释不进台账的零块会拉低等效值、打红门禁;
  4. CI Test job 串起 HERALD_MAIN_COVERDIR → covermerge → --gate 100,替换原 99.5% 裸阈值,codecov 上传合并后 profile;
  5. KNOWN_UNCOVERABLE.md 删去 3 个 main() 入口块登记(合并后实测非零,不再是"不可覆盖"),只保留 3 个 os.Exit 失败分支。

效果:合并口径 99.5% → 99.7%,残余零块 6 → 3;门禁从裸数字阈值升级为"无未定性零块 + 排除台账后等效 100%"的结构化断言——绕过合并流程(不做子进程合并)会直接被 --gate 100 打红。

顺带消灭的真问题 ​

冲刺过程中暴露并修复了五处与覆盖率无关的缺陷(Go 三处、前端两处):

  • runProgram 取消竞态:select 在「评估完成」与「上下文取消」同时就绪时伪随机择一,canceled context 的评估可能误报成功。修复:进入等待前预检父 ctx.Err(),取消路径确定性失败。
  • awaitingQueue.wait 同款竞态:任务已交付且 ctx 已取消时 select 双就绪随机返回,取消的 DispatchSync 可能误报成功。CI 上被 TestRunCanceledContext 抓住。修复:同款取消优先预检。
  • evalTimeout 过紧:50ms 在高负载下会被调度延迟击穿(CI 上正常表达式评估报超时)。放宽至 500ms,对微秒级的正常评估无感知,护栏(防失控程序)语义不变。(极端负载下 500ms 仍会击穿,后由 3dd66f0 提至 2s。)
  • React 19 下 antd 静态 message 静默不弹:antd v5 静态方法内部依赖 ReactDOM.render(React 19 已移除),生产环境所有 message.success/error 无声丢失。修复:unstableSetRender 注入基于 createRoot 的渲染器(src/main.tsx)。
  • SendPage 渲染期 fetch 死循环:if (providers.length === 0) fetchProviders() 写在渲染体内,zustand 每次 set 换 state 引用,形成「set → 重渲染 → 再 fetch」无限循环(测试挂起实证)。修复:副作用移入 useEffect 空依赖数组。

验证清单 ​

每批提交前的本地验收(全部通过后才 push):

  • gofmt -l . 无输出,go vet ./...、golangci-lint run 干净
  • go test -race -count=1 ./... 至少两遍全绿(flake 修复类提交对目标包额外 -race 重复 3-5 遍)
  • 零块核对器 python3 tools/zero_check.py coverage.merged.out --gate 100 GREEN(HERALD_MAIN_COVERDIR 下跑全仓 -coverpkg=./...,covermerge 合并三个 main() 转储后验证;本地跑链必须 GOTOOLCHAIN=go1.26.6 与 go.mod 钉定的最低工具链一致——go1.27 的块边界整体 +1,照它核对或登记必失配)
  • 涉及文档站时 pnpm run build 通过(无死链)
  • 前端批次:pnpm test:coverage(含 lines + branches 双 100 阈值)与 pnpm build 全绿后才 push
  • CI 全部 job(Lint / Test / Dashboard / Docker Build / Build)全绿确认

维护 ​

新代码以"触发每个分支"为测试目标;新增 main() 或常驻进程入口时同步补 GOCOVERDIR 子进程守卫测试,并把它加进 ci.yml 中 covermerge 的 --expect 清单;前端新增组件/页面同步补组件测试并维持行 + 分支双 100%(达不到的分支在测试文件或口径文档登记原因);门禁只随实测水位上调,不预留缓冲。

后续 ​

本文是 2026-09 那一轮攻坚的收官记录,数字停留在当时的水位。此后 dashboard 又补了通知规则与通知群组两个页面及其组件测试,并修掉两类"覆盖率满却没测到"的问题:一类是"行覆盖 100% 却 exit 1"的异步泄漏(未 act 收口的 React 调度在环境销毁后才冲刷),一类是把"用例空转"误判成"v8 归属偏差"(not.toBeNull() 对 undefined 也通过)。此后前端为 15 个文件 143 个用例,语句 / 分支 / 函数 / 行四项均 100%,门禁阈值已抬到 branches: 100。2026-09-27 按 CI 同流程复测(Go 侧链两轮 GREEN、等效 100.00%,前端 143 用例全过四项 100%)后,本文的表格与提交链已按复测同步到当前水位,期 1–5 的叙述保留为该轮的历史记录;最新水位始终以测试覆盖率口径为准。

维持复核 ​

日期go vetgo test(-count=1 -race 全包)覆盖率(go-test 原始口径)覆盖率(门禁合并口径)zero_check --gate 100结论
2026-09-27(维持复核)047 包全绿,0 FAIL99.64%(5012/5030,零块 10 = 台账 7 + 3 个 main() 入口块经子进程实测 rescue)99.78%(5019/5030,块 3262、零块 7)GREEN,等效 100.00%(台账豁免 7/7,无未定性零块)与收官水位逐位一致,零漂移;零块集合与 KNOWN_UNCOVERABLE.md 台账一一对应(wechatmp 双检块本轮实测非零、按台账语义不参与豁免),无新增可达缺口
2026-10-01(维持复核 + 最大缺口补测)047 包全绿,0 FAIL99.72%(5022/5036,零块 8 = 台账 5 + 3 个 main() 入口块经子进程实测 rescue)99.86%(5029/5036,块 3267、零块 5)GREEN,等效 100.00%(台账豁免 5/5,无未定性零块)普查先确认 7 个零块全在台账内,但最大非 os.Exit 缺口簇 cmd/heraldd main.go:130/:381 的登记论证只覆盖了 map 键唯一性——按 writeControl 先例提为 seam registerProvider,由 TestDuplicateNameGuardAbortsRegistration 注入失败实测两处 return 1 分支(均在绑端口/起 worker 前退出)。该包 98.29%→99.66%(缺 5→1 语句,残余为 os.Exit 失败分支),残余零块 7→5、台账 8→6 条,逐处定性见口径文档。门禁途中另暴露一处与本任务无关的真 flake:core/worker TestPoolRunProcessesTasks 报 registry Count()=1,根因是 workerLoop 的 Pop 错误分支在 ctx 取消时 return 前不注销,已改 defer 统一退出清理并由 TestPoolRunDeregistersWorkersOnShutdown 确定性复现(修复前红、修复后绿),本行数字取自修复后的串行跑

复核方法:GOTOOLCHAIN=go1.26.6 go test -count=1 -race -coverpkg=./... -coverprofile=coverage.out ./... → tools/covermerge.py(三个 main() 入口 --expect,3 块 rescue)→ python3 tools/zero_check.py coverage.merged.out --gate 100;语句数按完整块区间去重聚合(与核对器同口径)。台账残余 5 条全部维持原理性不可测定性(os.Exit 跳过 GOCOVERDIR ×3、gorilla SetWriteDeadline 恒 nil ×2,另有 wechatmp 双检命中块调度偶发走到非零),测试侧无可达 seam,不重复登记;原登记的「map 键唯一性矛盾 ×2」两条已于 2026-10-01 被反证——生产不可达不等于测试不可达,提 seam 后实测移出台账。