docs(gates): 主机单测套数复点同步——41 套 / 无专属钩子 19 套

第 41 套 boot_stats 由 728d6067(2026-10-27 11:29)入库,全仓写的
「40 套 / 18 套无专属钩子」从那天起过期;本轮按实测复点同步。

真值双源互证:test/host/*/run_unit_test.py 实数 41(43 个目录,除
common 与 __pycache__)= scripts/check_test_coverage.py 自报
suites=41/41。专属钩子 22 根(YAML 解析 id 以 -unit-test 结尾,
22 条全对得上有 runner 的目录)⇒ 无专属钩子 19 套 = 41 − 22。

改当前态共 18 处:AGENTS.md 提交门禁节三处、.pre-commit-config.yaml
test-coverage 注释、docs/门禁说明/host-unit-tests.md §6 第 4 条六处
(无钩子名单首位补 boot_stats、目录数 42→43、裁定句改「裁定当时
18 套,2026-10-28 现值 19 套」)、docs/门禁说明/README.md、
docs/代码规范/pre-commit使用说明.md 两处、docs/编号规范.md、
scripts/README.md、docs/任务规划/分工说明-20261028 一条、
docs/测试说明/主机单测覆盖率口径.md 追加 2026-10-28 = 41 套 ~105 s
新实测点、test/host/sys_log_mcu/sys_log_mcu_test.c 注释行。

刻意不动:带日期戳的历史耗时与套数行(只追加新的实测点,不覆写)、
host-unit-tests.md 里「22 根 / 40 套 / 18 套无专属钩子」这句复核原文
(只在后面追加更正)、scripts/archive/id_gate/ 留档两处、
同数不同义的五处(40KB 级 / −20~40% / 不到 40 行 / 18 断言点 /
板端 RTC 套件)。钩子数 51、专属 22 根、提交期实跑 48 根本轮实测
未变,相关句一字未动。

门禁:doc-links --all --sections-strict / id-check-new --worktree /
no-mojibake / bom-drift / validate-config 全 rc=0,
test/host/sys_log_mcu 单测 rc=0;改后复扫「40 套」仅剩 3 处
(1 处刻意保留 + 2 处留档)、「42 个目录」0 处、「18 套」仅剩 2 处
(都带「当时」口径)。
This commit is contained in:
2026-10-29 00:58:23 +08:00
parent 295eb9911f
commit 41670a1cc6
10 changed files with 20 additions and 18 deletions
+1 -1
View File
@@ -745,7 +745,7 @@ repos:
# 口径 / 分母 / 基线 / 盲区详解都在 docs/测试说明/主机单测覆盖率口径.md,本处只留接线事实。
# 触发面**含门禁自身**(.pre-commit-config.yaml):不带自身就等于谁改坏这根闸的 files:
# 模式,它就再也不跑且无人察觉(同 lv-conf-sync / bsp-err 的既有做法)。
# ⚠ stages: [manual](2026-10-28 用户裁定):本钩子跑光全部 40 套单测(实测 ~105 s,
# ⚠ stages: [manual](2026-10-28 用户裁定):本钩子跑光全部 41 套单测(实测 ~105 s,
# 占一次带代码提交耗时的三分之二)→ 不压在提交期,由**远端 CI** 强制
# (.gitea/workflows/ci.yml 的「手动阶段门禁(覆盖率总闸 + 全量 cppcheck)」步),本地等价入口 =
# scripts/ci_local.py(manual 段);手工复跑:
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -32,7 +32,7 @@ git commit
└─ manual(pre-commit --hook-stage manual;不随 git commit 跑)
├─ cppcheck 全量 C 静态分析(自有代码)
├─ test-cppcheck test/ 侧静态分析
└─ test-coverage 主机单测覆盖率总闸(40 套 + 回退台账)
└─ test-coverage 主机单测覆盖率总闸(41 套 + 回退台账)
```
**阶段**:`pre-commit` 阶段拦截代码问题、`commit-msg` 阶段拦截格式问题,任一失败 → commit 中止;另有 `manual` 阶段(`stages: [manual]` 的慢钩子)**不随 `git commit` 跑**,由远端 CI 与 `scripts/ci_local.py` 的 manual 段承担(见下)。一次 `git commit` 实跑 **48** 根 = `pre-commit` 阶段 47 根 + `gitlint` 1 根。
@@ -332,7 +332,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
| 文件 | 位置 | 说明 |
|---|---|---|
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**51 个钩子**声明数,一次 `git commit` 实跑 48 根;口径 = YAML 解析后的 hook 条目数,自查命令 → `docs/门禁说明/README.md` §2。原写「40 个,2026-10-24 清点」是当时的快照数、之后加钩子没同步——正是 §2.2 计数口径行要避免的事)。2026-10-28 复核 = 51:同日 51 → 52(加 id-check-new)、随即 52 → 51(退役 id-check),一增一减写成当日一行的两个阶段、净零。逐项详解在 `docs/门禁说明/`(索引 `docs/门禁说明/README.md`;主机侧单测 **22 根**合一篇,含各套件来历与「18 套无专属钩子」台账 `docs/门禁说明/host-unit-tests.md` §6;UI 侧另有 `font-charset` 与 `ui-golden` 两篇——后者此前**在旧手册里连小节都没有**,只活在配置注释里,本次补齐) |
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**51 个钩子**声明数,一次 `git commit` 实跑 48 根;口径 = YAML 解析后的 hook 条目数,自查命令 → `docs/门禁说明/README.md` §2。声明数与一次提交的实跑数分列,勿混写;计数改动的完整台账只记在 AGENTS.md 提交门禁节与本目录 README §2,本行不重述)。逐项详解在 `docs/门禁说明/`(索引 `docs/门禁说明/README.md`;主机侧单测 **22 根**合一篇,含各套件来历与「19 套无专属钩子」台账 `docs/门禁说明/host-unit-tests.md` §6;UI 侧另有 `font-charset` 与 `ui-golden` 两篇) |
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
| `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) |
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
@@ -20,7 +20,7 @@
- **交付物**:
1. **口径 = YAML 解析后的 hook 条目数**:用 `& ".venv\Scripts\python.exe"` 解析 `.pre-commit-config.yaml` 取实数,与 `AGENTS.md` 的 **51** 做 **声明 / 文档 / 实跑** 三数同源核对(**注释掉的候选钩子不计**,直接 grep 声明行会多算 1);
2. `docs/门禁说明/README.md` 的 **篇数 ↔ 钩子数** 差集清单,核对 **27 篇 + 1 指针** 是否仍成立;
3. 有差异 **只改文档、不加钩子**(Lead 裁定:`test/host/` 40 套里只有 22 根专属钩子,**没有专属钩子 ≠ 没被跑**,余量由 `test-coverage` 总闸兜);新条目标识用 **语义短语**,**不再发字母+数字编号** → `docs/编号规范.md` §1。
3. 有差异 **只改文档、不加钩子**(Lead 裁定:`test/host/` 41 套里只有 22 根专属钩子,**没有专属钩子 ≠ 没被跑**,余量由 `test-coverage` 总闸兜);新条目标识用 **语义短语**,**不再发字母+数字编号** → `docs/编号规范.md` §1。
- **禁止**:手工全局替换改名/挪目录(走 `scripts/rename_ref.py`,先 dry-run);章节引用写成非紧邻形态(`doc-links` 带 `--sections-strict` 拦提交);在配置注释里复述那个 grep 字面量(文本计数再虚高 1)。
## 共用约束(A/B 都要守)
@@ -76,7 +76,9 @@
退出码:`0` 全过且无回退 / `1` 有单测失败或覆盖率回退 / `2` 环境错误(无 cl、无 mscoverage、
编译失败、XML 缺失)/ `3` 用法错误。全量实测约 **67 s**(33 套 × 编译 + 插桩运行;
2026-10-25 实测;16 套时约 25 s、28 套时约 45 s,套件数涨了耗时就涨,别拿旧数当预算)。
2026-10-25 实测;16 套时约 25 s、28 套时约 45 s,套件数涨了耗时就涨,别拿旧数当预算)。最新一次实测:
2026-10-28 = 41 套 **~105 s**(该钩子同日移入 `stages: [manual]`,由远端 CI 与 `ci_local.py` 的 manual 段强制)。
套件数的现值与逐个来历见 `docs/门禁说明/host-unit-tests.md` §6 第 4 条。
中间产物(exe / PDB / Cobertura XML)一律落 `%TEMP%\dtu_host\<工作树哈希>\coverage\<模块>`
(与主机单测同根、按树分家,2026-10-25 起;改根用 `--work-dir` 或 `DTU_HOST_BUILD`),**不进仓库**,
+1 -1
View File
@@ -85,7 +85,7 @@
> - ❌「不做 = 维持原状 = 还能发」——**错**。新用例一律用**语义化测试函数名 + 文档用例表的行名**(§3.3),
> 不再配 TC 号;指认**既有**用例仍可引用现存号(旧号只读,不是许可新配)。
> - ❌「停发 = 这族要废了,顺手清掉、把号腾出来复用」——**也错**。现存号与它在 `scripts/archive/id_gate/id_registry.json`
> 里的登记,是 `check_test_traceability.py` 与 40 套 `run_unit_test.py` **按字面量读的追溯把手**,
> 里的登记,是 `check_test_traceability.py` 与 41 套 `run_unit_test.py` **按字面量读的追溯把手**,
> 清它等于换机制(`编号治理历史.md` §9.1);号不回收、不复用是 §1 的硬规矩。
> - ❌「registry 里 TC 族还带着活游标,所以门禁允许我发」——**不成立:这条缺口已于 2026-10-27 同日关掉**。
> 37 个 TC 类族现在**全部** `frozen: true`,其中 **34 个 `TC-HOST-*` 子族的 `cursor` 冻结在裁定日该套件存量
+2 -2
View File
@@ -1,6 +1,6 @@
# 门禁逐项说明(每种门禁一篇)
> 这一目录回答一个问题:**声明的 51 个钩子到底在干什么、拦谁、放谁、什么时候会骗人**——其中 3 根慢钩子带
> 本目录逐项说明各门禁:**声明的 51 个钩子到底在干什么、拦谁、放谁、什么时候会骗人**——其中 3 根慢钩子带
> `stages: [manual]`,一次 `git commit` 只实跑 48 根(口径见下条)。
> 每个门禁一篇,八段固定结构:查什么 / 为什么要它 / 触发面 / 报错怎么读 / 好处 /
> **限制与盲区** / 豁免与基线 / 改门禁本身要注意什么。**最后一节是这套文档存在的理由**——
@@ -74,7 +74,7 @@
| [`setview-check.md`](setview-check.md) | `setview-check` | 定值查看行表与 `prot_set` 枚举序 / `g_prot_set_meta` 上下限是否同一份口径 | `tools/setview_check/**`、`ui_w_setview.*` | 已写 |
| (专篇待补,档位与限度正文在 [`../UI设计/UI_自动化测试流程.md`](../UI设计/UI_自动化测试流程.md) §7.3) | `uinav-selftest` | 板端 UI 导航器认屏快检(黄金参考帧分类;完整闭环档=合入前/上板前手工跑,快检不互替) | `scripts/board_ui_nav.py`、`simulator/tests/ref/*.bin` | 指针 |
| [`host-unit-tests.md`](host-unit-tests.md) | 22 个 `*-unit-test` | 主机侧单测(同一机制 22 个实例):逻辑层在无硬件的 PC 上编译并跑断言 | 各自被测模块与其测试目录 | 已写 |
| 详解在 [`../测试说明/主机单测覆盖率口径.md`](../测试说明/主机单测覆盖率口径.md)(口径 / 分母 / 基线 / 盲区都在那篇,本目录不重复) | `test-coverage` | **覆盖率总闸**:跑全部 40 套主机单测 + 已覆盖行只增不减的回退台账 | `App/`、`Drivers/BSP/`、`test/host/`、脚本与基线 | 已写(在 `docs/测试说明/`;`stages: [manual]`) |
| 详解在 [`../测试说明/主机单测覆盖率口径.md`](../测试说明/主机单测覆盖率口径.md)(口径 / 分母 / 基线 / 盲区都在那篇,本目录不重复) | `test-coverage` | **覆盖率总闸**:跑全部 41 套主机单测 + 已覆盖行只增不减的回退台账 | `App/`、`Drivers/BSP/`、`test/host/`、脚本与基线 | 已写(在 `docs/测试说明/`;`stages: [manual]`) |
| [`trailing-whitespace.md`](trailing-whitespace.md) | `trailing-whitespace` | 行尾空白(改写型;CubeMX/Keil 生成文件豁免) | 全部 | 已写 |
| [`end-of-file-fixer.md`](end-of-file-fixer.md) | `end-of-file-fixer` | 文件末尾必须恰好一个换行(改写型;同豁免) | 全部 | 已写 |
| [`check-added-large-files.md`](check-added-large-files.md) | `check-added-large-files` | 别把大文件(日志/镜像/二进制)误提进仓库 | 全部 | 已写 |
+7 -7
View File
@@ -88,30 +88,30 @@
3. `include_dirs` 与源文件清单是**手写**的:新依赖一个目录就会假失败(见 §4 第 2 类),反过来,
漏了某个真实依赖可能让测试**编过却少编了一个翻译单元**(少测而不自知)。
4. **专属钩子不是唯一防线,而「没有专属钩子」也不等于「没被跑」**。2026-10-26 按 git 历史与实测重新
清点(上一版写「16 个模块有专属钩子、另有 12 套、总闸跑 28 套」——三个数都随 2026-10-25 的
清点(2026-10-28 复点:下列数字已按复点值更新;上一版写「16 个模块有专属钩子、另有 12 套、总闸跑 28 套」——三个数都随 2026-10-25 的
六根新钩子与后续新增套件过期了,正是本目录反复在防的"文档与机器面不同码"):
- **22** 根专属钩子 = 有独立 `files:` 触发面、需要**秒级归因**的那个子集;
- `test/host/` 下带 `run_unit_test.py` 的套件实测 **40** 套(42 个目录,除 `common` 与
`__pycache__`)⇒ **没有专属钩子的是 18 套**:`cfg_file_store_mcu`、`console_log`、
- `test/host/` 下带 `run_unit_test.py` 的套件实测 **41** 套(43 个目录,除 `common` 与
`__pycache__`)⇒ **没有专属钩子的是 19 套**:`boot_stats`、`cfg_file_store_mcu`、`console_log`、
`ftp_cfg`、`iec104_serv`、`lftpd`、`lftpd_inet`、`lftpd_io`、`net_cfg`、`net_events`、
`net_listener`、`net_pending`、`net_select`、`net_socket`、`prot_set_store`、`soe_store`、
`sys_log_mcu`、`task_prof`、`ui_sink`;
- 这 18 套每次 CI 与 `scripts/ci_local.py` 都由 `test-coverage` **总闸**真跑(该钩子带 `stages: [manual]`:
- 这 19 套每次 CI 与 `scripts/ci_local.py` 都由 `test-coverage` **总闸**真跑(该钩子带 `stages: [manual]`:
`git commit` 阶段不跑它,这一根与 `cppcheck` / `test-cppcheck` 同属 CI 与 `ci_local.py` 的 manual 段);
发现规则是
`HOST_TEST_DIR.rglob("run_unit_test.py")`(与 `scripts/ci_local.py` 的主机侧纯逻辑单测同一口径),
退出码语义写死「0 = 全部单测通过且覆盖率相对基线没回退 / 1 = 有单测失败或覆盖率回退」⇒
**失败照样拦(CI 与 `ci_local.py` 的 manual 段)**。差的只是归因粒度(要在总闸输出里认出是哪套)与耗时(40 套全量比单根慢得多);
**失败照样拦(CI 与 `ci_local.py` 的 manual 段)**。差的只是归因粒度(要在总闸输出里认出是哪套)与耗时(41 套全量比单根慢得多);
- 要不要给某套补第 23 根,判据现成:pre-commit 4.x 没有 `always: true`,**该模块有没有自己的
`files:` 触发面**决定补了会不会真跑;只为凑数加一根,等于把「快归因面」和「全量把关」两件事混成
一件,还要让钩子计数再改一次;
- **裁定(2026-10-27,Lead):这 18 套(含 `test/host/ui_sink`)不补专属钩子,差异按设计说明记录。**
- **裁定(2026-10-27,Lead):这批无专属钩子套件(裁定当时 18 套,2026-10-28 现值 19 套;含 `test/host/ui_sink`)不补专属钩子,差异按设计说明记录。**
理由不是省事:22 根专属钩子存在的唯一意义是**秒级归因**;而 `ui_sink` 改动面小,它的失败面全部
落在 `test-coverage` 总闸的必跑路径里,坏了照样在 CI / `ci_local.py` 拦下。再加一根只是多一个必须「声明数 / 文档数 /
实跑数」三处同源同步的计数点——本仓 2026-10-26 到 27 一天之内就为 40 / 50 / 51 这三个数不同源
返工过两轮。**要读「这套跑没跑」,读总闸输出,不要来数钩子。** 两个方向的空头账一起堵住:
① 别把没接线的套件算进钩子账(原写「23 个 / 含 ui-sink」正是这么错的;2026-10-27 复核的准数是
22 根 / 40 套 / 18 套无专属钩子);② 也别把总闸的绿读成「每套都被单独归因过」——它只保证
22 根 / 40 套 / 18 套无专属钩子——第 41 套 `boot_stats` 当日 11:29 才入库,此后两数是 41 / 19,以本条上面的实测行为准);② 也别把总闸的绿读成「每套都被单独归因过」——它只保证
「全跑过 + 覆盖率没回退」。将来确实要给某套补专属钩子,先过上一条判据,并在**同一笔**里改
AGENTS 与门禁索引的计数(口径 = YAML 解析条目数,见 `docs/门禁说明/README.md` §2 首条)。
- 总闸的触发面是 `App/`、`Drivers/BSP/`、`test/host/`、覆盖率脚本与基线,**外加门禁自身**
+1 -1
View File
@@ -44,7 +44,7 @@
| `ci_local.py` | **本地跑与 CI 等价的门禁**(静态门禁 pre-commit 47 根 + **manual 段**(`cppcheck` / `test-cppcheck` / `test-coverage` 三根逐根 `--hook-stage manual`,默认跑)+ 提交信息门禁 + **体积门禁** + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由体积门禁兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 |
| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的体积门禁步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** |
| `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → 体积门禁与 CI 同时拦红 |
| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 40 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;挂在 `test-coverage` 钩子上(`stages: [manual]`,**`git commit` 不跑**),入口 = 远端 CI「手动阶段门禁」步 / `scripts/ci_local.py` 的 manual 段(`--coverage` 旗标保留只为兼容旧命令;口径见 `docs/测试说明/主机单测覆盖率口径.md`) |
| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 41 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;挂在 `test-coverage` 钩子上(`stages: [manual]`,**`git commit` 不跑**),入口 = 远端 CI「手动阶段门禁」步 / `scripts/ci_local.py` 的 manual 段(`--coverage` 旗标保留只为兼容旧命令;口径见 `docs/测试说明/主机单测覆盖率口径.md`) |
| `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) |
| `ci_find_python.ps1` | CI 侧 Python 探测(候选顺序须与 `ci_local.py` 同序同步,否则 CI 与本地判定会分叉) |
| `ci_runner_bootstrap.ps1` | **本机 runner 运维脚本**(体检 `-Status` / 干跑 `-DryRun` / 部署 `-SetEnv -StartNow`):装"等 Gitea 就绪 + 崩溃自拉起"的看门狗 + 开机计划任务 + 机器级 `DTU_*` |
+1 -1
View File
@@ -193,7 +193,7 @@
* (s_entry_fill / s_put / syslog_post_quiet / syslog_post_dropped)与 26 行
* 可执行代码**全部**有断言跑到:覆盖行 +26、函数 +4,未覆盖仍是同一批 3 行老账
* (一条死分支 + sys_log_evidence 里 snprintf<=0),**一条没涨**。
* ⚠ 全量门禁(40 套并集 + 基线只增不减)由提交前那一次统一跑,本行只是自量记录。
* ⚠ 全量门禁(41 套并集 + 基线只增不减)由提交前那一次统一跑,本行只是自量记录。
* L 族变异实测(2026-10-26,三处同时注入后逐字节还原):
* ① s_paths[] 里 accid 与 alarm 两格**对调**(就是"表序错"那个静默错法);
* ② 给 quiet 的丢弃分支补一条 `DBG_ERROR`(= 悄悄退化成 syslog_post);