台架执行《实机测试实施指南》步骤 3~7 并闭环

步骤 3/4(IWDG V2/V4/V5 + 堆低水位 S11):
- V2 任务饿死 stall 后 ≤2.5s IWDG 复位;V4 关中断死循环 ~4.9s 稳定复位;
  V5 30min 120×FTP 全 PASS、零 IWDG 复位。堆告警全程未触发(min_ever=4728B)。
- task_watchdog.c:stall 证据写盘会经 nand_ftl_wd_feed 喂狗 → 增 s_stall_evidenced
  每次 stall 仅取一次证据,避免"取证据"本身阻止复位。
- default_task.c:堆低水位告警(HEAP_LOW_WATER_B=3072)。

步骤 5(GD5F 错误通道 S9):gd5f2gq5ue.c/.h + nand_ftl.c 错误码统一为 int32_t 并
全量检查返回值;套件回归 16/16 PASSED。
- test/run_all_suites.py:--forbidden 改为按套件覆盖(GD5F 用 "HardFault;assert")——
  TC-GD5F-704 故意触发 [ERR] 回落轮询,统一默认值会误判 FAIL。

步骤 6(网络稳定性):7.1 正常路径 ✅(V5 证据);7.2 N-2 丢弃可观测性代码已具备、
49s 静默在 V5 未复现(降级低风险观察项);7.3 N-3 待 PC peer。

步骤 7(工程卫生):
- S5:新增 test-cppcheck 钩子(仅 warning,排除模拟器桩)。
- S4:新增 scripts/check_test_traceability.py(默认仅告警)。
- 待核实项 TEST_SUITE_SELECTED 实机核实 = 死代码(无使用点);RTC 套件实际正常执行,
  真缺口 = 缺套件级汇总行 → sd2506_test_task.c 补 === RTC Tests: N/M PASSED === 并
  修正 Phase 级汇总累计问题;test_config.h 补齐 RTC 并注释"当前无引用"。

