test(NETCFG,CFG): P1①四个主机单测模块,并修 net_cfg 的 WAIT 契约与定长错记

按 docs/测试说明/主机单测覆盖率口径.md §9 的清单吃 P1 第①批「配置与工具」四块,
新增 4 个主机单测模块共 104 条断言,全部 MSVC 编译通过并 PASS:
- test/host/net_cfg/(45 条):出厂默认可用、注册幂等与内容、load 报 -1 仍返回 0
  (RAM 恒有效契约)、跨任务保存交接状态机 IDLE/WAIT/OK/FAIL、MAC 三态优先级、
  IPv4 定长格式化。
- test/host/ftp_cfg/(16 条):[ftp] 段注册(**p_cb 必须为 NULL**,口令下次登录才
  生效)、默认口令判定大小写不敏感、NULL 一律判不等(fail-closed)。
- test/host/prot_set_store/(16 条):blob 返回码分流 1 无档 / 0 成功 / **2 换代
  (只打 INFO)** / -1 损坏;写盘失败必须回置脏标志(否则定值静默丢失)。
- test/host/task_prof/(27 条):无效样本闸门(防 DWT 复位把 max 抬到 2^32)、
  未 enter 就 exit 不记账、首次 5s + 每 2s 节流、环形缓冲与裸发两通道、
  周期→微秒换算随 SystemCoreClock 变。

覆盖率收益(已 --update-baseline 登记):套件 16 → 20 全绿;files_tested 19 → 23;
未触及 125 → 121 个文件(.c 47 → 43、9,657 → 9,228 行);分母 2,893 → 3,163 插桩
行;line=2811/3163(88.9%)、func=225/240(93.8%)。§9 的恒等式同步更新且仍咬合:
43 = 9(P1 剩) + 5(P2) + 29(P3)、9,228 = 2,188 + 2,226 + 4,814。

新单测照出并修掉两处真问题(都在 net_cfg,不是测试写错):
1. 实现违反自己的文档契约:net_cfg.h 明写"属主任务写盘中显示 WAIT",但
   net_cfg_save_run 为了不丢新请求会先清 pending 再写盘 → 写盘那几百毫秒里 UI
   保存行退回上一轮的 OK/IDLE(第一次保存按 ENTER 后屏幕上还是 ENTER),用户只
   会再按一次。补 s_save_busy 标志,TC-NETCFG-028/026/030 钉死(含失败路径必须
   清 busy,否则 UI 永久卡 WAIT)。
2. "定长 11 字符"是错的:%03u.%03u.%03u.%03u = 4 段×3 位 + 3 个点 = **15**。这个数
   错了 4 处(net_cfg.h、net_cfg.c、网络配置说明.md 两处)——是我照抄它才撞上
   FAIL,错在文档不是代码。已全部更正,并确认 len ≥ 16 的要求与 UI_BUF_STRLEN=32
   本来就没问题(无截断风险)。

桩的口径(沿用既有模块,不另起一套):真实头优先——cfg_file.h / cfg_file_store.h /
console_log.h / prot_set.h 全用真头(接口一漂移就编译期报错),只有 main.h 必须桩
(真实 main.h 拉整棵 HAL 头树,PC 上编不动;桩里字段名与位掩码宏名与 CMSIS 一致)。
prot_set_store 连真实 prot_set.c 一起编:blob 的编解码与脏标志在它里面,只有拿真
serialize 产出的 blob 才能造出"换代"与"损坏"两种真实形态,手搓等于测自己的桩。
配置引擎本身(解析/写盘/沉降)归 test/host/cfg_file 的 103 条,本批不重复测。

编号门禁:按 2026-10-21 批 3 的 B 层口径补建 4 族 TC-HOST-*(NETCFG 游标 056 /
FTPCFG 014 / PROTSTOR 016 / TASKPROF 020,代码即属主登记处)。这一步不是走形式:
建族之前文档里引用 TC-NETCFG-028 被判「台账之外新账」直接卡提交,建族后才通过;
编号规范 §8.4 变更记录加一行说明族数 9 → 13。id_registry.json 只做增量插入(+88 行,
无重排)。

自查(提交时 pre-commit run --all-files,**仅 3 条失败且都与本分支改动无关**):
- mem-numbers / mem-budget:本 worktree 因需验证代码改动而 @build 过,新生成的 .map
  与 docs/内存管理 受管快照漂移。实测增量 ER_IROM1 +12 B、RW_IRAM1 +8 B、Flash
  +16 B、**CCM 0**,水位百分比全不变(改动 = 1 个静态字节 + 几条指令)。按并行分支
  约定**不在分支里 --write**,台账由 Lead 合并后统一回填。
