测试:补启动段可观测性与模拟器差距清单,run_all_suites 加只编译模式(S10+S13+S6)
CI / L0 静态门禁 (pre-commit) (push) Successful in 1m10s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 25s

This commit is contained in:
2026-10-02 13:57:25 +08:00
parent fe963e7d91
commit d9425ddd23
12 changed files with 288 additions and 22 deletions
+1
View File
@@ -92,6 +92,7 @@ docs/
| [`自动化测试流程.md`](测试说明/自动化测试流程.md) | `@test` 闭环细节:命令 / 串口标记 / 失败模式 |
| [`测试文档模板规范.md`](测试说明/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
| [`链路延迟测试说明.md`](测试说明/链路延迟测试说明.md) | 网络链路时延分段定义、判定基线、执行流程 |
| [`模拟器差距清单.md`](测试说明/模拟器差距清单.md) | **PC 模拟器 vs 真机差距登记册**(L3 门禁可信度依据;改动涉及网络/日志/硬件层前必查 §3 决策规则) |
**主机侧单元测试(L0.5)**
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 261,196 B (255.07 KB) | 1,048,576 B (1024 KB) | 24.9% |
| ER_IROM1 | 262,100 B (255.96 KB) | 1,048,576 B (1024 KB) | 25.0% |
| RW_IRAM1 | 84,864 B (82.88 KB) | 131,072 B (128 KB) | 64.7% |
| RW_CCM | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
| **Flash (ROM 合计)** | **261,896 B (255.8 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
| **Flash (ROM 合计)** | **262,800 B (256.6 KB)** | 1,048,576 B (1024 KB) | **25.1%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -601,6 +601,14 @@ scripts/check_test_traceability.py
**完成判据**:文档建立且至少收录已知差距;UI 回归流程文档引用它。
> ✅ **已完成(2026-10-02)**:落地为 [`../测试说明/模拟器差距清单.md`](../测试说明/模拟器差距清单.md)
> (放 `docs/测试说明/`——它是跨子系统的测试资产,比 `UI设计/` 更贴切)。
> 收录 **6 条网络**(N-1~N-6,含 pending 缺失、代次号恒 0、9 个 MCU 函数 sim 未实现)+
> **4 条日志/配置**(L-1~L-4,含 mtime 粒度、堆后端差异)+ **7 条硬件专属**(H-1~H-7)+
> **4 条"有意对齐"**(防误判)。新增 **§3 六条"必须回真机"决策规则**(把该建议从"登记册"
> 提升为"可执行判据")。`simulator/README.md` §五与 `docs/README.md` §5 已交叉引用;
> 登记约定写入 §4(新增 `sim/` 后端差异必须登记)。
### 4.7 S14:CI 与体积门禁的关系澄清 🟡
**现状**:`mem-numbers` 在 CI 上被跳过(无 `.map`)。
@@ -625,19 +633,23 @@ scripts/check_test_traceability.py
|---|---|---|---|---|
| **第 1 波**(零风险、高收益) | S8 | 清理孤儿源文件 | 无 | 小 | ✅ 2026-10-02 |
| | S1-a | `sys_clock` L0.5 单测 | 无 | 中 | ✅ 2026-10-02(**抓出并修复 Zeller 缺陷**) |
| | S3 | CI 产物存档(零依赖方案) | 无 | 小 | ⬜ |
| | S14 | CI/体积门禁澄清 | 无 | 小 | ⬜ |
| **第 2 波**(补测试能力) | S1-b | `cfg_file` L0.5 单测 | 无 | 中 |
| | S2 | L0.5 适格台账 | S1-a 经验 | 小 |
| | S10 | 启动段可观测性核查 | 无 | 小 |
| | S6 | 测试构建可编译性(方案 A) | 无 | 小 |
| **第 3 波**(架构加固) | S7 | 真机回归加自检判定 | 无 | 中 |
| | S11 | 堆低水位告警 | 无 | 小 |
| | S9 | GD5F 错误通道(= 上游 C2) | 无 | 大 |
| | S3 | CI 产物存档(零依赖方案) | 无 | 小 | ✅ 2026-10-02 |
| | S14 | CI/体积门禁澄清 | 无 | 小 | ✅ 2026-10-02 |
| **第 2 波**(补测试能力) | S1-b | `cfg_file` L0.5 单测 | 无 | 中 | ✅ 2026-10-02(**抓出并修复外科手术保存死码 P0**) |
| | S2 | L0.5 适格台账 | S1-a 经验 | 小 | ✅ 2026-10-02 |
| | S10 | 启动段可观测性核查 | 无 | 小 | ✅ 2026-10-02(补 `sd2506_init` 6 条 + `app_main` 3 条;Flash +904 B) |
| | S6 | 测试构建可编译性(方案 A) | 无 | 小 | 🔄 2026-10-02 |
| **第 3 波**(架构加固) | S7 | 真机回归加自检判定 | 无 | 中 | ⏸ 判据需真机 |
| | S11 | 堆低水位告警 | 无 | 小 | ⏸ 判据需真机 |
| | S9 | GD5F 错误通道(= 上游 C2) | 无 | 大 | ⏸ 判据需真机 |
| | S5 | `test/` 宽松静态检查 | 先跑基线 | 小~中 |
| | S4 | 用例追溯脚本 | 无 | 中 |
| **第 4 波**(按需) | S12 | 模块级断言宏 | 试点验证 | 小(试点) |
| | S13 | 模拟器差距清单 | 无 | 小 |
| | S13 | 模拟器差距清单 | 无 | 小 | ✅ 2026-10-02 |
> **"⏸ 判据需真机"说明**(2026-10-02 补):S7 / S11 / S9 的**代码可离线编写**,但其**完成判据**
> 均要求真机观测(`@test` 捕获自检失败 / 台架长跑观察到堆告警 / GD5F 套件回归 PASS)。
> 在无 MCU 的环境下实施会留下"已改未验证"的债,故押后至有实机的窗口。
### 5.2 与上游任务的关系
@@ -670,5 +682,7 @@ scripts/check_test_traceability.py
| 日期 | 变更 |
|---|---|
| 2026-10-02 | 实施记录(第 2 波):**S1-b 已完成**(`cfg_file` L0.5 单测 20 用例 / 139 断言点 + 钩子 `cfg-file-unit-test`;**首跑抓出"外科手术保存死码"P0**,`s_encode` 返回值 `==0` 恒假 → 已存在配置文件的保存静默不落盘,修 3 处为 `>=0`,记入 E-63);**S2 已完成**(§4.2.1 L0.5 适格台账);**S10 已完成**(补 `sd2506_init` 6 条 + `app_main` 3 条启动日志,Flash +904 B,`项目启动顺序管理.md` §6.8 新增"启动段死机判读法");**S13 已完成**(`docs/测试说明/模拟器差距清单.md`,6 网络 + 4 日志配置 + 7 硬件 + 4 有意对齐,含 §3 六条"必须回真机"规则)。§5.1 路线图状态已更新,S7/S11/S9 标注"判据需真机"押后 |
| 2026-10-02 | 实施记录(第 1 波前半):**S8 已完成**(删除孤儿 `crc.c/h`、`ringbuf.c/h`,重跑引用关系图);**S1-a 已完成**(`sys_clock` L0.5 单测 20 用例 / 100 断言点 + 钩子 `sys-clock-unit-test`;**首跑抓出 Zeller 星期缺陷并修复**,Flash −112 B)。§3.1 S1-a 步骤表与 §5.1 路线图已更新状态 |
| 2026-10-02 | 实施记录(第 1 波后半):**S3 已完成**(CI 两 job 产物存档 + 汇总行,零依赖方案);**S14 已完成**(CI 体积门禁盲区显式提示 + `CI配置指南.md` §0.0 声明) |
| 2026-10-02 | 初版:测试体系量化盘点(4 套件 222 断言点 / L0.5 仅 1 模块 / 15 钩子)+ 5 个测试结构性问题 + 7 个架构改进点 + 14 条实施建议(S1~S14)+ 4 波路线图。所有论断基于当日源码实测(孤儿文件核查、断言点统计、CI workflow 走读)。**纯建议,未改任何代码** |
@@ -214,6 +214,27 @@ if (sys_evtHandle != NULL) {
- **修法**:所有**状态位**等待加 `osFlagsNoClear`(已改:`Src/freertos.c` 的 ftpTask、`App/task/ui_task.c`、`App/sync/time_sync.c`、`App/task/net_task.c`、`test/board/ch395f_test_task.c`);**命令位**(`CFG_SAVE`/`NET_RECONFIG`)保持消费型等待。
- **工程化防线**:① 关键消费者加"启动超时告警"(如 ftpTask 10s 内未到 listen 就 `DBG_ERROR`);② 用 `scripts/stack_report.py` 看任务栈水位——**某任务水位异常低 = 它根本没干活**(本次正是靠 ftpTask 只有 156B 才发现)。
8. **启动段(`osKernelStart` 之前)死机的判读法(2026-10-02 S10 落地)**:`app_main_init` 全程运行在**内核启动前**——此时没有任务、没有看门狗保护,一旦卡死就是"无输出 + LED 不闪"(N-03b 的显式代价)。得益于 S10 补齐的可观测性,现在**每一步都有结果日志**,判读规则很简单:
> **开机日志的最后一行 = 卡住的那一步**(该步的"结果日志"没打出来)。
启动进度轨迹(按顺序,每条都是"结果"而非"进入前"锚点):
| # | 日志行 | 对应步骤 | 卡此步的含义 |
|---|---|---|---|
| 1 | `boot: syslog init OK` | `syslog_mcu_init()` | (纯 RAM,不会卡) |
| 2 | `reset cause: ... (CSR=...)` | 复位原因翻译 | (纯计算,不会卡) |
| 3 | `SD2506 init: OK/FAILED (ret=N)` | `sd2506_init()` | **I2C1 不通**(PB6/PB7、上拉、供电)——失败时其内部还会先打 `sd2506_init: ... FAIL` 指明具体寄存器与错误码 |
| 4 | `[CLK] sys_clock_init: ...` | `sys_clock_init()` | RTC 读/写异常(降级时会打 `RTC abnormal, fallback to default time`) |
| 5 | `RS485 init: OK (UART4, DE=PA15)` | `rs485_init()` | UART4 / DE 引脚配置 |
| 6 | `NAND init: OK/...` | `gd5f2gq5ue_init()` | **BBT 全片扫描**——启动段最耗时的一步,卡住多为此处(SPI2 与片选) |
| 7 | `ADC init: OK/TIMEOUT` | `tpafe5160_init()` | TPAFE5160 SPI 通信 |
| 8 | `App init OK` | 全部完成 | 此后问题属任务/调度层,不再是启动段 |
- **若一行日志都没有**:问题在 `fault_handler_enable()` 之前(`HAL_Init` / 时钟树 / `system_stm32f4xx.c`),或 USART1 本身未初始化(`MX_USART1_UART_Init` 在 `main.c` 的 CubeMX 初始化段)。
- **若卡在某步且无该步结果日志**:看该步**内部**是否还有更细的日志(如 `sd2506_init` 的 `ID read FAIL` / `charge reg read FAIL` 区分了 I2C 失败与寄存器校验失败)。
- **配套手段**:`sd2506_init` / `gd5f2gq5ue_init` 内部的失败日志已细化到错误码;`sys_clock_init` 的降级路径写 syslog 自检(`clock chip fault`)。**注意日志本身有成本**——`DBG_*` 单条约 90 B Flash + 192 B 栈(见根目录 `AGENTS.md`),故只补真正静默的黑洞,不加冗余的"进入前/完成后"重复锚点。
---
## 7. 总结
+112
View File
@@ -0,0 +1,112 @@
# 模拟器差距清单(PC 模拟器 vs 真机)
> **用途**:把散落在各子系统文档里的"模拟器无法建模 X"汇成**单一事实源**,回答一个关键问题——
> **模拟器回归通过 ≠ 真机通过,那么差在哪?差多少?哪些必须真机补测?**
> **性质**:**登记册**(living document)。新增模拟器后端差异时必须在此登记一行,不得只写在别处。
> **上游依据**:`审查记录/测试与架构改进建议.md` §2.8 / §4.6(S13)、`审查记录/下一步任务规划.md` §3.1(N-4 二期门禁)。
> **相关**:`UI设计/LVGL模拟器部署与使用说明.md`(部署与使用)、`simulator/README.md`(目录与架构)、
> `测试说明/测试开发标准流程.md`(L3 门禁在测试分层中的位置)。
---
## 0. 一句话结论
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
> 因此 **L3(模拟器回归)与 L1/L2(板端套件 / `@test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
---
## 1. 模拟器覆盖范围(基线)
| 覆盖 | 说明 |
|---|---|
| UI 场景层 | `Drivers/BSP/UI/ui_menu.c` 等**同一份源码**,MCU 与 PC 共用(`simulator/README.md` §2.3) |
| 渲染链路 | LVGL I1 渲染 → SDL 窗口(MCU 端为帧缓冲 → RemoDispBus) |
| 配置文件引擎 | `App/cfg/cfg_file.c` 共享核心 + `cfg_file_store_sim.c`(宿主文件 `sim_config.ini`) |
| `net_*` BSD API 之上的协议 | `ui_net_sink.c`(RemoDispBus 服务端,TCP 7003)与 `net_socket_sim.c`(Winsock 后端) |
| 日志记录格式 | `sys_log` 定长记录编解码(`sim_syslog.c` 写宿主文件) |
**不覆盖**:CH395F 真实网络栈、GD5F NAND、SD2506 RTC、TPAFE5160 ADC、RS-485、IWDG、中断/时序相关行为。
---
## 2. 差距清单
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`@test` / 板端套件 / 目检)。
### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`)
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|---|---|---|---|---|---|
| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4 二期专门建模 |
| N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
| N-3 | **`net_process_messages` / `net_recv_to` 等缺失** | 未实现(MCU 专属:CH395F 消息队列驱动、带超时接收) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"带超时接收"与"消息泵"的上层逻辑在模拟器上**不可编译/不可运行** | 板端 CH395F / NET 套件 |
| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,需 `net_relisten` 显式重建 | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
| N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`,使 `DONTWAIT` 语义与 MCU 对齐(设计要点 2) | 中断驱动 + 状态机,天然非阻塞 | ——(此项是**对齐**而非差距,登记以说明"为何模拟器能测非阻塞路径") | —— |
### 2.2 日志与配置
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|---|---|---|---|---|---|
| L-1 | **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §219 |
| L-2 | **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 |
| L-3 | **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) |
| L-4 | **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——P0-1 类"KB 级分配必然返回 NULL"缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) |
### 2.3 硬件专属(模拟器完全无对应)
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|---|---|---|---|---|---|
| H-1 | **IWDG 硬件行为** | 无 IWDG;`sim_port.c` 桩(空实现 + 计数器) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑可在模拟器跑(纯 RAM) | 真机 `@test`(复位原因日志)+ 看门狗单测(L0.5 已覆盖位图逻辑)<br>`看门狗设计说明.md` §4.9 |
| H-2 | **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` |
| H-3 | **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` |
| H-4 | **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` L0.5(已覆盖算术面) |
| H-5 | **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) |
| H-6 | **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
| H-7 | **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6.8,S10 落地) |
### 2.4 哪些差异是"有意的对齐"(非缺陷)
登记以免后人误判为差距:
| 项 | 说明 |
|---|---|
| `lv_conf.h` 独立 | 模拟器 `simulator/lv_conf.h` 与 MCU `Lib/lv_conf.h` 相互独立(SDL/主题差异),**有意不统一** |
| `DBG_*` → `printf` | 模拟器 stub 覆盖 MCU 版头(`simulator/README.md` 文件职责表),**有意** |
| `NET_NO_OS` 剥离 FreeRTOS | `net_socket_sim.c` 构建时定义,剥离 RTOS 依赖,**有意** |
| 无 SDL_PollEvent | LVGL SDL 驱动内部统一轮询,主循环轮询会吞键盘事件(`simulator/README.md` 已知坑 7) |
---
## 3. 什么时候必须回真机(决策规则)
模拟器回归(L3)通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件):
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
2. **改动触及 §2.3 的 H-1~H-7 任一子系统**的初始化或运行时行为
3. **改动触及 §2.2 的 L-2/L-3/L-4**:变更检测令牌、段回调副作用、内存分配失败路径
4. **涉及网络重建 / 多连接并发**(N-2、N-5)
5. **涉及启动段(`osKernelStart` 之前)**(H-7)
6. **涉及内存预算**:新增任务/缓冲/栈(L-4;模拟器堆不可作依据)
反之,**纯 UI 场景/渲染/导航、配置解析算术、日志记录编解码格式**的改动,模拟器 + L0.5 可给出足够信心。
---
## 4. 登记约定
- **何时登记**:新增/修改 `simulator/sim/` 下任何后端、或发现"某逻辑模拟器走了不同分支"时,**必须**在 §2 加一行。
- **格式**:沿用本表列(差距 / 模拟器行为 / 真机行为 / 影响 / 回归补测)。
- **联动**:若该差距影响 L3 门禁的可信度,须同步在 `UI设计/UI_自动化测试流程.md` 引用本清单。
- **收敛**:当某差距被 N-4 二期之类的工作**消除**时,不删行——改标 ~~删除线~~ + 注明消除日期与提交,保留"曾经差在哪"的历史。
---
## 5. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-02 | 初版(S13):收录 6 条网络差距(N-1~N-6)、4 条日志/配置差距(L-1~L-4)、7 条硬件专属差距(H-1~H-7)、4 条"有意对齐"项。证据来源:`net_socket_sim.c` 文件头设计要点 + 双端导出函数逐一对比(MCU 有而 sim 缺 9 个)+ 各子系统文档既有记载汇总。§3 给出 6 条"必须回真机"决策规则 |
+34 -1
View File
@@ -218,15 +218,46 @@ static void phaseN_run(void) {
& '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6)
```
**核心保障:`try/finally` 无条件还原 `test_config.h`**(含异常 / `KeyboardInterrupt`
路径)——跑完忘还原 = **测试固件进库**。这与提交期门禁 `test-suite-switch`(§7.1)
是**两条独立防线**:前者是流程自动化、后者是门禁兜底。
> ⚠️ **不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起,
> ⚠️ **完整回归不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起,
> 且破坏性套件会改设备持久状态。仅回归窗口手工使用。
#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加 S6)
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑,
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
7 个配置实例,见 `测试与架构改进建议.md` 问题 T-4)。
```powershell
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build
```
| 项 | 说明 |
|---|---|
| 做什么 | 逐套件:改 `test_config.h` 宏 → `@build` → 记 PASS/FAIL;结束后无条件还原 |
| **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) |
| 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
| RAM 溢出 | P1-9 类"编译过但链接失败"会**自然暴露**为 `L6406E`——这正是想要的 |
| 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 |
> **2026-10-02 实测**:4 套件(CH395F / STORAGE / GD5F / RTC)**全部 `BUILD_OK: 0 errors, 0 warnings`**
> ——即 T-4 提到的"CH395F 套件不可编译"问题**已修复**,现已纳入可自检范围。
> ⚠️ **隐藏陷阱:`.map` 必须复位为产品态**(2026-10-02 实测踩到)。脚本跑完最后一轮留下的
> `STM32F407-Demo.map` 是**测试套件**的体积(RTC 套件下 ER_IROM1 达 266 KB),
> 会被 `mem-numbers` 钩子判为"文档快照与 `.map` 不一致"→ **下次提交直接 FAIL**。
> 故脚本在还原 `test_config.h` **之后**追加一次产品构建(`[..] 复位构建产物为产品态`)——
> **仅还原源码不够,必须再编一次**。同理适用于完整回归模式。
**建议用法**:改动 `test/board/` 下任何测试代码、或改 `test_config.h` 后,跑一次
`--check-build` 再提交;比逐个手工改宏构建省事,且不会忘记还原(含 `.map` 复位)。
---
## 4. 主机侧测试工具
@@ -237,6 +268,7 @@ static void phaseN_run(void) {
|---|---|---|
| Python 串口助手(serial_tool) | `tools/serial_tool/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle |
| `test/host/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 |
| **`test/run_all_suites.py`** | `test/` | **多套件回归编排**(T-5)+ **`--check-build` 只编译不烧录**(S6,纯离机,验证 4 套件可编译性);见 §3.6 |
| `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) |
| **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) |
| **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 |
@@ -582,6 +614,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
| 日期 | 变更 |
|---|---|
| 2026-10-02 | **S6**:`test/run_all_suites.py` 新增 **`--check-build`**(只编译不烧录,纯离机,不需要 MCU/串口/ST-LINK);新增 **§3.6.1** 说明其解决的问题(测试构建无独立门禁,2026-08~09 CH395F 套件实例)、判定语义与用法。**实测 4 套件全部 `BUILD_OK: 0 errors, 0 warnings`**(即 T-4 提到的"CH395F 不可编译"已修复) |
| 2026-10-02 | **S1-b + S2**:新增 **§4.2.1「L0.5 适格模块台账」**(判据 + 模块状态表 + "两条实证":sys_clock / cfg_file 首测即各抓一处生产缺陷);§4.1 登记 `test/host/cfg_file/` 新单测。同步新增 [`配置文件单元测试说明.md`](配置文件单元测试说明.md)(含抓出的 `s_encode` 返回值误判 P0 缺陷记录) |
| 2026-10-02 | **S3 + S14**:§9.1 第 2 条改正(原文"强制跑 `mem-numbers`"自相矛盾 → 改为"按 `.map` 有无自适应跳过")并加 🔴 门禁盲区声明;§9.2 补 S3 结果可见性(`Tee-Object` 落盘 + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`)。另:§4.1 登记 `test/host/sys_clock/` 新单测(S1-a) |
| 2026-10-01 | 新增 **§9 CI 持续集成(Gitea Actions,T-6)**:`.gitea/workflows/ci.yml`(`l0-gates` + `l05-host-unit-tests`,Windows runner);**L0 + L0.5 上 CI、L1/L2/L3 留本地**;刻意不跑 `@build`、`.map` 缺失时自适应跳过 `mem-numbers`;新增 `scripts/ci_local.py` 本地等价命令(§4.1 登记);§1 补 CI 说明;`docs/待处理内容.md` T-6 销项 |