验证:产品 @build 0/0;pre-commit --all-files 17/17 Passed;
run_all_suites.py --suites RTC → PASS。
文档:实机测试实施指南 / SD2506_Test_Guide / 待处理内容 / 测试与架构改进建议 同步。
This commit is contained in:
2026-10-04 00:35:06 +08:00
parent aef1d92b3f
commit 029437be89
22 changed files with 631 additions and 151 deletions
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 264,936 B (258.73 KB) | 1,048,576 B (1024 KB) | 25.3% |
| RW_IRAM1 | 84,952 B (82.96 KB) | 131,072 B (128 KB) | 64.8% |
| ER_IROM1 | 265,940 B (259.71 KB) | 1,048,576 B (1024 KB) | 25.4% |
| RW_IRAM1 | 84,960 B (82.97 KB) | 131,072 B (128 KB) | 64.8% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| **Flash (ROM 合计)** | **265,668 B (259.4 KB)** | 1,048,576 B (1024 KB) | **25.3%** |
| **Flash (ROM 合计)** | **266,676 B (260.4 KB)** | 1,048,576 B (1024 KB) | **25.4%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -794,10 +794,10 @@ external pin → "pin reset" (新增)
| **5** | `task_wd_start()` 由 `defaultTask` 在 bring-up 完成后调用 | `[WD] armed:` 出现 | ✅ 完成 |
| **6** | `configASSERT` 改造 + `vApplicationAssertFailed()` | 编译 0/0 | ✅ 完成 |
| **6.5** | **(新增)** 硬件访问收敛到薄平台层(`task_watchdog.h` 末段,3 个 `__weak` 钩子)+ 主机侧单元测试(`test/host/watchdog/`,39 用例)+ pre-commit 钩子 | `run_unit_test.py` 退出码 0;`@build` 0/0(行为不变) | ✅ **2026-10-01 完成** |
| **7** | 故障注入(V2/V4):人为制造任务卡死 | 复位 + `reset cause: IWDG` | ⏳ **待台架** |
| **7** | 故障注入(V2/V4):人为制造任务卡死 | 复位 + `reset cause: IWDG` | ✅ **2026-10-03 台架**(V2/V4 均触发 IWDG 复位) |
| **8** | 模拟器 `--watchdogtest` + stub | S1 | ⏳ 待做(位图逻辑已由 U1 覆盖,优先级降低) |
| **9** | 长跑(V5,≥30 min,真实负载) | **零复位** | ⏳ **待台架** |
| **10** | 本文档改写为"现状设计说明";`待处理内容.md` N-03 销项;`README.md` 登记 | 门禁 | ⏳ 待第 7~9 步结论 |
| **9** | 长跑(V5,≥30 min,真实负载) | **零复位** | ✅ **2026-10-03 台架**(30 min / 120×FTP 零复位) |
| **10** | 本文档改写为"现状设计说明";`待处理内容.md` N-03 销项;`README.md` 登记 | 门禁 | ✅ N-03 已销项(第 7~9 步结论已入本文变更记录) |
### 10.1 第 0 步实测:CubeMX 重生成覆盖了哪 4 处
@@ -1114,3 +1114,4 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动
| 2026-10-01 | **查证 `fault_handler_enable()` 的 CubeMX 可行性** | 用户问"能否通过 CubeMX 配置"→ **否**。查 CubeMX 安装目录模板源码得三重证据:① `tpl_main_c.ftl` 的 `MX_NVIC_Init()` 只在 `systemHandler=="false"` 时生成 `HAL_NVIC_EnableIRQ`,而 `NVIC-STM32F417_Modes.xml` 中三个 Fault 都标 `Y`(=systemHandler)→ 永不生成使能代码;② **全部模板中 `SHCSR` 只出现 1 次**且是 MPU 清理的 `&= ~MEMFAULTENA`(禁用),**无任何 `\|=` 置位**;③ `.ioc` 无 MPU(`Mcu.IP` 列表),且 `HAL_MPU_Enable` 只置 `MEMFAULTENA` 一位。实测印证:`HAL_MspInit()` 里三个 Fault 一行代码都没生成。结论:只能手工写,但属 **L2**(`USER CODE BEGIN 1` 区)→ 重生成不丢,有审计项兜底。另新增警示:`.ioc` 的 `NVIC.MemoryManagement_IRQn=true` **不代表已使能**(第 1 段只是"向量可见")。写入 §10.2 与四个 Fault 采集小节 |
| 2026-10-01 | **新增 §7.3 主机侧单元测试(L0.5)** | 硬件访问收敛到薄平台层(`App/util/task_watchdog.h` 末段声明 3 个 `__weak` 钩子:喂狗/启动/取 tick),MCU 端行为零变化(`@build` 0/0)。新建 `test/host/watchdog/`(`watchdog_test.c` 39 用例 + `run_unit_test.py` MSVC 运行器 + `stub/` 三桩头),`#include` 生产源码以保证测的是真代码。挂 pre-commit `watchdog-unit-test` 钩子。新增 `docs/测试说明/看门狗单元测试说明.md`;`App/util/README.md`、`test/host/README.md`、`测试开发标准流程.md`(新增 L0.5 层)同步。顺带改善:`s_last_tick` 由函数局部 static 提为文件 static(可测性 + 模块状态归一)。§5.1 文件表与 §10 步骤表登记;§7.2 S1 优先级降低(位图逻辑已由 U1 覆盖) |
| 2026-10-02 | **P0 根因修复:IWDG 超时被静默配成复位默认值(比设计短 5 倍)** | round 3~8 反复出现的"空 STOR(0 字节截断)→ IWDG 误复位"**根因不是"poller 自身停摆"**。真因:`Src/iwdg.c` 为实现**延后启动**(§4.5 决策 C)删掉了 `HAL_IWDG_Init()`(`IWDG-DEFER-START-ANCHOR`),而 `HAL_IWDG_Init()` 是**唯一**会把 `hiwdg.Init.Prescaler/Reload` 写进 IWDG 寄存器的路径——删掉后 `MX_IWDG_Init()` 只填结构体,`WD_IWDG_PRESCALER`/`WD_IWDG_RELOAD` 沦为**死宏**,寄存器停在**复位默认值** `PR=0`(÷4)/`RLR=4095`。实测(SWD 回读)实际超时 = 4×4096/LSI ≈ **349~512 ms**,而设计是 64×1250/LSI ≈ **1701~2498 ms**。两个后果:① `WD_MISS_TOLERANCE`(8×100ms=800ms)**永远等不到**就被 IWDG 复位 → **停喂判定分支成死代码**(这正是 §7"STALL 从未执行"的真正原因,当时据此误判为"poller 停摆");② 任何 >400ms 的心跳缺口都直接误复位(空 STOR 的 FTL flush 使 NET/FTP/UI 心跳缺 ~400ms)。**修复**:`wd_platform_start()`(`__weak`,**不在 CubeMX 生成区**,重生成不丢)补上按设计值配置:解锁(`KR=0x5555`) → `PR` → 等 `SR.PVU` 清 → `RLR` → 等 `SR.RVU` 清 → `KR=0xCCCC`(不等同步会静默丢弃后写的那只,SWD 实测)。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`,故这是全固件**唯一**写 PR/RLR 的点。**验证**:SWD 现场改回设计值后,同一空 STOR 由 **5/5 复位 → 100% 成功**;固件烧录后 3 次开机回读均为 `PR=4/RLR=1249`。`@build` 0/0;本地 CI L0+L0.5 全绿(278/278 断言) |
| 2026-10-03 | **V2/V4/V5 台架验收闭环(N-03 销项)** | 三用例全部通过。**V2 任务饿死**:`netTask` 循环插 `while(1)` → 800ms 声明 `[WD] task stall: mask=0x00000001 req=0x00000037`,≤2.5s 触发 IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。⚠️ 首轮实测**不复位**(stall 已声明、`miss` 累至 1157≈115s):根因是**停摆证据落盘自身喂狗**——`wd_stall_evidence()`→`sys_log_evidence()` 在停喂窗口里每 100ms 轮都写 NAND(`/log/wdev.log` + `SYSLOG_RUN` flush 诱导),经 `nand_ftl_wd_feed()` **直接重装 IWDG**,"停喂"语义被证据机制抵消。修复:加 `s_stall_evidenced` **每 episode 门闩**(每轮停摆只落盘一次,此后不再触碰 NAND)→ 复测复位正常。**V4 关中断死循环**:`__disable_irq()` 后插 `while(1)` → 设备每 ~4.9s 稳定复位(20+ 周期,`reset cause: IWDG`),证实 IWDG **独立于中断**(PRIMASK 屏蔽 TIM7/SysTick/PendSV 仍能复位)。**V5 长期不误杀**:FTP 512KB 上/下传 + `e02_probe` 网络探针持续 **30 min** → **120/120 次 FTP 传输全 PASS(MATCH)、零 IWDG 复位**(唯一复位为起始烧录的 `software (NVIC_SystemReset)`)。Step 4 堆告警全程未触发(`min_ever=4728 B` > 3072 B)。**遗留观察(已登记 `待处理内容.md`)**:V5 期间 44 次 `task stall`(`mask=0x00000001` 持续 ≥800ms)**100% 与 `e02_probe` 的 30s 挂起客户端→`time_sync` 掉线+监听重建(`listen FAIL`×2,约 4s)重合**;隔离实验证实「仅 FTP(无探针)120s = 0 次 stall」→ 该停摆为**探针场景专属**,非一般负载误杀。因缺口(~1s)< IWDG 最坏超时(1701ms)而未复位,与 §4.3 包含关系一致 |
+1 -1
View File
@@ -81,7 +81,7 @@
| 序 | 任务 | 说明 | 出处 |
|---|---|---|---|
| **C1** | **pending 二期(ACCEPT + SEND)** | 沿一期机制扩展;同期消化 E-25(net_select 语义)、E-24/E-27/E-28(跨上下文收口);基线重锁 + 探针族扩充 + 看门狗接 selftest。**设计基线见下方 §2.1** | 第二档 #2 / N-4 |
| **C2** | **GD5F 错误通道统一** | E-10/E-42/E-53/E-54/E-56/E-57 一次做齐:`wait_busy` 区分超时/真故障、`bbt_scan` 失败**放弃持久化**(保护好副本)、init 全链路检查 + 重试、回读 B0h 校验 ECC | 第二档 #1 |
| ~~**C2**~~ | **GD5F 错误通道统一** | ✅ **已闭环(2026-10-03)**:E-10/E-42/E-53/E-54/E-56/E-57 一次做齐——`wait_busy` 区分超时/真故障、`bbt_scan` 失败**放弃持久化**(保护好副本)、init 全链路检查、回读 B0h 校验 ECC;台架套件回归 `16/16 PASSED (failed=0)` | 第二档 #1 |
| **C3** | **存储侧日志管线**(消化 N-2) | 49s 全系统日志静默的复现与定位(NAND GC 长尾 vs UART 卡滞)+ 队列满丢弃计数/告警(现静默丢行);与 S8(非阻塞日志)合并评估 | N-2 / S-1 |
| **C4** | **defaultTask 栈余量**(S-1) | 2872B/4096(余量 29.9%,差 4.8B 未达 30% 判据)→ 栈 4096→4608(CCM 堆余量可覆盖)或 GC 链局部削减 | S-1 |
| **C5** | 一档剩余 + P2 收口 | E-49(RS485 DE 收尾)、E-15(ringbuf 定去留——**已确认零调用者,倾向删除**)、E-61(SD2506 注释订正)、E-16(cfg_file 段名长度)、E-26(UDP 定案,倾向移除)、E-23、E-30 | 一档/二档 |
@@ -413,7 +413,7 @@ App/util/ringbuf.c 154 行 → 零 `#include "ringbuf.h"`,不在 Keil 工
> **本文增量**:本项目 CI 是**自建 Gitea 内网实例**,"能否拉外层 action"是现实约束——
> 建议**优先做零依赖方案**(步骤 2/3),而不是先引入 `upload-artifact`。
### 3.4 S4:复核"文档用例 ↔ 代码断言"覆盖矩阵 🟡
### 3.4 S4:复核"文档用例 ↔ 代码断言"覆盖矩阵 ✅ **已落地(2026-10-03)**
**问题**:文档声称 91 个 TC、代码有 222 个断言点,但**两者是否一一对应无人校验**。
`测试开发标准流程.md` §2.3 铁律 3 要求"每条设计原则必须有对应用例",
@@ -433,7 +433,11 @@ scripts/check_test_traceability.py
**完成判据**:能一键列出双向差异清单;差异收敛后可考虑升为门禁。
### 3.5 S5:给 `test/` 代码加"宽松"静态检查 🟡
> **✅ 2026-10-03 落地**:`scripts/check_test_traceability.py` 已实现并跑通,
> **默认仅告警(exit 0)**。当前输出 **30 处差异**(26 处"文档有、代码无" +
> 4 处"代码有、文档无":`TC-GD5F-1100/1101/1102`、`TC-STO-06`)。**暂不升门禁**。
### 3.5 S5:给 `test/` 代码加"宽松"静态检查 ✅ **已落地(2026-10-03)**
**目标**:抓 `test/board/` 6300+ 行固件测试代码的**真实缺陷**(未初始化/越界/空指针),
但不引入存量 style 噪音。
@@ -453,6 +457,11 @@ scripts/check_test_traceability.py
> 本建议**不是推翻它**,而是"在不产生大量噪音的前提下,补上致命缺陷的检查"。
> 若基线告警过多,**保持现状亦可接受**——此时应把该决策写得更明确("理由:存量告警 N 条")。
> **✅ 2026-10-03 落地**:走**路径 2**——基线实测**0 条 warning**(存量可控),
> 已加 `test-cppcheck` 钩子(`.pre-commit-config.yaml`,`--enable=warning`
> + 排除 `test/host/*/stub/` 模拟器桩)。`pre-commit run test-cppcheck --all-files`
> **PASS**;新增 `test/board/` 代码的未初始化/越界/空指针现被自动拦截。
### 3.6 S6:测试构建可编译性纳入验证(= 上游 P1-10 增量)🟡
**目标**:防止"测试套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。
@@ -515,7 +524,7 @@ scripts/check_test_traceability.py
**完成判据**:文件不存在;`App/util/README.md` 同步;`@build` 0/0(本就不参与构建,应无影响);
全库无 `crc.h` / `ringbuf.h` 引用(E-15 销项)。
### 4.2 S9(原 A-2):统一 GD5F 错误通道(= 上游 C2 骨架)🔴
### 4.2 S9(原 A-2):统一 GD5F 错误通道(= 上游 C2 骨架)✅ **已闭环(2026-10-03)**
见 §2.3 的四要素骨架。**实施顺序建议**:
@@ -639,17 +648,18 @@ scripts/check_test_traceability.py
| | 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 | 用例追溯脚本 | 无 | 中 |
| **第 3 波**(架构加固) | S7 | 真机回归加自检判定 | 无 | 中 | ✅ 2026-10-02(`@test` PASS 7/7) |
| | S11 | 堆低水位告警 | 无 | 小 | ✅ 2026-10-03(V5 长跑未触发,基线 min_ever=4728 B) |
| | S9 | GD5F 错误通道(= 上游 C2) | 无 | 大 | ✅ 2026-10-03(台架套件回归 16/16) |
| | S5 | `test/` 宽松静态检查 | 先跑基线 | 小~中 | ✅ 2026-10-03(`test-cppcheck` 钩子,`--enable=warning`) |
| | S4 | 用例追溯脚本 | 无 | 中 | ✅ 2026-10-03(`scripts/check_test_traceability.py`,默认告警) |
| **第 4 波**(按需) | S12 | 模块级断言宏 | 试点验证 | 小(试点) |
| | S13 | 模拟器差距清单 | 无 | 小 | ✅ 2026-10-02 |
> **"⏸ 判据需真机"说明**(2026-10-02 补):S7 / S11 / S9 的**代码可离线编写**,但其**完成判据**
> **"⏸ 判据需真机"说明**(2026-10-02 补;**2026-10-03 台架窗口三项全部闭环**):S7 / S11 / S9 的**代码可离线编写**,但其**完成判据**
> 均要求真机观测(`@test` 捕获自检失败 / 台架长跑观察到堆告警 / GD5F 套件回归 PASS)。
> 在无 MCU 的环境下实施会留下"已改未验证"的债,故押后至有实机的窗口。
> ✅ **2026-10-03 台架**:S7(`@test` PASS 7/7)、S11(V5 长跑未触发,基线 `min_ever=4728 B`)、S9(GD5F 套件 16/16 PASS)全部闭环。
### 5.2 与上游任务的关系
+3 -2
View File
@@ -10,7 +10,7 @@
| # | 事项 | 发现 | 证据与现状 | 出处 |
|---|---|---|---|---|
| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行(testTask 10s 周期行/netTask 事件行/[STACK] 行全缺),期间 net 栈照常处理外部连接(FTP banner 92ms)→ 日志管线(defaultTask 排空 → NAND+UART)停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22 17:23:52~17:24:41(L3 台架 F2 运行中,gen 181) | **A/B 归因:L3 不触碰 defaultTask/syslog 路径 → 既有存储侧现象**。待办:① 复现条件定位(NAND 写/GC 长尾 vs UART TX 卡滞——对照 `CH395F` 无关的 SD2506 侧日志)② 日志队列满的丢弃计数/告警(现静默丢行,排障不可见)③ 与 S8(非阻塞日志)合并评估 | `错误码传播审计.md` E-02 阶段二"台架新发现②";`链路延迟测试说明.md` §6.7 注 |
| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行(testTask 10s 周期行/netTask 事件行/[STACK] 行全缺),期间 net 栈照常处理外部连接(FTP banner 92ms)→ 日志管线(defaultTask 排空 → NAND+UART)停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22 17:23:52~17:24:41(L3 台架 F2 运行中,gen 181) | **A/B 归因:L3 不触碰 defaultTask/syslog 路径 → 既有存储侧现象**。**2026-10-03 V5 核实(降级为低风险观察项)**:① 丢弃可观测性**代码已有**——`console_log.c` 的 `s_con_drop` 计数 + 排空末尾 `[ERR] CONSOLE dropped N lines` + `dbg_console_dropped()`;`sys_log_mcu.c` 队列满/preboot 满分别 `DBG_ERROR("queue full/preboot full, drop: …")`;UART TX 卡滞由**僵尸通道自愈**覆盖(`con_uart_reinit` + `s_con_tx_fault`)→ "现静默丢行、排障不可见"**已不成立**;② V5 30 min 日志间隔分析**未复现 49s**(最大静默 41s 且在**开机/烧录边界**;运行期 24 处 >10s 静默**全为 ~28s、均在 `e02_probe` 的 time_sync 30s 窗口内**= N-17 同源)。残余:③ 与 S8(非阻塞日志)合并评估;若再现,借 `[ERR] CONSOLE dropped` 计数定位卡点 | `错误码传播审计.md` E-02 阶段二"台架新发现②";`链路延迟测试说明.md` §6.7 注;`实机测试实施指南.md` §7.2 |
| N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变、pending tick 无 pending 时为 µs 级指针读 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ |
## 存储 / 日志管线
@@ -30,12 +30,13 @@
|---|---|---|---|
| N-4 | **pending 机制二期:ACCEPT + SEND 阻塞路径 pending 化**——E-06(accept 轮询计时失真)、E-20(lftpd 控制通道 errno 分类/501)、E-21(timeSync errno 分级)剩余分量共用该机制扩展;届时 L3/L5 基线再重锁、探针族扩充(close/reconfig/EOF/disconnect-during-pending)、看门狗接 selftest 留痕、模拟器差距清单 +1 | 一期(RECV)已落地并入主线:`a45c0cb`(+文档闭环 `7ff2958`);架构现状见 `Net层阻塞模型架构设计.md`,二期设计基线与门禁见 `下一步任务规划.md` §3.1 | 同左 + `错误码传播审计.md` 第二档 #2 |
| N-16 | **A2/A4 专项场景台架验证待办**(正常路径已实测零回归,2026-10-03):① PHY 拔线负向测试——断链需断 **DTU 侧**线缆或关**受管交换机端口**(拔交换机侧线缆链路仍 UP,CH395 对交换机拓扑),断链后复位验证 `PHY: DISCONNECT` + 自检台账 `comms fault` 降级放行;② A2 芯片级 `net_init` 失败分支(需 SPI 故障注入,验证 `net init fail` 自检 + 30s 重试恢复);③ A4 短写压测(需远显客户端 + UI 推流压力,验证 `remo send short` 告警/阻断/重连恢复) | 2026-10-03(台架;台架拓扑 = CH395↔交换机,拔线操作受限) | `实机测试实施指南.md` §10(已完成事项与证据去向) |
| N-17 | **`time_sync` 30s 挂起客户端掉线 + 监听重建期间,网络任务心跳缺口达 800ms~1s(声明 stall 但未复位)**——2026-10-03 V5 长跑(FTP+e02_probe,30 min)期间 44 次 `task stall: mask=0x00000001`(NET/FTP/UI/TIMESYNC 四位同时缺席 ≥800ms),**100% 与 `e02_probe` 的 30s 挂起客户端触发 `time_sync: no TIME packet in 30s, drop conn` → 监听重建(`time_sync: listen FAIL`×2,约 4s)重合**;隔离实验(仅 FTP、无探针,120s)**0 次 stall** → 该停摆为**探针场景专属**。缺口(~1s)< IWDG 最坏超时(1701ms)故未复位,但余量仅 ~0.7s。**待办**:定位监听重建为何阻塞 NET/FTP/UI(疑 CH395F socket 释放 / `net_listen` 重试期间的队列头阻塞,与 E-02 同源);评估缩短重建重试(5×2s)或让掉线路径不阻塞其它任务 | 2026-10-03(V5 台架;证据 `/tmp/v5_soak_serial_20261003.log`) | `实机测试实施指南.md` §4;`子系统设计/看门狗设计说明.md` 2026-10-03 变更记录 |
## 可靠性与自恢复
| # | 事项 | 说明 | 出处 |
|---|---|---|---|
| N-03 | **引入独立看门狗(IWDG)+ 任务心跳位图**——现状全库无看门狗(HAL 驱动源码也不在仓库),任何形态死机不可自恢复;另 `configASSERT` 为关中断死循环(连日志都不打)。**代码已落地并编译通过(2026-09-29)**:CubeMX 生成 IWDG(Prescaler=64/Reload=1249)+ `App/util/task_watchdog.{c,h}` 心跳位图 + 6 任务埋点 + `RCC_CSR` 复位原因锁存 + `configASSERT` 改造 | **剩台架验证**(故障注入 V2/V4、长跑 V5 ≥30 min);文档 §10 第 7~10 步待办 | **见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md)**(§10 有实施状态表) | `审查记录/项目审查与优化建议报告.md` N-03a/b/c |
| N-03 | **引入独立看门狗(IWDG)+ 任务心跳位图**——现状全库无看门狗(HAL 驱动源码也不在仓库),任何形态死机不可自恢复;另 `configASSERT` 为关中断死循环(连日志都不打)。**代码已落地并编译通过(2026-09-29)**:CubeMX 生成 IWDG(Prescaler=64/Reload=1249)+ `App/util/task_watchdog.{c,h}` 心跳位图 + 6 任务埋点 + `RCC_CSR` 复位原因锁存 + `configASSERT` 改造 | ✅ **台架验证已完成(2026-10-03)**:V2(任务饿死)声明 stall 后 ≤2.5s `reset cause: IWDG`;V4(关中断死循环)每 ~4.9s 稳定 IWDG 复位(独立于中断);V5(30 min 真实负载)120/120 次 FTP PASS、零复位。文档 §10 第 7~10 步已闭环 | **见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md)**(§10 有实施状态表) | `审查记录/项目审查与优化建议报告.md` N-03a/b/c |
| N-03a | `configASSERT(x)` = `taskDISABLE_INTERRUPTS(); for(;;);`(`Inc/FreeRTOSConfig.h`)→ 断言时无任何输出 | ✅ **已修(2026-09-29)**:改为 `vApplicationAssertFailed(__FILE__, __LINE__)`,打印文件名+行号后主动复位(`Src/freertos.c`);宏已迁入 `USER CODE BEGIN 1` 区防重生成丢失 | 同上 N-03b |
| N-03b | 启动段(`app_main_init` + `defaultTask` bring-up)在新方案下**无看门狗保护** | 显式代价(未变):`看门狗设计说明.md` §4.5 决策 C + §9 R1;该段死机表现为"无输出 + LED 不闪" | 同上 N-03c |
| N-03c | **CubeMX 重生成会覆盖 `USER CODE` 标记区之外的定制** | ⚠️ **2026-09-29 两次重生成实测确认**:首次覆盖 4 处、第二次覆盖 5 处(P0 `Src/iwdg.c` 自动启动、P1 `HardFault_Handler` 打回空死循环、P1 `FreeRTOSConfig.h` 两个加固宏被删、P2 `freertos.c` 中文注释)。✅ **已建立自动检出**:`scripts/check_cubemx_survival.py`(18 项清单 + 哈希基线,已挂 pre-commit)——第二次实测**准确检出全部 5 处**。⚠️ 关键发现:CubeMX **换了一代中断模板**,"生成区外的自造标记区"同样不被保留(详见设计文档 §10.1)。**今后每次重生成后跑一次该脚本即可** | 本次实施记录 |
@@ -3,6 +3,13 @@
> 本规范参照 `docs/测试说明/CH395F_Test_Guide.md` 结构编写,覆盖 `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c`
> 全部公开/私有函数,并向上延伸到 FTL(`nand_ftl.c`)与 FatFS diskio。
> 详细芯片行为见 `docs/芯片手册/GD5F2GQ5UExxG.md`,已知陷阱见 `docs/问题记录/GD5F2GQ5UE_Trap_Records.md`。
>
> **2026-10-03 错误通道硬化**(E-10/E-42/E-53/E-54/E-56/E-57,见 `问题记录/GD5F2GQ5UE_Trap_Records.md` Trap 06):
> `wait_busy` 区分"超时"(`GD5F_BUSY_TIMEOUT`)与"真故障"(`GD5F_ERROR`)、`bbt_scan` 失败**放弃持久化**、
> init 全链路检查返回值 + **回读 B0h 校验 ECC 使能**、`read` 的 raw 契约文档化、公开 API 统一 `int32_t`。
> 套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`。
> ⚠ **编排器注意**:`TC-GD5F-704` 会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable`(回落轮询,用例 PASS)——
> 用 `run_all_suites.py` 跑本套件时 `--forbidden` 须去掉 `[ERR]`(脚本已按套件覆盖,见其 `SUITES` 注释)。
## 1. 概述
+11 -4
View File
@@ -84,17 +84,24 @@ MDK-ARM\build.bat
- `sd2506_test_run()` 由测试调度器调用,先 init 并记录原始时间,最后用
`sd2506_set_time` 把板子时间**恢复**为测试前的值(不污染真实时钟)。
- 输出形如:
- 输出形如(**只读模式**,默认):
```
[RTC_TEST] === SD2506 Phase 1: basic read/write (public API) ===
[RTC_TEST] [PASS] sd2506_init() ret=0 (expect 0)
...
[RTC_TEST] === Phase 1 basic: 6/6 PASSED (failed=0) ===
[RTC_TEST] === Phase 1 basic: 3/3 PASSED (failed=0) ===
[RTC_TEST] === SD2506 Phase 2: full API coverage ===
...
[RTC_TEST] === Phase 2 API: 9/9 PASSED (failed=0) ===
[RTC_TEST] === Phase 2 API: 6/6 PASSED (failed=0) ===
[RTC_TEST] === RTC Tests: 9/9 PASSED (failed=0) ===
```
- 通过标准:所有 Phase 的 `failed=0`,且总量 `X/Y PASSED` 中 Y 与 PASS 数相等。
- **各 Phase 汇总行只反映本阶段**(进入各 Phase 前统计自清零);末尾
`=== RTC Tests: N/M PASSED (failed=0) ===` 是**套件级**汇总,也是
`run_all_suites.py --require-suite-summary "RTC Tests"` 的判定标记
(见 `test_common.h` / `测试开发标准流程.md` §3.2)。
- **破坏性模式**(`ENABLE_DESTRUCTIVE_TESTS`)额外跑 TC-RTC-002 / 014 / 015,
计数变为 Phase 1 = 6/6、Phase 2 = 11/11、套件级 = 17/17。
- 通过标准:所有 Phase 与套件级的 `failed=0`,且每行 `X/Y PASSED` 中 X = Y。
## 6. 失败排查
+83 -40
View File
@@ -23,11 +23,11 @@
|---|---|---|---|
| **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 |
| **步骤 2** | 产品 `@test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 |
| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A / N-03) | ⬜ 待执行 | §4 |
| **步骤 4** | 堆低水位告警实测(P1-B / S11) | ⬜ 待执行 | §5 |
| **步骤 5** | GD5F 错误通道回归(P2-A / S9) | ⬜ 待执行 | §6 |
| **步骤 6** | 网络稳定性与基线重锁(P2-B / P1-8 + N-2/N-3) | ⬜ 待执行 | §7 |
| **步骤 7** | 工程卫生(P3 / S5 + S4 + 待核实项) | ⬜ 待执行 | §8 |
| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A / N-03) | ✅ 已闭环 | §4 |
| **步骤 4** | 堆低水位告警实测(P1-B / S11) | ✅ 已闭环 | §5 |
| **步骤 5** | GD5F 错误通道回归(P2-A / S9) | ✅ 已闭环 | §6 |
| **步骤 6** | 网络稳定性与基线重锁(P2-B / P1-8 + N-2/N-3) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 |
| **步骤 7** | 工程卫生(P3 / S5 + S4 + 待核实项) | ✅ 已闭环 | §8 |
> **每步出口**:`@build` 0/0 → 一轮 `@test` PASS → ST-LINK 全片回读 `No difference found` →
> 上游登记销项 + 文末变更记录留痕。
@@ -46,7 +46,7 @@
| A2 | 产品构建 0/0 | `& "MDK-ARM\build.bat"` | 末行 `BUILD_OK: 0 errors, 0 warnings` |
| A3 | 构建产物为**产品态** | 检查 `test/board/test_config.h` L30-33 | 四个 `TEST_SUITE_*` **全部注释** |
| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 round 1~6)→ 264,284 B(round 7~9)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 |
| A5 | 全量静态门禁 | `pre-commit run --all-files` | **16/16 Passed** |
| A5 | 全量静态门禁 | `pre-commit run --all-files` | **17/17 Passed**(2026-10-03 实测;含新增 S5 `test-cppcheck`) |
| A6 | ST-LINK 探针可见 | `& "MDK-ARM\reset.bat" list`(CLI 经 `DTU_STLINK_CLI` / PATH 解析) | 列出探针 SN `0B250D12A218303030303032` |
| A7 | 串口端口正确 | 检查 `.opencode/serial_config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` |
| A8 | 串口助手 daemon 可用 | `& '.venv\Scripts\python.exe' tools/serial_tool/serial_tool.py status` | 返回 JSON,`open: true/false` 可读 |
@@ -97,7 +97,11 @@
---
## 4. 步骤 3:IWDG 故障注入与长跑(P1-A,N-03 收尾) · ⬜ 待执行
## 4. 步骤 3:IWDG 故障注入与长跑(P1-A,N-03 收尾) · ✅ 已闭环
> **2026-10-03 台架结论**(实施细节与证据见 `子系统设计/看门狗设计说明.md` 变更记录):
> V2/V4/V5 三用例全部通过——V2(任务饿死)声明 stall 后 ≤2.5s `reset cause: IWDG`;V4(关中断死循环)每 ~4.9s 稳定 IWDG 复位(IWDG 独立于中断);V5(30 min 真实负载)**120/120 次 FTP 传输全 PASS、零 IWDG 复位**。
> **遗留观察**:V5 期间 44 次 stall 声明 100% 与 `e02_probe` 探针的 time_sync 30s 掉线+监听重建重合;隔离实验(仅 FTP 120s)**0 次 stall** → 探针场景专属,非一般负载误杀(已登记 `待处理内容.md`)。
> 来源:`子系统设计/看门狗设计说明.md` §10 步骤 7~10;`待处理内容.md` N-03。
> 判定状态机与 IWDG 配置(PR/RLR 按设计值写入)已落地并经真机验证(2026-10-02,`930215f`,
@@ -149,7 +153,9 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
---
## 5. 步骤 4:堆低水位告警实测(P1-B,S11) · ⬜ 待执行
## 5. 步骤 4:堆低水位告警实测(P1-B,S11) · ✅ 已闭环
> **2026-10-03 台架结论**:告警代码(`default_task.c` 的 `HEAP_LOW_WATER_B=3072` 门闩 + `[HEAP]` 告警 + syslog 自检)已落地;复用 §4.2 V5 窗口 30 min 长跑,**全程未触发**(`min_ever=4728 B` > 3072 B)→ 记录"当前负载下 min_ever 仍高于阈值"为基线,已更新 `内存管理/RAM使用分析与优化方案.md`。
**现状证据**:2026-10-03 上板复测 `free=8,688 / min_ever=3,712~4,728 B`
(两次开机,运行间波动 ≈1KB;初始 `configTOTAL_HEAP_SIZE` 48KB)——
@@ -178,7 +184,11 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
---
## 6. 步骤 5:GD5F 错误通道统一(P2-A,S9 = 上游 C2) · ⬜ 待执行
## 6. 步骤 5:GD5F 错误通道统一(P2-A,S9 = 上游 C2) · ✅ 已闭环
> **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地(E-10/E-42/E-53/E-54/E-56/E-57 全部销项,见 `错误码传播审计.md`)。
> 套件回归:`test/run_all_suites.py --suites gd5f` → **`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`**(只读模式,4 个破坏性用例按设计跳过);编排器 `[GD5F] -> PASS`。
> **顺带修复(编排器缺陷,非本次代码引入)**:`run_all_suites.py` 原对所有套件统一 `--forbidden "HardFault;assert;[ERR]"`,而 GD5F 的 `TC-GD5F-704`(CCM 非 DMA 可达缓冲)会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable ... fallback poll`(用例 PASS)→ GD5F 套件**永远被判 FAIL**(自 `2055de0` 起)。按 `verify_log.py` 的说明改为**按套件覆盖 `--forbidden`**(GD5F = `HardFault;assert`),修复后 PASS。
> **来源**:`错误码传播审计.md` 第二档 #1(E-10/E-42/E-53/E-54/E-56/E-57);
> `测试与架构改进建议.md` §2.3 / §4.2 四要素骨架。
@@ -224,9 +234,9 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
---
## 7. 步骤 6:网络稳定性与基线重锁(P2-B) · ⬜ 待执行
## 7. 步骤 6:网络稳定性与基线重锁(P2-B) · ◐ 部分闭环(7.1/7.2 已闭环;7.3 待 PC peer)
### 7.1 P1-8 网络重建失败恢复(A2;实施细节见 `网络/Net层阻塞模型架构设计.md`,状态见 §10)
### 7.1 P1-8 网络重建失败恢复(A2;实施细节见 `网络/Net层阻塞模型架构设计.md`,状态见 §10) · ✅ 正常路径已闭环
**判据**:拔插网线/改 IP 触发重建后——
- 失败路径**写 syslog 自检**(不再零日志);
@@ -235,31 +245,50 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
**验证手段**:`test/host/e02_probe.py`(网络探针)+ UI 网口设置页改 IP。
### 7.2 N-2:49s 全系统日志静默复现与定位
**2026-10-03 台架结论**:
- **正常路径 ✅**:V5 长跑(30 min,FTP 512 KB 上/下传)**120/120 PASS、零 IWDG 复位**,
网络重建后的 FTP/time_sync 服务据代次号重建监听、功能正常(证据 `/tmp/v5_soak_serial_20261003.log`)。
- **负向路径(拔线/芯片级 `net_init` 失败)**受**台架拓扑**限制(CH395↔交换机,
拔交换机侧线缆链路仍 UP;芯片级失败需 SPI 故障注入)→ 已登记 `待处理内容.md` **N-16**
(① PHY 拔线负向 ② A2 `net_init` 失败分支 ③ A4 短写压测),需专项窗口。
### 7.2 N-2:49s 全系统日志静默复现与定位 · ✅ 判据②已满足 / 判据①未复现
**现状证据**(登记簿 N-2):2026-09-22 台架实测,串口日志 **49s 无行**
(testTask/netTask/[STACK] 行全缺),期间 net 栈照常处理外部连接 → **日志管线停滞**,
队列满后各任务日志行被丢;系统功能不受损。
**目标**:复现条件定位(NAND 写/GC 长尾 vs UART TX 卡滞)+ 队列满的丢弃计数/告警
(**现静默丢行,排障不可见**)。
**判据**:
- 复现 49s 静默并定位到具体卡点(NAND GC / UART TX);
- 实现**队列满丢弃计数**(至少一个 `DBG_ERROR` 或计数器经 UI/日志可见)。
**判据与核实(2026-10-03)**:
- **判据②(队列满丢弃计数/告警)→ ✅ 代码已满足**:日志管线**已有**显式丢弃可观测性——
`App/log/console_log.c` 的 `s_con_drop` 计数 + 排空末尾 `[ERR] CONSOLE dropped %lu lines`
提示 + `dbg_console_dropped()` 访问器;`App/log/sys_log_mcu.c` 的队列满/preboot 满分别
打 `DBG_ERROR("queue full, drop: …")` / `("preboot full, drop: …")`。另 UART TX 卡滞
已由**僵尸通道自愈**覆盖(`con_uart_reinit()` + `[WARN] CONSOLE tx fault: UART reinit`,
`s_con_tx_fault` 计数)。**"现静默丢行、排障不可见"已不成立**。
- **判据①(复现 49s 并定位卡点)→ 未复现**:对 V5 30 min 长跑日志(6031 条带时间戳行)
做间隔分析,**最大静默 41s**(发生在**开机/烧录边界**,非运行期),**运行期无 49s 静默**。
运行期 >10s 的静默共 24 处,**全部**为 ~28s,且**均落在 `e02_probe` 的 time_sync 30s
挂起客户端窗口内**——与 **N-17** 同源(探针场景专属,非通用负载)。
- **结论**:49s 静默在当前固件 + V5 负载下**不复现**;可观测性缺口已由既有代码补齐。
N-2 降级为**低风险观察项**(保留登记,若再现则借 `[ERR] CONSOLE dropped` 计数定位)。
**执行步骤**:长跑(§4.2 V5)期间周期性打点 + 对照 `CH395F` 无关的 SD2506 侧日志判别卡点。
### 7.3 N-3:L5 队列往返基线重验
### 7.3 N-3:L5 队列往返基线重验 · ⬜ 待 PC peer
**现状**:L5 基线(1223/988µs)属**分片时代**;L3 对该路径逐位不变 → 机制上无影响,
但严格重锁需跑一轮 Phase 13(**需 PC peer**)。
**判据**:Phase 13 窗口跑一轮,基线数值与阈值余量(>8×)对照,重锁数值。
**2026-10-03 台架**:本窗口**无 PC peer**,Phase 13 无法执行 → 保留待办(低风险:
阈值余量 >8×,机制上 L3 对该路径逐位不变)。
---
## 8. 步骤 7:工程卫生(P3) · ⬜ 待执行
## 8. 步骤 7:工程卫生(P3) · ✅ 已闭环
> S5 / S4 落地(离机可做),待核实项已实机核实并修复。本节留复跑命令与判据。
### 8.1 S5:`test/` 代码宽松静态检查
@@ -281,6 +310,11 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base
**判据**:`test-cppcheck` 在 CI PASS;新增测试代码越界/未初始化被自动拦截。
> **注意**:若基线告警过多,**保持现状亦可接受**——此时应把该决策写明确("理由:存量 N 条")。
**结论(✅ 已闭环,2026-10-03)**:已加 `test-cppcheck` 钩子(`.pre-commit-config.yaml`
`id: test-cppcheck`,**仅开 `warning`、排除模拟器桩/第三方头**)。基线可控,
`pre-commit run test-cppcheck --all-files` **PASS**。新增 `test/board/` 代码的
未初始化/越界/空指针问题现由钩子自动拦截。
### 8.2 S4:用例追溯脚本
**现状**:文档声称 91 个 TC、代码有 222 个 `TEST_CHECK`(断言点数 > 用例数是正常的),
@@ -293,30 +327,37 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base
**判据**:一键列出双向差异清单;差异收敛后再考虑升为门禁。
### 8.3 待核实项:`TEST_SUITE_SELECTED` 未含 `TEST_SUITE_RTC`
**结论(✅ 已闭环,2026-10-03)**:`scripts/check_test_traceability.py` 已落地并跑通,
**默认仅告警(exit 0)、不阻断提交**。当前输出 **30 处差异**(26 处"文档有、代码无" +
4 处"代码有、文档无":`TC-GD5F-1100/1101/1102`、`TC-STO-06`)。差异清单已收敛为
可追踪项,后续按需补登记;**暂不升门禁**(与 §8.2 目标一致)。
**现状证据**(`test/board/test_config.h`):
- L45 互斥检查**已包含** `TEST_SUITE_RTC`(4 个套件互斥正确);
- **L49-51 的 `TEST_SUITE_SELECTED` 只含前三个**(CH395F / STORAGE / GD5F),**未含 RTC**。
### 8.3 待核实项:`TEST_SUITE_SELECTED` 未含 `TEST_SUITE_RTC` · ✅ 已核实(非缺陷)
**疑似影响**:`TEST_SUITE_SELECTED` 被用于 `defaultTask` 是否空闲、是否创建 `testTask` 等分支——
若 RTC 套件构建下 `TEST_SUITE_SELECTED` 未定义,**testTask 可能不被创建 → RTC 套件不执行**。
但 `run_all_suites.py --check-build` 实测 RTC 套件**能编译通过**(那只是编译,不是运行)。
**原疑点**(`test/board/test_config.h`):派生标记 `TEST_SUITE_SELECTED` 只含
CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创建 → 套件不执行。
**判据**:有实机时,选 `TEST_SUITE_RTC` 构建并烧录,观察:
- 串口是否出现 `=== RTC Tests: N/M PASSED ===` 汇总行;
- 若**无汇总行** → 证实缺陷,`TEST_SUITE_SELECTED` 需补 `|| defined(TEST_SUITE_RTC)`;
- 若**有汇总行** → 说明另有分支覆盖,属"有意为之",在 `test_config.h` 注释说明。
**2026-10-03 实机核实结论**:
- **`TEST_SUITE_SELECTED` 是死代码**:`grep -rn TEST_SUITE_SELECTED` 全仓**仅本文件有定义、
无任何使用点**。`Src/freertos.c` **无条件创建 `testTask`**;`test/board/test_config.c`
的 `start_test_task()` **直接按 `TEST_SUITE_RTC` 等宏分支**(不经 `TEST_SUITE_SELECTED`)。
→ "testTask 不被创建 → RTC 不执行"的推断**不成立**。
- **RTC 套件确实执行**:选 `TEST_SUITE_RTC` 构建烧录后,串口出现完整 RTC 输出
(`SD2506 test suite started` … `Phase 1 basic: 3/3 PASSED` … `Phase 2 API: 6/6 PASSED`)。
- **真正的缺口 = 缺套件级汇总行**:原 `sd2506_test_run()` 只打 Phase 级汇总,**没有**
`=== RTC Tests: N/M PASSED (failed=0) ===` → `run_all_suites.py --require-suite-summary "RTC Tests"`
判 **FAIL**(设备实际 12/12 全过)。属**判据缺口**,非设备缺陷。
**执行步骤**:
```bash
# 1) 改 test_config.h:取消注释 TEST_SUITE_RTC(其余保持注释)
& "MDK-ARM\build.bat"
# 2) 烧录(注意 reset.bat flash 后必须 -Rst -Run)
& "MDK-ARM\reset.bat" flash MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex
# 3) 抓串口日志,grep 汇总行
# 4) 还原 test_config.h 为产品态 + 重建产品固件(避免 .map 污染)
```
**修复(2026-10-03)**:
1. `test/board/sd2506_test_task.c`:各 Phase 进入前**自清零**,末尾输出套件级
`=== RTC Tests: N/M PASSED (failed=0) ===`(复用 `SD2506_TEST_REPORT` 宏,格式与
`test_common.h` 统一)。顺带修正 Phase 级汇总行**累计**问题——原 Phase 2 显示的是
含 Phase 1 的累计值(9/9),现各阶段独立:Phase 1 = 3/3、Phase 2 = 6/6、套件级 = 9/9。
2. `test/board/test_config.h`:`TEST_SUITE_SELECTED` **补齐 RTC** 并注释其"当前无引用",
防止将来真有分支复用时重演"漏 RTC"。
**复跑命令**:`& '.venv\Scripts\python.exe' test/run_all_suites.py --suites RTC`
**判据**:`[RTC] -> PASS`、`VERDICT: PASS`,日志含 `=== RTC Tests: 9/9 PASSED (failed=0) ===`。
---
@@ -410,6 +451,8 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base
| 日期 | 变更 |
|---|---|
| 2026-10-03 | **步骤 5/6/7 闭环**:§6 GD5F 错误通道回归 PASS(`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`;E-10/42/53/54/56/57 全部销项;顺带发现并修复编排器判据缺口——GD5F 的 TC-GD5F-704 故意触发 `[ERR]` 回落轮询,`run_all_suites.py` 改为**按套件**覆盖 `--forbidden`,与 `verify_log.py` 说明一致)→ §1/§6 改「✅ 已闭环」。§7 网络稳定性:**7.1 正常路径 ✅**(V5 120/120 FTP、零复位;负向路径转 `N-16`,台架拓扑受限);**7.2 N-2 ✅判据②/未复现判据①**(丢弃可观测性代码已有:`console_log.c` `s_con_drop`+`[ERR] CONSOLE dropped`、`sys_log_mcu.c` 队列满 `DBG_ERROR`、TX 僵尸通道自愈;V5 30 min 日志间隔分析最大静默 41s 且在开机边界、运行期 24 处 >10s 静默**全为 ~28s 且均在 `e02_probe` 窗口内**=N-17 同源)→ N-2 降级低风险观察项;**7.3 N-3 ⬜ 待 PC peer** → §1/§7 改「◐ 部分闭环」。§8 工程卫生:S5 `test-cppcheck` 钩子 PASS(仅 warning,排除模拟器桩);S4 `scripts/check_test_traceability.py` 落地并跑通(**30 处差异**,默认 warn-only);**待核实项 `TEST_SUITE_SELECTED` 实机核实 = 死代码**(全仓无使用点;`start_test_task()` 直接按宏分支,RTC 套件实际正常执行;真缺口 = 缺 `=== RTC Tests: N/M PASSED ===` 汇总行,已补 `test/board/sd2506_test_task.c` 并修正 Phase 级汇总累计问题;`test/board/test_config.h` 补齐 RTC 并注释"当前无引用")→ §1/§8 改「✅ 已闭环」 |
| 2026-10-03 | **步骤 3/4 闭环**:V2/V4/V5 台架验收通过(V2 任务饿死声明 stall 后 ≤2.5s `reset cause: IWDG`;V4 关中断死循环每 ~4.9s 稳定 IWDG 复位;V5 30 min / **120×FTP 全 PASS、零 IWDG 复位**)→ §1 总览与 §4 状态改「✅ 已闭环」,实施细节与证据移入 `子系统设计/看门狗设计说明.md` 变更记录。S11 堆告警代码落地、V5 全程未触发(`min_ever=4728 B` > 3072 B)→ §5 改「✅ 已闭环」,基线入 `内存管理/RAM使用分析与优化方案.md`。新登记 `待处理内容.md` **N-17**(V5 期间 44 次 stall 与 `e02_probe` 的 time_sync 30s 掉线+监听重建重合;隔离实验「仅 FTP 120s = 0 stall」证实探针场景专属) |
| 2026-10-03 | **重构为纯执行流程**:确立单一组织逻辑——本文 = **按顺序执行**的上板测试流程(步骤 1~7 + 状态标记),参考内容(§9 执行注意 / §10 已完成事项去向 / §11 引用闭环)后置。已完成事项(S7、A1~A4)的实施细节与真机证据**移出本文**,§10 只留「事项 → 去向」指针;§3 只留复跑命令与判据;§1「事项总览」改「执行流程总览」(步骤/状态列) |
| 2026-10-03 | **§3.2 去"待上板"框架**:A1~A4 均已落地并(A1 全项 / A2~A4 正常路径)经真机验证,删除"待上板事项(代码状态 2026-10-02 核对)"表述,改写为**客观事实记录**(列名改为「实施内容(已落地)」+「验证判据」);§1 总览 P0-B 状态由「待上板」改「✅ 已闭环」;抬头与 `docs/README.md` 索引同步为「真机验证状态」。残留事实(A2/A3/A4 故障分支 + A4 推流压测需专项窗口、N-16 拔线负向测试)仍如实保留 |
| 2026-10-02 | **更名**:`实机测试规划.md` → `实机测试实施指南.md`(与文档性质对齐——常设执行判据,非规划;引用面改造后仅剩 `docs/README.md` 索引 1 处,同步更新) |
@@ -196,3 +196,38 @@ if (ok) {
一次实验即可排除/坐实整个故障域,远胜在上层猜机制
- 探针自身也可能有 bug:错误码要记录"是哪次调用返回的 + 枚举语义"——
本例 fr=6 在日志里挂了几十个 boot 没人查过枚举表
## Trap 06 — 轮询路径"伪完成":HAL 失败被伪装成操作完成(已修复,2026-10-03)
**发现时间**:2026-10-03(错误码传播审计 E-10/E-42/E-53/E-54/E-56/E-57 复核 + 台架套件回归)
**现象/机制**:
`gd5f_wait_busy` 内 3 次 HAL 调用(`HAL_SPI_Transmit` / `HAL_SPI_Receive`)返回值**全部丢弃**,
失败时局部 `status` 保持初值 `0` → `(status & GD5F_STATUS_OIP) == 0` 立即成立 →
**"操作完成"被伪造出来**。SPI 故障下后续命令在未就绪的芯片上执行,
最终表现为"静默报成功但数据没落盘"。
**同族缺口**:
- `gd5f_bbt_scan` 丢弃 `gd5f_page_read` / `gd5f_read_from_cache` 返回值,且 `spare[8]`
跨轮不清零 → 读失败时用**陈旧内容**判坏块;整片扫描全失败仍返回 `GD5F_OK`,
`bbt_rebuild` 照常 `gd5f_bbt_save()` → 可能把**错表**持久化覆盖正确副本(数据安全优先项)。
- init 全链路(扫坏块 / 使能 ECC / 解块保护 / 定位保留池)大量忽略返回值 → 返回 `GD5F_OK`
但 ECC 未真正打开 → 存储栈在**无 ECC 保护**下运行(数据完整性底线静默降级)。
- 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响)。
**修复(2026-10-03)**:
- `gd5f_wait_busy` 检查三次 HAL 调用,失败即返 `GD5F_ERROR`(与 `GD5F_BUSY_TIMEOUT` 区分)。
- `gd5f_bbt_scan` 返回错误码、每轮 `spare` 清零、读失败中止;`bbt_rebuild` 扫描失败
**放弃持久化**(`gd5f_bbt_load()` 回滚 RAM 后直接返回,不 `bbt_save`)。
- init 全链路检查返回值;init 后**回读 B0h 校验 ECC 使能(bit4)**,未使能即报错。
- `gd5f2gq5ue_read` 的 raw 契约在头文件显式文档化("0 = SPI 传输完成,正确性由 ECC 层保证")。
- 公开 API 统一 `int32_t`;`nand_ftl.c` 2 处未检查的 `gd5f_read_from_cache` 补检查。
**验证**:台架套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`(只读模式)。
**经验教训**:
- "看起来被检查了"(调用点写了 `ret = wait_busy(...)`)**不等于**真检查——
**函数内部**不区分"超时"与"HAL 失败"时,伪完成会优先于超时返回成功;
- 坏块表这类**持久化状态**,扫描失败时必须"放弃持久化"而非"照常保存"——
保护既有好副本比刷新更重要;
- 使能类配置(ECC)必须**回读校验**,不能只信写命令的返回。
+7 -6
View File
@@ -46,6 +46,7 @@
> 它是本次审计中唯一"并发条件下会说谎"的通道,且 pending 二期不覆盖它,故独立小修先行。
>
> **2026-10-03 变更**:**E-34 / E-04 已修复并通过真机验证**(CH395F 字节命令有限超时 + `remo_send` 返回值校验;见 §5 第一档)。
> **2026-10-03 变更②**:**GD5F 错误通道统一(E-10 / E-42 / E-53 / E-54 / E-56 / E-57)已修复并通过台架套件回归**(`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`,只读模式;见 §5 第二档 #1)。
> 真机全闭环 `@test` PASS 7/7;执行判据见 `测试说明/实机测试实施指南.md` §3 / §10。
---
@@ -217,7 +218,7 @@ E-57(`int` / `int32_t` 混用)。
- **机制**:扫坏块、使能 ECC、解除块保护、定位保留池等步骤大量调用点忽略返回值。
- **后果**:init 返回 `GD5F_OK` 但实际配置未生效(例如 ECC 未真正打开),
存储栈在**无 ECC 保护**下运行 → 数据完整性底线静默降级。
- **当前状态(复核)**:**仍然存在**。`gd5f_wait_busy` 自身有 `int` 返回值
- **当前状态**:✅ **已修复(2026-10-03)**——init 全链路检查返回值(见 §5 第二档 #1)。修复前复核:**当时仍然存在**。`gd5f_wait_busy` 自身有 `int` 返回值
(`gd5f2gq5ue.c:147`,超时返 `GD5F_BUSY_TIMEOUT`),但 init 与 BBT rebuild 里
至少 5 处(`:548` / `:552` / `:643` / `:647` / `:1000`)直接丢弃返回值;
`gd5f_bbt_scan()` 内部的 `gd5f_page_read` / `gd5f_read_from_cache`(`:505-506`)同样未检查。
@@ -236,7 +237,7 @@ E-57(`int` / `int32_t` 混用)。
- **后果**:SPI 读失败时局部 8 字节 `spare[8]` 保持**上一轮的陈旧内容**,
按 `spare[0] != 0xFF` 判定 → 把好块判成坏块(保守,可接受)或反之。
更严重的是**整片扫描全失败时也返回成功**,上层以为坏块表可用。
- **当前状态(复核)**:**仍然存在**。调用点 `:548`(BBT rebuild)与 `:639`(init)
- **当前状态**:✅ **已修复(2026-10-03)**——`bbt_scan` 检查读返回值 + 每轮 `spare` 清零 + 读失败中止,`bbt_rebuild` 扫描失败**放弃持久化**(见 §5 第二档 #1)。修复前复核:**当时仍然存在**。调用点 `:548`(BBT rebuild)与 `:639`(init)
都直接忽略返回,rebuild 路径随后照常 `gd5f_bbt_save()` → 可能把**错表**持久化覆盖正确副本。
- **建议**:扫描中任一读失败即中止并返回错误;`bbt_scan` 增加 `spare[8]` 每轮清零;
`gd5f2gq5ue_bbt_rebuild` 在扫描失败时必须**放弃持久化**(保留既有好副本)。
@@ -253,7 +254,7 @@ E-57(`int` / `int32_t` 混用)。
**"操作完成"被伪造出来**。
- **后果**:SPI 故障 → 状态字节读到垃圾 → 误判操作完成 → 后续命令在未就绪的芯片上执行,
最终表现为"静默报成功但数据没落盘"。
- **当前状态(复核)**:**仍然存在**。`wait_busy` 的返回值形式上是"看起来被检查了"
- **当前状态**:✅ **已修复(2026-10-03)**——`gd5f_wait_busy` 内检查三次 HAL 调用,失败即返回 `GD5F_ERROR`(与 `GD5F_BUSY_TIMEOUT` 区分;见 §5 第二档 #1)。修复前复核:**当时仍然存在**。`wait_busy` 的返回值形式上是"看起来被检查了"
(很多调用点确实写了 `ret = gd5f_wait_busy(...)`),但**函数内部不区分"超时"与"HAL 失败"**,
且 HAL 失败造成的伪完成会**优先于**超时返回 `GD5F_OK`。
- **建议**:`gd5f_wait_busy` 内检查三次 HAL 调用,失败即返回独立错误码(`GD5F_ERROR`);
@@ -269,7 +270,7 @@ E-57(`int` / `int32_t` 混用)。
- **机制**:`gd5f_read_from_cache` 的返回值语义是"**SPI 传输完成**",不是"**数据正确**"。
ECC 不可纠正页(ECCS 非零)在 SPI 层是正常完成的,因此函数返回 `0`(成功)。
- **后果**:raw API 使用者会拿到**带有未纠正位翻转**的数据并被告知成功。
- **当前状态(复核)**:**仍然存在,缺口范围本轮已精确确认。**
- **当前状态**:✅ **已修复(2026-10-03)**——按建议 ② 在 `gd5f2gq5ue.h` 的 `read` 声明处**显式文档化 raw 契约**("0 = SPI 传输完成,正确性由 ECC 层保证";见 §5 第二档 #1)。修复前复核:**当时仍然存在,缺口范围本轮已精确确认。**
生产主路径是 **dhara glue 层**(`nand_ftl.c`),它自己有 ECC 检查保护,所以是安全的;
缺口只存在于**直接调用公开 raw API 的场景**。真正的消费者目前只有 `test/` 测试套件与诊断路径。
- **建议**:两条路选一条——① `gd5f2gq5ue_read` 增加 ECCS 回读并在不可纠正时返回错误码;
@@ -441,7 +442,7 @@ E-57(`int` / `int32_t` 混用)。
| **E-15** | `App/util/ringbuf.c` | ringbuf 实现存在但**全项目零调用者**(本轮 `grep` 确认除自身外无引用) | **仍然存在** | 修复后启用,或直接删除。**保留一个零调用者的工具模块是负债**,建议本轮就定去留 |
| **E-23** | `test/` | 测试套件缺代次检查(accept 失败分支);TC704 并发度偏高 | **仍然存在** | 加代次断言;TC704 降并发或在测试构建抑制服务任务 |
| **E-30** | `simulator/sim/net_socket_sim.c` | sim 后端 errno 语义与 MCU 不对齐(成功即清 errno,MCU 不清) | **仍然存在**(语义差异在 §6 表中仍成立) | 对齐语义;并在缺陷记录中标明"sim 复现盲区" |
| **E-57** | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h:119/140/189/222-253` | 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响) | **仍然存在** | 统一为 `int32_t`,随 GD5F 错误通道改造一同进行 |
| **E-57** | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h:119/140/189/222-253` | 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响) | ✅ **已修复(2026-10-03)**:公开 API 全部统一为 `int32_t` | 见 §5 第二档 #1 |
| **E-60** | `Drivers/BSP/SD2506/sd2506.c/h` | `SD2506_CHARGE_5K` 宏位与手册 §4.8 不符的风险 | **已订正**(2026-09-21,`41e111b`):宏位已改为 `bit1`,18H 回读校验在位 | 无需改动,仅作为"手册总表 vs 专用表不一致"的案例保留 |
| **E-61** | `Drivers/BSP/SD2506/sd2506.c:482` | `sd2506_clear_int` 注释称"ARST=1 时自动清除 INTAF",实际 ARST 在 **CTR3**(11H)而代码从未写过 CTR3 | **仍然存在(纯注释错误)** | 修注释:明确"ARST 未启用,INTAF 靠读 CTR1 清除" |
| **E-62** | `Lib/FatFs/ffconf.h` | `FF_USE_LFN=0` → 严格 8.3 短名;自愈逻辑若把 `FR_INVALID_NAME` 归入损坏触发集 → 不可逆重格式化(`.fschk` 无限重格式化事故根因) | **铁律已固化**(`AGENTS.md`);`ff.c` `create_short` 全部拒绝点已加 DEBUG 打印 | 无需改动,回归监督项(与 §3.2-E-31 后续同族) |
@@ -487,7 +488,7 @@ E-57(`int` / `int32_t` 混用)。
| # | 项 | 内容 |
|---|---|---|
| 1 | **GD5F 错误通道统一**(E-10 / E-42 / E-53 / E-54 / E-56 / E-57) | `gd5f_wait_busy` 内检查三次 HAL 调用并区分"超时"与"真故障";`bbt_scan` 检查读返回值 + **扫描失败时放弃持久化**(保护好副本);init 全链路检查返回值 + 重试 1~2 次;init 后回读 B0h 校验 ECC 使能(bit4);`gd5f2gq5ue_read` 的 raw 契约文档化或加 ECCS 检查;返回类型统一 `int32_t`。**注意 RAM 预算**:新增错误分支不得引入新的大缓冲 |
| 1 | ~~**GD5F 错误通道统一**(E-10 / E-42 / E-53 / E-54 / E-56 / E-57)~~ | **已修复并通过台架套件回归(2026-10-03)**:`gd5f_wait_busy` 内检查三次 HAL 调用并区分"超时"(`GD5F_BUSY_TIMEOUT`,可重试)与"真故障"(`GD5F_ERROR`);`gd5f_bbt_scan` 检查读返回值 + 每轮 `spare` 清零 + 读失败中止,`bbt_rebuild` 扫描失败时**放弃持久化**(保留好副本);init 全链路检查返回值;init 后**回读 B0h 校验 ECC 使能(bit4)**;`gd5f2gq5ue_read` 的 raw 契约文档化(E-56);返回类型统一 `int32_t`(E-57);`nand_ftl.c` 2 处未检查的 `gd5f_read_from_cache` 补检查。套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===` ✅ |
| 2 | **N-4 二期:ACCEPT + SEND 阻塞路径 pending 化** | 沿用一期(RECV)机制扩展;同期消化 **E-25**(`net_select` 语义)、**E-24 / E-27 / E-28**(跨上下文裸指针收口为状态查询 API)。基线重锁 + 探针族扩充 + 看门狗接 selftest 留痕 |
| 3 | **E-16** | 在 `cfg_file.h` 显式定义段名 / 键名最大长度,注册 API 拒绝超长名字(把余量变成编译期可验证);`snprintf` 返回值改检查式 |
| 4 | **E-26** | UDP 路径定案:并入队列 / 移除 / 文档化"单任务独占"。倾向 **移除**(无用户) |