- ui-golden:simulator/tests/ref/*.bin 写在 .gitignore 里(黄金参考是本地生成物),
  新建的 worktree 天生没有,报的是"缺黄金参考"而非像素差异;本分支零 UI 文件改动。
- 其余全部 Passed,含 @build 0 错误 0 警告、test-cppcheck(它抓到过我一条 p!=NULL
  后又解引用的写法,已改为断言+显式守卫)、coding-standard、doc-links(35 处=存量)、
  id-check --strict、mojibake 与 BOM/CR/U+FFFD/行尾逐项卫生。
- 自审修掉自己写的两条恒真断言(形如 x==0?1:1 的假绿)与一个重复编号标签。
- 4 个新模块**尚未挂 pre-commit 钩子**:.pre-commit-config.yaml 是单写文件,任务工作
  树内不改;覆盖率脚本按 test/host/**/run_unit_test.py 自动发现,已实跑 20/20。
- 未跑 @test:改动仅 1 个静态字节 + UI 保存行的显示时序,无硬件语义变化。
This commit is contained in:
2026-10-23 15:25:24 +08:00
parent e70b861215
commit b1291f1308
18 changed files with 2033 additions and 37 deletions
+88
View File
@@ -1592,6 +1592,94 @@
"subitem_ok": true,
"owner_note": "B 层口径同其余 TC-HOST 族;本族是 2026-10-21 补漏——上批模块码改名前它是纯数字 `TC-104-*`,未进当时的悬空普查,故漏建族",
"cursor_note": "游标按实测最大号收录(2026-10-21)"
},
{
"name": "TC-HOST-NETCFG",
"class": "TC",
"title": "主机侧单测用例号:网络配置编排与跨任务保存交接(test/host/net_cfg)",
"pattern": "\\bTC-NETCFG-[0-9]{1,4}[a-z]?\\b",
"digit_width": [
2,
4
],
"owner_docs_glob": [
"test/host/net_cfg/*"
],
"scope": "global",
"frozen": false,
"cursor": "TC-NETCFG-056",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
"subitem_ok": true,
"owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);本族 2026-10-23 随覆盖率口径 §9 P1 第①批新建模块补登记",
"cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌"
},
{
"name": "TC-HOST-FTPCFG",
"class": "TC",
"title": "主机侧单测用例号:FTP 鉴权配置 [ftp] 段(test/host/ftp_cfg)",
"pattern": "\\bTC-FTPCFG-[0-9]{1,4}[a-z]?\\b",
"digit_width": [
2,
4
],
"owner_docs_glob": [
"test/host/ftp_cfg/*"
],
"scope": "global",
"frozen": false,
"cursor": "TC-FTPCFG-014",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
"subitem_ok": true,
"owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);本族 2026-10-23 随覆盖率口径 §9 P1 第①批新建模块补登记",
"cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌"
},
{
"name": "TC-HOST-PROTSTOR",
"class": "TC",
"title": "主机侧单测用例号:保护定值持久化胶水层(test/host/prot_set_store)",
"pattern": "\\bTC-PROTSTOR-[0-9]{1,4}[a-z]?\\b",
"digit_width": [
2,
4
],
"owner_docs_glob": [
"test/host/prot_set_store/*"
],
"scope": "global",
"frozen": false,
"cursor": "TC-PROTSTOR-016",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
"subitem_ok": true,
"owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);本族 2026-10-23 随覆盖率口径 §9 P1 第①批新建模块补登记",
"cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌"
},
{
"name": "TC-HOST-TASKPROF",
"class": "TC",
"title": "主机侧单测用例号:任务耗时探针与上报节流(test/host/task_prof)",
"pattern": "\\bTC-TASKPROF-[0-9]{1,4}[a-z]?\\b",
"digit_width": [
2,
4
],
"owner_docs_glob": [
"test/host/task_prof/*"
],
"scope": "global",
"frozen": false,
"cursor": "TC-TASKPROF-020",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
"subitem_ok": true,
"owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);本族 2026-10-23 随覆盖率口径 §9 P1 第①批新建模块补登记",
"cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌"
}
],
"unmanaged_bare": {
+78 -29
View File
@@ -44,8 +44,8 @@
"func_rate": 0.9565,
"funcs_covered": 22,
"funcs_valid": 23,
"line_rate": 0.9609,
"lines_covered": 172,
"line_rate": 0.9665,
"lines_covered": 173,
"lines_valid": 179
},
"App/algo/sample_ring.c": {
@@ -64,6 +64,14 @@
"lines_covered": 572,
"lines_valid": 652
},
"App/cfg/prot_set_store.c": {
"func_rate": 1.0,
"funcs_covered": 4,
"funcs_valid": 4,
"line_rate": 0.931,
"lines_covered": 27,
"lines_valid": 29
},
"App/fs/vol_lock.c": {
"func_rate": 1.0,
"funcs_covered": 3,
@@ -128,6 +136,14 @@
"lines_covered": 156,
"lines_valid": 157
},
"App/util/task_prof.c": {
"func_rate": 1.0,
"funcs_covered": 9,
"funcs_valid": 9,
"line_rate": 0.9462,
"lines_covered": 88,
"lines_valid": 93
},
"App/util/task_watchdog.c": {
"func_rate": 1.0,
"funcs_covered": 8,
@@ -151,6 +167,22 @@
"line_rate": 0.9737,
"lines_covered": 37,
"lines_valid": 38
},
"Drivers/BSP/NETCFG/ftp_cfg.c": {
"func_rate": 1.0,
"funcs_covered": 4,
"funcs_valid": 4,
"line_rate": 0.9722,
"lines_covered": 35,
"lines_valid": 36
},
"Drivers/BSP/NETCFG/net_cfg.c": {
"func_rate": 0.9375,
"funcs_covered": 15,
"funcs_valid": 16,
"line_rate": 0.9643,
"lines_covered": 108,
"lines_valid": 112
}
},
"generated_by": "scripts/check_test_coverage.py --update-baseline",
@@ -193,6 +225,13 @@
"lines_total": 152,
"ok": true
},
"ftp_cfg": {
"files": {
"Drivers/BSP/NETCFG/ftp_cfg.c": 36
},
"lines_total": 36,
"ok": true
},
"iec104": {
"files": {
"App/algo/freq.c": 130,
@@ -214,6 +253,13 @@
"lines_total": 253,
"ok": true
},
"net_cfg": {
"files": {
"Drivers/BSP/NETCFG/net_cfg.c": 112
},
"lines_total": 112,
"ok": true
},
"phasor": {
"files": {
"App/algo/phasor.c": 55,
@@ -240,6 +286,14 @@
"lines_total": 179,
"ok": true
},
"prot_set_store": {
"files": {
"App/algo/prot_set.c": 179,
"App/cfg/prot_set_store.c": 29
},
"lines_total": 208,
"ok": true
},
"sha256": {
"files": {
"App/util/sha256.c": 114
@@ -262,6 +316,13 @@
"lines_total": 157,
"ok": true
},
"task_prof": {
"files": {
"App/util/task_prof.c": 93
},
"lines_total": 93,
"ok": true
},
"time_sync": {
"files": {
"App/sync/time_sync.c": 99
@@ -295,6 +356,7 @@
"App/algo/prot_set.c",
"App/algo/sample_ring.c",
"App/cfg/cfg_file.c",
"App/cfg/prot_set_store.c",
"App/fs/vol_lock.c",
"App/log/soe_rec.c",
"App/protocol/iec104/iec104_asdu.c",
@@ -303,22 +365,25 @@
"App/sync/time_sync.c",
"App/util/sha256.c",
"App/util/sys_clock.c",
"App/util/task_prof.c",
"App/util/task_watchdog.c",
"Drivers/BSP/GD5F2GQ5UE/nand_ftl.c",
"Drivers/BSP/NET/lftpd/lftpd_auth.c"
"Drivers/BSP/NET/lftpd/lftpd_auth.c",
"Drivers/BSP/NETCFG/ftp_cfg.c",
"Drivers/BSP/NETCFG/net_cfg.c"
],
"totals": {
"files_generated_excluded": 5,
"files_tested": 19,
"files_untested": 125,
"func_rate": 0.9324,
"funcs_covered": 193,
"funcs_valid": 207,
"line_rate": 0.8821,
"lines_covered": 2552,
"lines_valid": 2893,
"modules_ok": 16,
"modules_total": 16
"files_tested": 23,
"files_untested": 121,
"func_rate": 0.9375,
"funcs_covered": 225,
"funcs_valid": 240,
"line_rate": 0.8887,
"lines_covered": 2811,
"lines_valid": 3163,
"modules_ok": 20,
"modules_total": 20
},
"untested_sources": [
{
@@ -405,18 +470,10 @@
"code_lines": 171,
"path": "Drivers/BSP/NET/lftpd/lftpd_inet.c"
},
{
"code_lines": 167,
"path": "Drivers/BSP/NETCFG/net_cfg.c"
},
{
"code_lines": 162,
"path": "Drivers/BSP/NET/net_select.c"
},
{
"code_lines": 160,
"path": "App/util/task_prof.c"
},
{
"code_lines": 157,
"path": "Drivers/BSP/TPAFE5160/tpafe5160.c"
@@ -489,10 +546,6 @@
"code_lines": 62,
"path": "App/protocol/iec104/iec104_asdu.h"
},
{
"code_lines": 59,
"path": "Drivers/BSP/NETCFG/ftp_cfg.c"
},
{
"code_lines": 56,
"path": "App/protocol/iec104/iec104_link.h"
@@ -517,10 +570,6 @@
"code_lines": 44,
"path": "App/log/soe_rec.h"
},
{
"code_lines": 43,
"path": "App/cfg/prot_set_store.c"
},
{
"code_lines": 42,
"path": "Drivers/BSP/UI/ui_content_page.h"