Files
deepseek-harness/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md
T
Chinesezjc a813b487ab docs(coverage): update Agent Note for Windows 4-partition alignment
The PR changes native Windows coverage partitions from 8 to 4 to reduce
vitest worker startup pressure under high self-hosted concurrency. Sync the
implemented Agent Note (EN/ZH) so the decision record no longer says Windows
is fixed at 8, and revise the same-partition-count alternative accordingly.
2026-08-25 13:17:22 +08:00

7.3 KiB

Agent Note: 单 job 分区覆盖率

Status: implemented

English | 中文

问题

原生 Windows 覆盖率是拉取请求完整清单中反馈最慢的路径。把插桩套件保留在单个 Vitest 进程内并只使用 1 个 worker,可以避开较大进程内 worker 池曾出现的 worker 丢失和 Node 24 CJS lexer 故障,但一次失败可能超过 14 分钟才会显现,而且门禁调度器会在子进程结束前扣住输出。

这项优化必须保留全部测试以及合并后的逐文件 100% 阈值,也必须留在既有覆盖率 job 内:若把同一套件拆到多个工作流 job,就会向必需拓扑增加 checkout、安装、产物传输和合并 job。

决策

普通的 pnpm run test:coverage 命令仍只启动一次 Vitest。Linux 覆盖率 CI 将 DSH_COVERAGE_PARTITIONS 固定为 4;原生 Windows 现在也固定为 4,以降低自托管高并发下的进程创建压力。运行期间不会由任何耗时触发器改变这两个数量。覆盖率豁免重型套件仍作为独立的无插桩门禁与插桩工作并排运行。

启用分区后,scripts/run-gates.ts 为插桩门禁选择 pnpm run test:coverage:partitionedscripts/coverage-partitions.ts 按配置数量并发启动 Vitest 子进程,每个进程只用 1 个 worker,并各自接收一个 --shard=<index>/<count> 选项。分区模式会在各子进程中关闭阈值与覆盖率报告器,为每个子进程分配独立报告目录,并让每个进程写出 1 份 blob 报告。

协调器等待全部子进程结束,验证 blob 目录只包含预期文件,然后执行一次 vitest --merge-reports ... --coverage。只有这条合并命令应用仓库的逐文件语句、分支、函数与行阈值,因此系统不会拿有意不完整的测试清单单独判定任一分区。

DSH_COVERAGE_MAX_WORKERS 继续控制无插桩豁免门禁和普通非分区路径的规模,不会调整分区子进程。原生 Windows 为豁免门禁分配 2 个 worker,并允许 4 道外层门禁并发。工作区构建与生产网站验证会立即启动;两道覆盖率门禁都等待完整构建。插桩套件包含针对已构建 lib/ 输出的打包器断言,因此这项依赖可避免它读取只完成部分产出的包闭包,也可避免豁免门禁的临时 Oxlint 探针与源码编译竞态。观测性清单只等待两道覆盖率门禁结算,因此在覆盖率失败后仍会运行;各门禁自身的 needs 依赖仍要求前置门禁通过。Linux 让 4 个插桩分区进程与 2 个豁免 worker 重叠运行,在保持每个插桩进程只有 1 个 worker 的同时,恢复普通路径原有的 4 路插桩并发。

失败与输出语义

分区子进程通过协调器流式传递 stdout 与 stderr。覆盖率门禁选择 run-gates 流式输出,因此测试进度与失败会在发生时进入 CI 日志,调度器不会缓冲完整日志。协调器还会为每个子进程保留一份有界的 64 KiB 混合输出尾部;子进程以失败状态结算时,它会打印 spawn 错误、退出码或信号,并在校验完整 blob 集合前重印这份尾部,使具体 Vitest 失败与最终分区诊断相邻。

普通测试失败仍通过 --coverage.reportOnFailure 产出 blob,使合并步骤可以先报告完整覆盖率状态,再由协调器返回失败。spawn 失败、信号终止、非零退出、blob 缺失或多余,以及合并失败都会让门禁失败。协调器只删除自己拥有的覆盖率目录树;若该路径是链接,则只 unlink,不递归跟随。

验证

scripts/coverage-partitions.spec.ts 固定了参数构造、包脚本分隔符移除、单 worker 分区、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。scripts/run-gates.spec.ts 固定了显式启用、非法数量拒绝、两道原生 Windows 覆盖率门禁对完整构建的依赖、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 act() 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。

已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒;这些数据来自先前的门禁顺序,只用于比较分区延迟,不代表当前峰值。当前的构建后阶段会让 4 个插桩分区进程与 2 个豁免 worker 并行,共形成 6 个覆盖率执行单元。若改为 16 个分区,则在尚未结束的生产网站工作或系统开销计入之前,该阶段就会达到 18 个执行单元。4 个分区保留独立进程隔离并与 Linux 对齐,代价是单 job 覆盖率墙钟更长;这是为了降低自托管高并发下 vitest worker 启动失败而接受的取舍。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。

曾考虑的替代方案

使用工作流级分片。 不予采用,因为多个 job 会重复设置工作,并需要上传、下载产物以及合并依赖。所选分区方案只在同一个 job 和工作区内使用多个进程。

提高单个插桩进程内的 Vitest worker 数。 不予采用,因为已完成的 Windows 高扇出试验暴露了 worker 退出、fixture(测试前置数据)不稳定和 Node 24 CJS lexer 故障。相互独立的单 worker 进程既保留隔离,也能让所选分区并发执行。

在每种宿主上使用相同的分区数量。 先前不予采用,因为 Linux 的 4 进程运行与 Windows 的 8 进程运行具有不同的启动成本与资源上限。本次变更在 Windows 高并发运行暴露 8 分片 worker 启动失败后,将两者统一为 4 分区。

在每个分区内独立应用阈值。 不予采用,因为每个分区有意只看到套件的一部分,会误报未覆盖文件。阈值归合并报告所有。

后果

每个分区都要支付 1 次 Vitest 启动与配置开销,最后还要执行 1 次报告合并,但它不引入另一套工作流拓扑,并保留唯一的最终阈值判定。分区输出可能交错,但分区启动标签和 Vitest 文件标识仍可用于归因。

Linux 与 Windows 使用相同的协调器,并各自设置分区数量与外围 worker 预算。原生 Windows 会在完整构建之后启动两道覆盖率门禁,因为其插桩语料可能消费构建产物;Linux 的专用覆盖率 job 不会与同一工作区中的并发构建共享目录。本地覆盖率默认保持简单;只有调用方显式选择分区包脚本并提供大于 1 的合法数量时,才启用分区。

未来调优从一个固定配置的完整运行开始。进度缓慢本身绝不会提高分区数量或外层并发,因为反复重启会抹掉选择稳定设置所需的唯一证据。