feat(门禁): 主机单测覆盖率脚本与口径落地(销报告 §4.8 空白 ①)
落地物: - scripts/check_test_coverage.py:发现规则与 L0.5 同一条(test/host/**/ run_unit_test.py),直接 import 各运行器复用其 TestBuild 与 unit_test_harness._BASE_FLAGS —— 配置零复制,不会漂出第二套口径; 只算自研生产源码(App/**、Drivers/BSP/**)的行/函数、多套取并集、 生成物(文件头写"自动生成/请勿手改")不进分母、分母一律来自插桩 报告不猜行数;中间物全在 %TEMP%,不新增 .gitignore 规则。 - scripts/test_coverage_baseline.json:增量拦截台账(只拦回退不拦进步, 与 layer-deps / bsp-err 同姿态);ci_local.py 加 opt-in --coverage。 - docs/测试说明/主机单测覆盖率口径.md:口径 + 工具链实测 + 九条盲区。 - 报告 §0.1 / §0.4 / §4.8 / §5.5 / §7 改写 + 变更记录 + 去处表。 工具链是实测出来的(换机必读):Windows 原生侧无任何 gcc/gcov(全盘扫 Program Files*、msys64、scoop、choco、.platformio、LocalAppData = 0 处); MSVC 14.51 已砍 /coverage,且只报 D9002 后照常编译成功 = 静默假数据; CodeCoverage.exe 17.0 三个子命令只打印用法。最终用 mscoverage 18.x (Microsoft.CodeCoverage.Console.exe)collect -f cobertura 做加载期动态 插桩,并实测它透传子进程退出码 → "插桩运行下仍必须 PASS"可当硬条件。 首登数字:16/16 套插桩下全绿,并集 line=2552/2893(88.2%)、 func=193/207(93.2%)、19 个文件在测、**未触及 125 个生产源文件 / 约 11,839 行**——最后一栏才是这套口径真正照出来的东西。 门禁自证:人为抬高基线后 行/函数/总计 三条回退路径都报 REGRESS 且 退出码 1;--module 子集跑不再假报总计回退(首版会,已修:并集口径下 子集必然变小,全部回退判定只在全量跑时生效)。 GCC 追问的复测结论(口径文档 §2.1 决策记录):本机 WSL2 Ubuntu 确实有 gcc/gcov 15.2.0,16 套单测在 gcc 下全部编过、用例数与 MSVC 逐个一致, gcov -b -c 也确实给出分支覆盖(nand_ftl.c 分支至少走过一次 41.40%); 但换编译器=换分母(sha256.c gcc 105 行 vs MSVC 114 行;adc_input.c 101 vs 120)。**用户裁定:本仓只留 MSVC 一条覆盖率通道**,不建第二条—— 多一套工具链就多一套分母、多一本台账、多一个"这台机器到底有没有 WSL/ gcov"的未决项,换来的只有分支这一维。分支覆盖因此**记为已接受缺口** (不是待办):绿灯只证明行与函数被测到,补偿判据 = TC 双向追溯 + 用例显式列边界值;§8 写明重开该裁定的前提是先答"两份分母怎么对齐"。 刻意未挂 pre-commit:16 个单测钩子已经跑过同一批测试;依赖 VS 可选 组件(缺组件时 skip 会静默失效、FAIL 会拦换机);待一个真实回退事件。 YAML 片段与理由写在口径文档 §8。 自查:pre-commit run --files(6 个改动文件)全 Passed;check_doc_links --all 回到存量 35 处(本分支贡献 0);check_ids.py --strict PASS; check_mojibake + BOM/CR/U+FFFD/行尾卫生逐项自查全净。本轮零 .c/.h 改动 (纯 .py/.json/.md),故未跑 @build、未占 build/bench 锁。
This commit is contained in:
@@ -10,6 +10,9 @@
|
||||
覆盖范围(与 CI 完全一致):
|
||||
L0 静态门禁 = `pre-commit run --all-files` + 提交信息门禁(gitlint)
|
||||
L0.5 主机侧纯逻辑单测 = `test/host/**/run_unit_test.py`
|
||||
L0.5c 覆盖率(**opt-in:`--coverage`**,2026-10-23)= `scripts/check_test_coverage.py`
|
||||
默认**不跑**、CI 也**不跑**:它依赖 VS 可选的「代码覆盖率工具」组件,且与 L0.5 那 16 个
|
||||
钩子跑的是同一批测试(覆盖率只是换一种看法)。口径与盲区 → docs/测试说明/主机单测覆盖率口径.md
|
||||
|
||||
⚠️ gitlint 必须**单独跑**(2026-10-11 审计):`stages: [commit-msg]` 写在 gitlint **上游**
|
||||
的 `.pre-commit-hooks.yaml`(项目 `.pre-commit-config.yaml` 里查不到这一行),而
|
||||
@@ -332,6 +335,30 @@ def run_l05(args):
|
||||
return rc_all
|
||||
|
||||
|
||||
def run_coverage():
|
||||
"""覆盖率附加步骤(opt-in,CI 不跑):主机单测覆盖率 + 基线增量门禁。
|
||||
|
||||
单独一步而不并进 L0.5:L0.5 是**必须绿**的门禁,覆盖率是**趋势视图**,两者失败含义不同
|
||||
(前者=测试挂了,后者=覆盖回退了 / 缺工具链)。退出码原样透传,由 main 汇总。
|
||||
"""
|
||||
print("")
|
||||
print("=" * 64)
|
||||
print(" L0.5c 主机单测覆盖率(mscoverage 动态插桩 + 基线增量门禁)")
|
||||
print("=" * 64)
|
||||
script = REPO_ROOT / "scripts" / "check_test_coverage.py"
|
||||
if not script.is_file():
|
||||
print("[WARN] 缺 scripts/check_test_coverage.py —— 跳过该步")
|
||||
return 0
|
||||
rc = _run([PY, str(script)])
|
||||
if rc == 2:
|
||||
print(" (退出码 2 = 环境错误:多半没装 VS 的「代码覆盖率工具」组件,或需设")
|
||||
print(" DTU_MS_COVERAGE。口径与排障 → docs/测试说明/主机单测覆盖率口径.md)")
|
||||
elif rc == 1:
|
||||
print(" (退出码 1 = 有单测失败,或已覆盖行/函数相对基线回退;确认无误后")
|
||||
print(" 跑 scripts/check_test_coverage.py --update-baseline 重登基线)")
|
||||
return rc
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
@@ -344,6 +371,8 @@ def main(argv=None):
|
||||
g = ap.add_mutually_exclusive_group()
|
||||
g.add_argument("--l0-only", action="store_true", help="只跑 L0 静态门禁")
|
||||
g.add_argument("--l05-only", action="store_true", help="只跑 L0.5 主机侧单测")
|
||||
ap.add_argument("--coverage", action="store_true",
|
||||
help="额外跑主机单测覆盖率(约 25 s;需 VS 代码覆盖率组件;默认不跑,CI 也不跑)")
|
||||
ap.add_argument("--keep-going", action="store_true",
|
||||
help="某步失败也继续跑后续步骤(默认首错即停)")
|
||||
ap.add_argument("--commits", type=int, default=10, metavar="N",
|
||||
@@ -388,6 +417,8 @@ def main(argv=None):
|
||||
if not args.l0_only:
|
||||
rc05 = run_l05(args)
|
||||
rc = rc or rc05
|
||||
if args.coverage:
|
||||
rc = rc or run_coverage()
|
||||
except FileNotFoundError as e:
|
||||
print("[ERROR] 环境错误:{}".format(e))
|
||||
return 2
|
||||
|
||||
Reference in New Issue
Block a user