ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DeepSeek Harness 覆盖率门禁实战:用自定义 istanbul reporter 输出精确未覆盖位置

DeepSeek Harness 覆盖率门禁实战:用自定义 istanbul reporter 输出精确未覆盖位置 DeepSeek Harness 覆盖率门禁实战用自定义 istanbul reporter 输出精确未覆盖位置【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harnessper-file 100% 覆盖率门禁是 DeepSeek Harnesseverything-is-a-plugin 架构的 Agent 编排框架持续集成的硬性底线但门禁失败时 vitest 只报文件、不报行号CI 红报几乎无法直接处理。本指南以仓库内一条已实现implemented的工程决策笔记为主线完整讲解其自定义 istanbul reporterscripts/coverage-uncovered-locations.cjs从问题定位、方案选型、接线方式、输出约定到验证方法的全部细节读完你既能直接复用该工具定位覆盖率缺口也能掌握在 vitest istanbul 生态中扩展自定义报表的完整套路。问题门禁知道哪个文件没达标却不知道差在哪几行DeepSeek Harness 在根 vitest.config.ts 的 coverage 块中对全部packages/*/*/src/**/*.{ts,tsx}启用了逐文件perFile100% 门槛语句、分支、函数、行四类指标全部要求 100%thresholds: coveragePartitionMode ? undefined : { perFile: true, statements: 100, branches: 100, functions: 100, lines: 100, },per-file 语义很严格一个覆盖良好的大文件不能补贴一个近乎空白的小文件。然而当某个文件触发门槛失败时vitest 输出的只有文件级错误行ERROR: Coverage for lines (…) does not meet global threshold (100%) for file这条信息能告诉你哪个文件掉了链子却不能告诉你具体差在哪些行。内置的text报表虽然有 Uncovered Line #s 列但它是覆盖全仓几百个文件的巨型表格存在四个致命缺陷该列按表宽截断长路径和长行号直接看不全只给行号、不给列号大文件内仍难以精确定位不区分语句statement、分支branch、函数function三类缺口达标文件同样占行噪音巨大。最终结果正如笔记所描述CI 上的覆盖率红报无法直接据此处理唯一的定位办法是在本地重跑一遍 html 报表。这正是本方案要消除的痛点。决策一个自定义 istanbul reporter输出可直接点击的单行记录核心方案是新增 scripts/coverage-uncovered-locations.cjs——一个继承istanbul-lib-report中ReportBase的自定义 reporter。它的工作方式如下对每个低于 100%的文件为每一个未覆盖语句、每一条未走到的分支路径、每一个未被调用的函数各输出一条自含的单行记录记录格式为path:line:col uncovered kind …在终端与 CI 日志中可直接点击跳转也便于 grep 过滤当所有文件全部达标时零输出绿色运行保持安静istanbul 的报表生成发生在 threshold 校验之前因此这些记录恰好落在既有 ERROR 行的正上方阅读顺序自然顺畅。源码级拆解三类记录如何生成reporter 的核心逻辑在onDetail(node)回调中对每个文件分别遍历三类映射表for (const id of Object.keys(fc.statementMap)) { if (fc.s[id] ! 0) continue; // 已覆盖计数非 0的语句跳过 const loc fc.statementMap[id]; if (!usable(loc)) continue; add(loc, ${rel}:${pos(loc)} uncovered statement${endSuffix(loc)}); }语句遍历statementMap凡fc.s[id]执行计数为 0 即视为未覆盖函数遍历fnMapfc.f[id]为 0 即为未调用优先使用fn.decl声明位置不可用时回退到fn.loc并附带函数名便于识别const name fn.name ? ${fn.name} : ; add(loc, ${rel}:${pos(loc)} uncovered function${name});分支遍历branchMap对每条分支路径逐一检查计数未走到的路径输出uncovered branch (type, path k/n)标注分支类型如if、switch与路径序号k/n。onEnd()负责汇总输出先打印一行Uncovered locations (per-file 100% gate): count作为小节标题再逐行打印记录。若records为空则整体静默——这就是全绿零输出的实现机制。接线全仓唯一的覆盖率配置点CI 与本地共用该方案的关键工程决策是单点接线。根 vitest.config.ts 的 coverage 块是全仓唯一的覆盖率配置被三类运行方式共同消费CI lanecheck:ci:coverage→tsx scripts/run-gates.ts ci-coverage见根 package.json内部由coverageGates()scripts/run-gates.ts执行pnpm exec vitest run --coverage本地命令test:coverage→vitest run --coverage根 package.json聚焦运行通过--coverage.include只对指定文件测覆盖率。reporter 以绝对路径同时加入 CI 与本地两个 reporter 数组const uncoveredLocationsReporter fileURLToPath(new URL(./scripts/coverage-uncovered-locations.cjs, import.meta.url)) // ... reporter: coveragePartitionMode ? [] : process.env.CI ? [text, uncoveredLocationsReporter] : [text, html, uncoveredLocationsReporter],这里有一个容易被忽视的坑必须用fileURLToPath转成绝对路径。原因在笔记中有明确说明——istanbul-reports 的create()对非内置 reporter 名会回退为裸require(name)如果传相对路径会按照istanbul 自己的包目录去解析必然找不到文件。用绝对路径才能确保解析到仓库内的脚本。另外注意CI 环境process.env.CI下 reporter 数组是[text, uncoveredLocationsReporter]本地则是[text, html, uncoveredLocationsReporter]——html 报表只在本地生成CI 只依赖 text 与自定义输出。输出约定五条细节保证日志可用性reporter 的输出细节并非随意为之每条约定都对应一个真实的工具链问题全部可在 scripts/coverage-uncovered-locations.cjs 源码中逐一核对0 基列号转 1 基istanbul 内部列号从 0 开始而编辑器与终端链接处理约定是 1 基。pos()函数统一执行loc.start.column 1function pos(loc) { return ${loc.start.line}:${loc.start.column 1}; }v8 的end.column Infinity降级v8 对整行语句给出的结束列号是Infinity无实际列语义。endSuffix()中检测到非有限列号时跨行的跨度降级为只带行号的(to line)后缀单行跨度则直接省略后缀只有结束位置确实携带额外信息时才输出完整的(to line:col)。隐式分支臂的位置回退分支的隐式臂例如缺少else的情况可能不带位置信息此时 reporter 回退到分支自身的 span保证记录仍然可点击跳转同时保留path k/n标注。记录排序同文件内按行、再按列排序items.sort((a, b) a.line - b.line || a.column - b.column)输出次序稳定可预期。不设条数上限整文件零覆盖时输出条数与文件语句数同阶。这是刻意为之——门禁要求零缺口全量列出本身就是行动清单vitest 自身的 ERROR 行已按文件汇总作为兜底不会因长输出丢失文件级摘要。为什么必须是 CJSESM-everywhere 纪律的一个有据例外DeepSeek Harness 整体遵循 ESM-everywhere 的工程纪律但这个 reporter 文件被迫使用 CommonJS这是有明确技术依据的例外而非偷懒istanbul 在 tsx/Vite 流水线之外用裸require()装载自定义 reporterTypeScript 在这一环节完全无法参与编译require(esm)返回的是命名空间对象无法通过 istanbul 的new Cons(cfg)构造调用因此 ESM 同样不可行结论CommonJS 是唯一可靠形态。脚本开头的大段注释也如实记录了这一点CommonJS by requirement: istanbul-reports loads custom reporters with a bare require() outside the tsx/ESM pipeline把为什么破坏纪律写成了代码级文档。配套改动依赖与 hygiene 门禁可见性方案还附带两处配套改动缺一不可根 package.json 新增 devDependencyistanbul-lib-report在 pnpm 的严格依赖布局下scripts/目录摸不到嵌套依赖无法依赖 vitest 传递引入的 istanbul 包必须显式声明为根 devDependency。knip.json 的 entry/project 通配增加scripts/**/*.cjs使该 CJS 文件及其依赖对仓库的 hygiene 门禁未使用依赖/未导出符号检查可见避免新文件被误报为死代码。考虑过的替代方案为什么最终选自定义 reporter笔记明确记录了三条被否决的路线理解它们能帮你判断何时该复用本方案依赖内置text报表的 Uncovered Line #s 列这正是问题现状本身——全仓大表、列宽截断、只有行号、不分缺口种类、达标文件同列无法在 CI 日志中直接处理等于没解决。加jsonreporter 包装脚本后处理coverage-final.json纯 ESM/TS 可行但包装脚本必须同时包住package.json的test:coverage与 run-gates 的 gate两个入口命令形状随之改变而自定义 reporter 路线只动一处配置两个入口自动生效——这正是单点接线的收益。用 TypeScript/ESM 写 reporter被 istanbul 的裸require()装载机制直接否决见上文为一个报表文件去替换 istanbul 的装载机制代价不成比例。验证本地矩阵 CI 实证方案的验证采用本地矩阵 CI 实证双轨本地矩阵故意制造未达标时三类记录语句/分支/函数齐全、位置与埋点一致混合运行只输出未达标文件同一次运行中 100% 的文件保持静默全绿运行零输出、退出码 0。CI 实证临时在clampTimeout埋入一处不可达语句/分支/函数后coverage lane 在全部测试通过632 文件 / 10326 用例、仅 threshold 失败的隔离条件下把 4 条记录打印在 ERROR 行上方埋入的失败代码并未进入已提交的代码树验证了工具在真实门禁下的表现。后果与边界覆盖率红报自足日志直接给出精确行列号与缺口种类不再需要本地重跑 html 报表定位——这是本方案最大的收益。代价可控一个 CJS 文件的纪律例外 一个根 devDependency全绿运行零输出不增加任何日志噪音。边界明确整文件零覆盖时输出与语句数同阶刻意不设上限门禁要求零缺口全量列出即行动清单。如需进一步了解项目覆盖率策略的全局设计包括豁免机制、分区模式、Windows 平台排除等可继续阅读根 vitest.config.ts 的 coverage 块与 docs/testing.md若要在自己的 vitest 项目中复用该方案只需复制coverage-uncovered-locations.cjs、按fileURLToPath绝对路径接线、并显式声明istanbul-lib-report依赖即可。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表