diff --git a/AGENTS.md b/AGENTS.md index 9d25cd15..c410c175 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,7 +91,7 @@ ## 提交门禁(pre-commit) -- 任一失败即拦截,共 **50 个钩子**(**口径 = YAML 解析后的 hook 条目数**:自查命令与「声明 / 文档 / 实跑」三数同源核对见 `docs/门禁说明/README.md` §2 首条——⚠ 直接 grep 配置里的声明行会多算一条**注释掉的**候选钩子(= 51),不是口径;加一条钩子就同批改 AGENTS 与门禁索引两处计数。2026-10-25 道E 连加 `bom-guard` / `setview-check` / `uinav-selftest` 三条,2026-10-26 加 `generated-fresh` 一条;**每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→ `docs/门禁说明/README.md` 索引**;通用机制与整体口径 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败;**2026-10-26 方案 B 同批加了第二根**:不可判定引用**绝对数**超过 `undecidable_total_ceiling` 即失败,因为比值会被"把可判定的引用改成散文"稀释(详见 `docs/门禁说明/id-check.md` §7.6))+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 22 个(`-unit-test` 后缀,2026-10-25 增补 adc-cal / prot-dim / vol-idle-sync / ui-data / meas / run-par 六个;⚠ `test/host/ui_sink` 有 `run_unit_test.py` 却**没有** per-module 钩子,只有 `test-coverage` 总闸跑得到它——原写「23 个 / 含 ui-sink」是把这条未接线的算进了钩子账)+ `test-coverage`(**覆盖率总闸**:跑全部 40 套并守基线回退;缺 mscoverage 组件时按用户裁定 **FAIL**、不 skip,2026-10-24)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,登记簿条目「rs485_task 白名单缺口」)+ `generated-fresh`(**生成器幂等性**:把 `tools/menu_gen/gen_menu.py`、中文字库链、`tools/arch_diagram/gen_*.py` 拷进**系统临时目录**重跑,与工作区**逐字节**必须一致——钩子只读、不写仓库也不 stash 别人在飞的改动;实证 `0f7b5a87` 改了模板文字没重跑生成物,接线前实测另有 5 张 `docs/images/` 配图已漂 → 钉成只减不增基线,**业务生成物零豁免面** → `docs/门禁说明/generated-fresh.md`)+ `mem-budget`(RAM 92% 预算门禁,登记簿条目「缩堆迁 CCM 后半(暂缓)」)+ `font-charset`(UI 字符集差集检查,防缺字白框)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 27 页黄金参考)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 3 条基线只减不增(2026-10-25 记录 reader 注入削掉 UI 记录 3 条边,6→3))+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**)) +- 任一失败即拦截,共 **50 个钩子**(**口径 = YAML 解析后的 hook 条目数**:自查命令与「声明 / 文档 / 实跑」三数同源核对见 `docs/门禁说明/README.md` §2 首条——⚠ 直接 grep 配置里的声明行会多算一条**注释掉的**候选钩子(= 51),不是口径;加一条钩子就同批改 AGENTS 与门禁索引两处计数。2026-10-25 道E 连加 `bom-guard` / `setview-check` / `uinav-selftest` 三条,2026-10-26 加 `generated-fresh` 一条;**每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→ `docs/门禁说明/README.md` 索引**;通用机制与整体口径 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败;**2026-10-26 方案 B 同批加了第二根**:不可判定引用**绝对数**超过 `undecidable_total_ceiling` 即失败,因为比值会被"把可判定的引用改成散文"稀释(详见 `docs/门禁说明/id-check.md` §7.6))+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 **22 根专属钩子**(`-unit-test` 后缀 = 有独立 `files:` 触发面、需要秒级归因的那个子集;2026-10-25 增补 adc-cal / prot-dim / vol-idle-sync / ui-data / meas / run-par 六根)——**这是口径不是缺口**:`test/host/` 实测 40 套带 `run_unit_test.py`,其余 **18 套**(含 `test/host/ui_sink`)没有专属钩子,但每次提交都被 `test-coverage` 总闸全量真跑并守基线回退,**失败照样拦**,只是归因落在总闸输出上;**没有专属钩子 ≠ 没被跑**,18 套台账与补钩子的判据 → `docs/门禁说明/host-unit-tests.md` §6 第 4 条(原写「23 个 / 含 ui-sink 七个」是双重错:既把 22 说成 23,又宣称 ui-sink 有钩子——配置历史里 `ui-sink-unit-test` 从未存在)+ `test-coverage`(**覆盖率总闸**:跑全部 40 套并守基线回退;缺 mscoverage 组件时按用户裁定 **FAIL**、不 skip,2026-10-24)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,登记簿条目「rs485_task 白名单缺口」)+ `generated-fresh`(**生成器幂等性**:把 `tools/menu_gen/gen_menu.py`、中文字库链、`tools/arch_diagram/gen_*.py` 拷进**系统临时目录**重跑,与工作区**逐字节**必须一致——钩子只读、不写仓库也不 stash 别人在飞的改动;实证 `0f7b5a87` 改了模板文字没重跑生成物,接线前实测另有 5 张 `docs/images/` 配图已漂 → 钉成只减不增基线,**业务生成物零豁免面** → `docs/门禁说明/generated-fresh.md`)+ `mem-budget`(RAM 92% 预算门禁,登记簿条目「缩堆迁 CCM 后半(暂缓)」)+ `font-charset`(UI 字符集差集检查,防缺字白框)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 27 页黄金参考)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 3 条基线只减不增(2026-10-25 记录 reader 注入削掉 UI 记录 3 条边,6→3))+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**)) - **范围 = 项目代码**:第三方(`Lib/`、`Middlewares/`、CMSIS、HAL、`lftpd`、`dhara`、`MDK-ARM`)排除;`cppcheck`/`coding-standard` 另排除 `test/`,规范检查再排除 CubeMX 生成的 `Src/`/`Inc/`。**`simulator/` 已纳入全部钩子**(2026-10-11)——生成物只靠 `.gitignore` 挡,**不在 `exclude` 里写目录名**(教训见 `docs/代码规范/pre-commit使用说明.md` §5.5) - ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关 - **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/` diff --git a/docs/测试说明/配置文件单元测试说明.md b/docs/测试说明/配置文件单元测试说明.md index 5824c19e..3993a024 100644 --- a/docs/测试说明/配置文件单元测试说明.md +++ b/docs/测试说明/配置文件单元测试说明.md @@ -160,6 +160,8 @@ test/host/cfg_file/cfg_file_test.c | **TC-CONFIG-053** | 变更后重载 + 挂回调段差异触发回调 | 变更响应 | | **TC-CONFIG-055** | `(size, mtime)` 令牌不变 → 不重载(令牌 = 变更信号) | 令牌契约 | | **TC-CONFIG-054** | poll 检出超长 → 工厂默认 + 重建模板 + 留痕;**不触发回调** | 在线修复通道保留 | +| **TC-CONFIG-056** | `[comm]` 段容量钉:段数上限 8、键数上限 16 写进断言(改上限必须同批改本行与用例) | 段/键上限是配置引擎的公开契约,不能靠"跑起来看看" | +| **TC-CONFIG-057** | 段/键写满后再新增 → 拒绝保存且原文件不动(满则拒,不是静默截断) | 溢出语义与 TC-CONFIG-043 同源,覆盖 `[comm]` 新增段 | **当前结果**:139/139 PASSED(`failed=0`)。 diff --git a/docs/门禁说明/host-unit-tests.md b/docs/门禁说明/host-unit-tests.md index 41aa85eb..ebe60eae 100644 --- a/docs/门禁说明/host-unit-tests.md +++ b/docs/门禁说明/host-unit-tests.md @@ -1,12 +1,12 @@ -# 主机侧单测(16 个 `*-unit-test` 钩子)— 同一机制的 16 个实例 +# 主机侧单测(22 个 `*-unit-test` 钩子)— 同一机制的 22 个实例 > 一句话:把**不依赖硬件的逻辑层**拿到 PC 上编译并跑断言(每个模块一个 `test/host/<模块>/run_unit_test.py`)。 > 它是"回归最快的两层之一"(静态层 + 逻辑层),但**不等于真机正确**。 -- 16 个钩子各有一个 `files:` 白名单(改动被测模块或测试目录才跑)、`pass_filenames: false`; +- 22 个钩子各有一个 `files:` 白名单(改动被测模块或测试目录才跑)、`pass_filenames: false`; 远端 CI 只跑 L0 静态门禁 + L0.5 主机侧单测(见 `docs/CI/README.md`)。 -## 1. 它在查什么(16 个实例) +## 1. 它在查什么(22 个实例) | 钩子 | 被测对象 | |---|---| @@ -26,6 +26,12 @@ | `iec104-unit-test` | IEC104 协议栈(ASDU / 链路 / 端口表) | | `soe-unit-test` | SOE 动作记录编解码 `App/log/soe_rec.c`、`soe_store.c` | | `nand-port-unit-test` | dhara NAND 移植层 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` | +| `adc-cal-unit-test` | 通道标定层 `App/algo/adc_cal.c`(2026-10-25 挂闸,下同) | +| `prot-dim-unit-test` | 量纲回归:标定 → 相量 → 定值 → 判据链的单位与倍率一致性 | +| `meas-unit-test` | L2.5 遥测快照层 `App/algo/meas.c`(200 ms 双区,104 与 UI 共用上游) | +| `run-par-unit-test` | 运行参数段 `App/algo/run_par.c`(遥测死区与系数) | +| `vol-idle-sync-unit-test` | 卷空闲同步 `App/fs/vol_idle_sync.c`(空闲窗口内的 f_sync 语义) | +| `ui-data-unit-test` | UI 数据快照层 `Drivers/BSP/UI/ui_data.c` 与各页写入方 | 共同机制:`run_unit_test.py` 在宿主编译器上编译被测 `.c` + 该目录的 `stub/` 替身,跑断言并汇总 用例数(各模块的 `TC-HOST-<模块码>` 号段见 `docs/编号规范.md`)。 @@ -42,7 +48,7 @@ | `nand-port`(dhara 移植层两条硬约束) | 149 断言 | 整页扫描的空闲判定(页首 64 B 全 0xFF 而第 1000 字节有数据等形态)+ **与旧算法对照**(旧实现只看页首,同一页答"空闲")。这类缺陷真机极难复现,发作后只表现为"某次复位后丢数据" | - 运行器统一用 MSVC `cl.exe`(远端 CI 的 L0.5 层);公共骨架是 - `test/host/common/unit_test_harness.py` —— **改动这个骨架会同时触发 16 个钩子** + `test/host/common/unit_test_harness.py` —— **改动这个骨架会同时触发 22 个钩子** (每个钩子的 `files:` 都把它写进触发面,这是有意的)。 ## 2. 为什么要它 @@ -55,7 +61,7 @@ ## 3. 触发面 -- 只有**改动该被测模块或该测试目录**时对应钩子才跑(16 个钩子各管一块,改日志不会触发 16 份编译)。 +- 只有**改动该被测模块或该测试目录**时对应钩子才跑(22 个钩子各管一块,改日志不会触发 22 份编译)。 - 单独跑:`& '.venv\Scripts\python.exe' test/host/<模块>/run_unit_test.py`。 ## 4. 报错怎么读 @@ -81,12 +87,25 @@ 行为不同"。桩签名改了而测试没跟着改,编译器未必报错(尤其桩返回宽类型)。 3. `include_dirs` 与源文件清单是**手写**的:新依赖一个目录就会假失败(见 §4 第 2 类),反过来, 漏了某个真实依赖可能让测试**编过却少编了一个翻译单元**(少测而不自知)。 -4. **专属钩子不是唯一防线**(2026-10-24 更正,原写"没有对应钩子的模块 = 零回归",如今不成立):上表 16 - 个模块各有专属钩子;仓库另有 12 套(P1 新增)没有专属钩子,改坏了由 `test-coverage` **总闸**兜 - 住——它跑全部 28 套,并守"已覆盖行只增不减"。总闸的触发面是 `App/`、`Drivers/BSP/`、 - `test/host/`、覆盖率脚本与基线,**外加门禁自身**(`.pre-commit-config.yaml`)——不带上自身 - 就等于谁改坏这根闸的 `files:` 模式,它就再也不跑(同 `lv-conf-sync`/`bsp-err` 的既有做法)。 - 纯 UI/文档改动不触发它,那时确实没有可回归的行为。 +4. **专属钩子不是唯一防线,而「没有专属钩子」也不等于「没被跑」**。2026-10-26 按 git 历史与实测重新 + 清点(上一版写「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`、 + `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 套每次提交都被 `test-coverage` **总闸**真跑:发现规则是 + `HOST_TEST_DIR.rglob("run_unit_test.py")`(与 `scripts/ci_local.py` 的 L0.5 同一口径), + 退出码语义写死「0 = 全部单测通过且覆盖率相对基线没回退 / 1 = 有单测失败或覆盖率回退」⇒ + **失败照样拦提交**。差的只是归因粒度(要在总闸输出里认出是哪套)与耗时(40 套全量比单根慢得多); + - 要不要给某套补第 23 根,判据现成:pre-commit 4.x 没有 `always: true`,**该模块有没有自己的 + `files:` 触发面**决定补了会不会真跑;只为凑数加一根,等于把「快归因面」和「全量把关」两件事混成 + 一件,还要让钩子计数再改一次; + - 总闸的触发面是 `App/`、`Drivers/BSP/`、`test/host/`、覆盖率脚本与基线,**外加门禁自身** + (`.pre-commit-config.yaml`)——不带上自身就等于谁改坏这根闸的 `files:` 模式它就再也不跑 + (同 `lv-conf-sync` / `bsp-err` 的既有做法)。纯 UI/文档改动不触发它,那时确实没有可回归的行为。 5. 时序与并发不在范围:中断抢占、DMA 完成回调、临界区、真机 tick 抖动都照不到(历史上最贵的 几个坑正是这类,见 `docs/问题记录/*_Trap_Records.md`)。 6. 断言写的是"作者当时想到的分支":**行 / 函数覆盖有度量了**(`test-coverage` 总闸出 @@ -109,4 +128,5 @@ | 日期 | 变更 | |---|---| +| 2026-10-26 | **数字与机器面对齐**(task-18 复核带出):实例 16 → **22**(2026-10-25 由 3528644e / 48bc50e7 / ffd462ef / 8c726986 / 201d7132 / 484d0682 各挂一根,§1 表补齐 6 行);§6 第 4 条「12 套无专属钩子 / 总闸 28 套」按实测更正为 **18 套 / 40 套**,并给出「没有专属钩子 ≠ 没被跑」的证据(总闸 rglob 发现规则 + 退出码语义)与补钩子的判据;顺带纠正 AGENTS 的「23 个 / 含 ui-sink」——`ui-sink-unit-test` 在配置历史里从未存在 | | 2026-10-23 | 首篇逐项文档(16 个实例清单 + 今天两个 runner 需补 include 目录的实测) |