docs(覆盖率): 新增 §9 未触及面建桩优先级清单(P1/P2/P3 逐个画像)
回答"那 125 个到底怎么吃":对 47 个未触及 .c(9,657 有效代码行)逐个量化后再排序, 不靠拍脑袋。排序口径写进文档、可复核可反驳:价值 = 有效代码行 × 逻辑度 × 缺陷历史 (该文件的 fix/refactor 提交数),成本 = 要桩的外部头数 − 能复用的现有桩;逻辑度 = 1 − 硬件符号密度(HAL_*() / FatFs f_*() / CH395F_* / SPI-I2C / OSAL 调用数 ÷ 行数)。 三档(数字互相咬合,覆盖率"不该 100%"在这里是算出来的): - P1 先吃 13 个 .c = 2,617 行:配置与工具 429(真实头即可编译,建议第一个落地,先用它 跑通"新增文件进台账 + NOTE 不误报回退"这条链)→ FTP 一套 1,083(20 次提交里 6 次 fix,最近两条都是静默型)→ NET 拆出的四块 903(须新建 stub/ch395f.h)→ 104 服务层 202。 - P2 值得但工程量大 5 个 = 2,226 行:net_socket.c 1,251(51 函数,**必须分片**,先测状态 迁移与语义判据);假 FatFs 一次吃 sys_log_mcu/cfg_file_store_mcu/soe_store 共 710; console_log 265(单写者契约 K1/K2 已写成表)。 - P3 明确不做 29 个 = 4,814 行,并把理由写死免得下次又列回待办:芯片驱动 1,990(假到底 ≈ 重写驱动,测的是自己的桩)、UI 侧 1,942(与模拟器同一份源码,已由 ui-golden 覆盖)、 任务编排 747(4~5 个函数的装配层,写成单测就是"断言我调了几次")、app_main 135 (被测对象本身就是内核启动前的时序约束)。 - 恒等式:47 = 13 + 5 + 29、9,657 = 2,617 + 2,226 + 4,814;做完 P1+P2 约 4.8k 行进分母 (现分母 2,893),files_tested 19 → 37。 配套口径修正: - "125 个未触及"易被读成 125 个待办,三处写精确 = 47 个 .c(9,657 行)+ 78 个 .h (2,182 行,多为声明与宏);待办池是那 47 个 .c。 - §8 下一步①改为指向 §9;终点判据从百分比改成"untested_sources 里剩下的每一条都能在 P3 表里找到归属",并提醒盯 files_tested/files_untested 而不是 88.2%(新增大块未测代码 会把它摊薄,那不是回退)。 自查:check_doc_links.py --all = 35 处(与主干同数,本分支贡献 0;期间修掉一处 §4.5 被 误算成本文自引用);check_ids.py --strict 通过;check_mojibake + BOM/CR/行尾卫生净。 本轮纯文档,零 .c/.h 改动,未跑 @build、未占 build/bench 锁。
This commit is contained in:
+68
-3
@@ -122,7 +122,9 @@
|
||||
| 被测到的自研生产源文件 | **19 个** / 2,893 可执行行 |
|
||||
| 行覆盖最低的两个 | `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 48.6%(123/253,函数 14/22)、`App/protocol/iec104/iec104_pts.c` 67.2%(117/174,函数 13/17) |
|
||||
| 行覆盖 95%+ 的文件 | 12 个(多为纯逻辑层:`adc_health` / `sample_ring` / `vol_lock` / `time_sync` / `task_watchdog` …) |
|
||||
| **从未进过任何主机单测的生产源文件** | **125 个 / 约 11,839 有效代码行**(前五:`net_socket.c` 1,251、`ch395f.c` 801、`lftpd.c` 694、`gd5f2gq5ue.c` 689、`sys_log_mcu.c` 458) |
|
||||
| **从未进过任何主机单测的生产源文件** | **125 个 / 约 11,839 有效代码行**——拆开是 **47 个 `.c`(9,657 行)
|
||||
| + 78 个 `.h`(2,182 行,多数只有声明与宏)**;前五:`net_socket.c` 1,251、`ch395f.c` 801、`lftpd.c` 694、
|
||||
| `gd5f2gq5ue.c` 689、`sys_log_mcu.c` 458。待办池是那 47 个 `.c`,且其中 29 个属 §9 P3「刻意不做」 |
|
||||
|
||||
⚠ 读法:88.2% 是"**被测的那 19 个文件内部**"的比率,**不是全仓覆盖率**。全仓的真相写在
|
||||
`untested_files` 这一栏(125 个),它比 88% 这个数字有用得多——也是 §7 第三条的由来。
|
||||
@@ -187,14 +189,77 @@
|
||||
|
||||
**下一步(按性价比排序)**:
|
||||
|
||||
1. 给 `net_socket.c` / `lftpd.c` / `ch395f.c` 这类"125 个未触及"里最大的几块建主机侧桩——
|
||||
比把 19 个文件从 88% 推到 95% 值钱得多(覆盖率只照"进没进过",建桩本身是这个动作里最贵的部分)。
|
||||
1. **扩大覆盖面**:按 **§9 的建桩优先级清单**吃(P1 13 个 .c / 2,617 行 → P2 5 个 / 2,226 行;
|
||||
P3 的 29 个 / 4,814 行**刻意不做**并已写明理由)。比把已有 19 个文件从 88% 推到 95% 值钱得多。
|
||||
2. **不做**:第二条覆盖率通道(gcc/WSL、`dotnet-coverage`、OpenCppCoverage 三条路都不走,用户裁定
|
||||
2026-10-23)。要重开这个裁定,前提是能同时给出"两份分母怎么对齐"的答案,否则只是多一本对不上的账。
|
||||
|
||||
## 9. 未触及面怎么吃:建桩优先级清单(2026-10-23 画像)
|
||||
|
||||
**先把读数纠正一次**:所谓"125 个未触及"= **47 个 `.c`(9,657 有效代码行)+ 78 个 `.h`(2,182 行,多数只有声明与宏)**,
|
||||
生成物 5 个已排除。**待办池不是 125,而是这 47 个 `.c`**——而且其中约一半在下面 P3 里被明确判为"不做"。
|
||||
|
||||
排序口径(把数据摊开,欢迎反驳):**价值 = 有效代码行 × 逻辑度 × 缺陷历史**(该文件的 fix/refactor 提交数),
|
||||
**成本 = 要桩掉的外部头数 − 能复用的现有桩**。逻辑度 = `1 − 硬件符号密度`(`HAL_*(`、FatFs `f_*(`、
|
||||
`CH395F_*`、SPI/I2C、OSAL 调用数 ÷ 行数),逐文件实测。
|
||||
现有桩共 13 个头(`test/host/*`/stub/):`dbg_log.h`(×6)、`FreeRTOS.h`、`task.h`、`main.h`、`cmsis_os.h`、
|
||||
`cmsis_os2.h`、`sys_clock.h`、`sys_events.h`、`iwdg.h`、`adc.h`、`tpafe5160.h`、`gd5f2gq5ue.h`、`net_socket.h`。
|
||||
⚠ **没有 `ch395f.h`**——NET 域那批必须新建它。FatFs 侧不建假头,沿用"真实 `ff.h` + 测试单元内假 diskio"
|
||||
(`test/host/nand_port/` 已趟通:接口契约原汁,桩与真实 API 一漂移就编译期报错)。
|
||||
|
||||
### P1|先吃:逻辑密、桩薄、有近期缺陷垫背(13 个 `.c` = 2,617 行)
|
||||
|
||||
| 批次 | 文件(有效代码行) | 逻辑度 | 要桩什么 | 判据出处(直接抄成用例) | 为什么值得 |
|
||||
|---|---|---|---|---|---|
|
||||
| **FTP 一套** | `lftpd.c`(694)、`lftpd_io.c`(207)、`lftpd_inet.c`(171)、`lftpd_string.c`(11) = 1,083 | 0.99/0.90/0.93 | 真实 `ff.h` + 假 diskio;复用 `net_socket.h` | `docs/测试说明/FTP_Test_Guide.md`、`docs/子系统设计/网络配置说明.md` §3.4(`pwd_hash`=SHA-256(user‖password)、连续 5 次失败锁 60 s、默认口令启动告警) | **20 次提交里 6 次是 fix**,最近两条都是"静默型":LIST/NLST 满块被截断回 550、对端行为误打 `[ERR]`(2026-10-23)——覆盖率最擅长照的就是这类"日志看不出根因" |
|
||||
| **NET 拆出来的四块** | `net_pending.c`(287)、`net_events.c`(263)、`net_listener.c`(191)、`net_select.c`(162) = 903 | 0.90/0.78/–/0.93 | **新建 `stub/ch395f.h`**(芯片寄存器只到函数边界)+ 假任务通知;`net_socket.h` 已有 | `docs/网络/网络自愈架构设计.md` §4.10(socket 读清点审计 + 新增一处读清的三条准入判据)、`docs/网络/Net层阻塞模型架构设计.md` | 挂起 recv、监听按代次重建、退避这些**语义在真机上极难复现**;这一族的 B3("顺手全量清一遍把别的 socket 门焊死")与 N34 E14 都是 fix 出来的真事 |
|
||||
| **配置与工具(最便宜,建议第一个落地)** | `net_cfg.c`(167)、`task_prof.c`(160)、`ftp_cfg.c`(59)、`prot_set_store.c`(43) = 429 | 1.00/0.97/–/– | 真实头即可(`cfg_file.h` 已被现有单测证明能在 PC 上编),最多补 `dbg_log.h` | `docs/子系统设计/网络配置说明.md`(工厂默认 192.168.1.100/24、MAC 全零=芯片内置、保存立即生效)、`docs/算法/采集与算法层设计说明.md` | 硬件符号 0~2 处,纯解析/编解码/格式化。**一天内能出第一份绿**,用来把"新增 `files_tested`"这条路先跑通 |
|
||||
| **104 服务层** | `iec104_serv.c`(202) | 0.96 | 复用 `time_sync`/stub 的 `cmsis_os2.h` + 已有 `net_socket.h` | 与 `test/host/iec104/`(97 用例)同一套脚手架 | 同族的 `iec104_pts.c` 现在只有 **67.2%**(全仓最低之一);服务层进来才能把 YK/YX 编排的分支照全 |
|
||||
|
||||
### P2|值得但工程量大(5 个 `.c` = 2,226 行)
|
||||
|
||||
1. **`net_socket.c`(1,251 行 / 51 个函数,全仓最大的逻辑块)** —— 必须**分片**,别一次吞。
|
||||
先测状态迁移与语义判据:EOF 时**不得** `net_close` 该 fd(会连监听一起毁掉)、`auto_relisten`、
|
||||
`settle_close(sockfd, p_sock)` 双参显式传、挂起 recv 超时、`net_*` 错误码经**任务通知值**回传
|
||||
(`net_msg_t` 是按值拷入队列的,改副本调用方看不到)。桩 = 新建假 CH395F。
|
||||
该文件 80 次提交、15 次 fix/refactor,值;但**一次做完必翻车**(先求把状态机进分母,别求百分比)。
|
||||
2. **`sys_log_mcu.c`(458) + `cfg_file_store_mcu.c`(129) + `soe_store.c`(123) = 710 行** —— 一套假 FatFs
|
||||
吃三块,性价比最高的第二批。判据现成:`docs/子系统设计/运行与自检记录设计说明.md` §4.4(写侧批量合并 + **掉电语义不变** +
|
||||
失败回投不丢条目 + 容量轮转只在慢路径)与 `docs/子系统设计/运行与自检记录设计说明.md` §4.5
|
||||
(读侧 8 条窗口 + 写者代数失效 + API 不变)。
|
||||
这块的历史教训就是"日志读写放大"与"`mkdir` 失败静默不再重试"。
|
||||
3. **`console_log.c`(265)** —— 单写者契约 K1/K2 已经写成表,照抄成用例最省力:
|
||||
`docs/子系统设计/控制台单写者日志设计说明.md`。
|
||||
|
||||
### P3|**明确不做**(把理由写死,免得下次又被列回待办:29 个 `.c` = 4,814 行)
|
||||
|
||||
| 类别 | 体量 | 为什么不做 | 那它靠什么把关 |
|
||||
|---|---|---|---|
|
||||
| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `@test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` |
|
||||
| UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 11 页像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
|
||||
| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`@test`)+ `docs/架构设计/项目启动顺序管理.md` |
|
||||
| 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 |
|
||||
|
||||
**四个数字对得上**:`47 = 13(P1) + 5(P2) + 29(P3)`、`9,657 = 2,617 + 2,226 + 4,814`。
|
||||
也就是说:**"覆盖率不该 100%"这件事在这份清单里是算出来的,不是嘴上说的**——P3 那 4,814 行永远不进分母,
|
||||
而 P1+P2 做完约 `4.8k` 行进分母(现在的分母只有 2,893),`files_tested` 从 19 → 37。
|
||||
|
||||
### 顺序建议与记账
|
||||
1. 落地顺序:**配置与工具 4 块**(先跑通"新增文件进台账 + NOTE 不误报回退"这条链)→ **FTP 一套** →
|
||||
**NET 四块**(含新建 `stub/ch395f.h`)→ **104 服务层** → P2 的 FatFs 三块与 `console_log.c` →
|
||||
最后啃 `net_socket.c` 的状态机分片。
|
||||
2. 每吃掉一批:`--update-baseline` 重登;看 `files_tested`(19→…)与 `files_untested`(125→…)这两个数,
|
||||
**不要盯着 88.2% 看**——那只是"已进分母部分"的自分数,新增大块未测代码时会**被摊薄而下降,那不是回退**(§5 已注明)。
|
||||
3. 终点判据不是百分比,而是:**`untested_sources` 里剩下的每一条都能在 P3 那张表里找到归属**。
|
||||
找不到归属的(既没进分母、也不属于"刻意不做")才是真窟窿。
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|---|---|
|
||||
| 2026-10-23 | 新建。落地 `scripts/check_test_coverage.py` 与 `scripts/test_coverage_baseline.json`:首次实测 16 套全通过,并集 2552/2893 行(88.2%)、函数 193/207(93.2%),在测 19 文件 / 未触及 125 文件。同步销项 `docs/问题记录/项目审查与优化建议报告.md` §4.8 空白 ①(覆盖率口径)与 §5.5 第 5 条前半、§0.1 / §0.4 / §7 对应行;`malloc(` 正则那条**同日也落地**(`coding-standard` CODE-RULE-20,见 `docs/门禁说明/coding-standard.md`)。**同日补 §2.1**:用户问「为什么不用 GCC」,实测本机 WSL2 Ubuntu 有 gcc/gcov 15.2.0,16 套单测在 gcc 下**全部编过且全绿**、`gcov -b -c` 确实给出分支覆盖(`nand_ftl.c` 分支 41.40%)——但换编译器=换分母(`sha256.c` gcc 105 行 vs MSVC 114 行),故默认通道不变。 |
|
||||
| 2026-10-23 | **用户裁定:本仓只留 MSVC 一条覆盖率通道**,不建 gcc/WSL 第二条("后面可能引入更多问题")。实测数据与三条代价保留在 §2.1 当决策依据,§7 第 1 条把分支覆盖改记为**已接受缺口**,§8 下一步删掉"建通道"并写明重开该裁定的前提(必须先答"两份分母怎么对齐")。脚本与基线**不受影响**,仍是 `mscoverage` 一条路 |
|
||||
| 2026-10-23 | 新增 **§9 建桩优先级清单**:对 47 个未触及 `.c`(9,657 行)逐个画像(有效代码行 / 函数数 /
|
||||
逻辑度=1−硬件符号密度 / 要桩的外部头 / 可复用现有桩 / fix 提交数),分为 **P1 13 个 2,617 行、
|
||||
P2 5 个 2,226 行、P3 29 个 4,814 行明确不做**(四条对得上:13+5+29=47、三段相加=9,657);
|
||||
§8 下一步第 1 条改为指向本节 |
|
||||
|
||||
@@ -32,8 +32,9 @@
|
||||
重开条件见 §5.3。今天又收口一项:**F-4 判定不做、F-6 已落地、F-5 降级观察**(§5.4)。
|
||||
4. **补面两条已全部落地(2026-10-23)**:覆盖率口径(`../测试说明/主机单测覆盖率口径.md`)+
|
||||
堆禁令机器拦截(`coding-standard` **CODE-RULE-20**,§5.5 第 5 条)。**剩下的量在覆盖面**:覆盖率首登
|
||||
照出 **125 个生产源文件从未进过任何主机单测**(约 11,839 行,`net_socket.c` 1,251 行最大)——给它们建桩
|
||||
比把在测的 19 个文件从 88% 推到 95% 值钱得多(N-12/N-13 那类"读代码才发现"的缺陷就是这么照出来的)。
|
||||
照出 **47 个 `.c`(9,657 有效代码行)+ 78 个 `.h` 从未进过任何主机单测编译单元**(`net_socket.c` 1,251 行最大)。
|
||||
建桩比把在测的 19 个文件从 88% 推到 95% 值钱得多,但**不是 125 个都该做**:优先级与"刻意不做"的清单已按逐文件
|
||||
画像写好 → `../测试说明/主机单测覆盖率口径.md` §9(P1 13 个 / 2,617 行、P2 5 个 / 2,226 行、P3 29 个 / 4,814 行不做)。
|
||||
5. **仓库体积重心在外部资料**(`docs/芯片手册` 717 文件 / 39.5 MB,占 docs 的 87%;`.git` 248 MB),
|
||||
见 §3.3 D-1 与登记簿 N-29——已决策暂不处理,但**推送/对外协作前必须重评**。
|
||||
|
||||
@@ -69,7 +70,8 @@ B0h 验 `ECC_EN`**——原先那两行的注释自己写着"否则后续 I/O
|
||||
| 存储/日志 | P2-10 ❌(N-25) | 卷锁现有 5 个使用者(配置原子写 / 日志 / SOE / lftpd_io / defaultTask),容量轮转删除与 UI 读并发仍未全覆盖 | 登记簿 S-5 面(卷锁覆盖面) |
|
||||
| 仓库 | D-1 ❌(N-29) | jpg 707 个 / 37.3 MB;`docs` 45.3 MB 里芯片手册占 39.5 MB;`.git` **248 MB** | 降采样 / 拆 submodule / LFS 三选一 + 历史重写;已决策暂不做 |
|
||||
| UI | U2 残余(U 簇) | 组件库只有 `ui_w_records.c/h` 一个;其余仍是页内自绘 | 按产品需求决定要不要继续抽组件 |
|
||||
| ~~测试~~ ✅ **两条全部闭环 2026-10-23**(覆盖率口径 + 堆禁令 CODE-RULE-20) | 覆盖率:`scripts/check_test_coverage.py` + 基线 `scripts/test_coverage_baseline.json`(口径与**盲区**见 `../测试说明/主机单测覆盖率口径.md`)——首登 16 套并集 2552/2893 行(88.2%)、**未触及 125 个生产源文件 / 约 11.8k 行**。堆禁令:`check_coding_standard.py` **CODE-RULE-20**(硬拦截、零基线;唯一豁免 = `simulator/sim/cfg_file_store_sim.c`
|
||||
| ~~测试~~ ✅ **两条全部闭环 2026-10-23**(覆盖率口径 + 堆禁令 CODE-RULE-20) | 覆盖率:`scripts/check_test_coverage.py` + 基线 `scripts/test_coverage_baseline.json`(口径与**盲区**见 `../测试说明/主机单测覆盖率口径.md`)——首登 16 套并集 2552/2893 行(88.2%)、**未触及 125 个文件 = 47 个 `.c`(9,657 行)+ 78 个 `.h`(2,182 行,多为声明/宏)**,
|
||||
| 待办池是那 47 个 `.c`;分批与「刻意不做」见 `../测试说明/主机单测覆盖率口径.md` §9。堆禁令:`check_coding_standard.py` **CODE-RULE-20**(硬拦截、零基线;唯一豁免 = `simulator/sim/cfg_file_store_sim.c`
|
||||
| 的平台分配后端宿主侧实现,见 `../门禁说明/coding-standard.md` §7) | 无(残余三项**已归口或已裁定**:覆盖率挂
|
||||
| pre-commit 的前置写在口径文档 §8;分支覆盖**不做**(§2.1 决策);扩大覆盖面按 §0.3 第 4 条) |
|
||||
| CI | T-7 ❌ | 远程 CI 只跑本机一台 runner,且不跑 `@build` | 保持现状即可;扩机器前重读 `docs/CI/README.md` |
|
||||
|
||||
Reference in New Issue
Block a user