4a4ad6bc5f
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
178 lines
11 KiB
Markdown
178 lines
11 KiB
Markdown
# 看门狗单元测试说明(主机侧)
|
||
|
||
> **模块**:Task Watchdog(IWDG + 任务心跳位图,`App/util/task_watchdog.c`)
|
||
> **测试类型**:**主机侧纯逻辑单元测试**(见 §1.5)——在 PC 上离线验证
|
||
> 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
||
> **状态**:✅ 已落地(47 断言,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||
> **日期**:2026-10-01(2026-10-25 v1.5 按位分账改造;改造史与设计依据见 `docs/子系统设计/看门狗设计说明.md` 变更记录——本文档不再设变更记录节)
|
||
> **关联文档**:
|
||
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、
|
||
> [`测试开发标准流程.md`](../流程与规范/测试开发标准流程.md)(测试分层与门禁总览)、
|
||
> [`测试文档模板规范.md`](../流程与规范/测试文档模板规范.md)(本文命名规范)
|
||
|
||
---
|
||
|
||
## 1. 概述
|
||
|
||
### 1.1 为什么单独做主机侧单测
|
||
|
||
`task_watchdog.c` 分两半:
|
||
|
||
| 半边 | 内容 | 可测性 |
|
||
|---|---|---|
|
||
| **判定状态机**(被测) | 位图取走清零、必需掩码比较、`s_miss_bits[]` 按位分账(容忍 = 同一位 8 连击)、100 ms 抽样节流、tick 回绕安全 | 纯逻辑,与硬件无关 → **可在 PC 上位穷举** |
|
||
| **硬件副作用** | `IWDG->KR` 写(喂狗 / 启动)、`HAL_GetTick()` | 需真机或桩 |
|
||
|
||
板端套件(`test/board/`)只能"在真机上完整跑一遍",**无法穷举边界**——例如:
|
||
|
||
- 节流窗口边界 99 ms / 100 ms / 199 ms / 200 ms 的分野;
|
||
- 连续缺席第 7 次 / 第 8 次 / 第 9 次的行为差异(停喂触发点);
|
||
- `HAL_GetTick()` 跨越 2^32 回绕后是否仍正确节流。
|
||
|
||
这些用主机侧单测一次性钉死,且**不占 MCU 的 RAM**(本项目 RAM 已 95%,见
|
||
`docs/内存管理/`)。设计上参照项目既有的"共享核心 + 平台后端"做法
|
||
(`App/cfg/cfg_file_store.h`:MCU 端 `cfg_file_store_mcu.c` / 模拟器端
|
||
`cfg_file_store_sim.c`)。
|
||
|
||
### 1.2 被测对象与桩
|
||
|
||
被测源文件以 **`#include` 原样纳入**测试单元,保证测的是**生产代码**而非副本:
|
||
|
||
```
|
||
test/host/watchdog/watchdog_test.c
|
||
├── #include "main.h" → stub/main.h (HAL_GetTick 声明)
|
||
├── #include "iwdg.h" → stub/iwdg.h (IWDG_TypeDef + IWDG 外部声明)
|
||
├── #include "dbg_log.h" → stub/dbg_log.h (DBG_* 映射 + 错误计数)
|
||
└── #include "task_watchdog.c" ← 生产源文件(App/util/),原样纳入
|
||
```
|
||
|
||
三个**平台钩子**(声明在 `App/util/task_watchdog.h` 末段"平台后端(内部)")在主机侧被替换为可观测桩:
|
||
|
||
| 钩子 | MCU 默认实现(`__weak`) | 主机侧桩 |
|
||
|---|---|---|
|
||
| `wd_platform_feed()` | `IWDG->KR = 0x0000AAAAu` | 计数 `s_feed_count++` + 写影子寄存器 |
|
||
| `wd_platform_start()` | `IWDG->KR = 0x0000CCCCu` | 计数 `s_start_count++` + 写影子寄存器 |
|
||
| `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` |
|
||
|
||
> **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致,
|
||
> 无额外分支、无额外符号(`/keil-build` 0/0 已验)。平台默认实现由
|
||
> `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()`
|
||
> 仅在主机侧编译(MCU 构建零代码、零符号)。
|
||
|
||
### 1.3 测试环境
|
||
|
||
| 依赖 | 版本 / 说明 |
|
||
|---|---|
|
||
| 编译器 | 本机 MSVC(VS2019 BuildTools 14.29 / VS2022 任一),由 `run_unit_test.py` 自动探测 |
|
||
| Python | 仓库 `.venv` 或任意 python3(**纯标准库**) |
|
||
| 硬件 | 无(不需要开发板 / 串口 / ST-LINK) |
|
||
|
||
> **为何不调 `vcvars64.bat`**:它内部调用 `reg.exe` 探测环境,本机安全策略禁止
|
||
> `reg.exe`。运行器因此**直接拼 `INCLUDE`/`LIB` 环境变量**并用绝对
|
||
> 路径调 `cl.exe`(只需 MSVC 版本目录 + Windows SDK 的 include/lib),无副作用、可重复。
|
||
>
|
||
> 该工具链逻辑**不在本模块目录内**(2026-10-01 抽取):已沉淀为通用 harness
|
||
> [`../../../test/host/common/unit_test_harness.py`](../../../test/host/common/unit_test_harness.py),
|
||
> 本模块的 `run_unit_test.py` 只**声明** `TestBuild`(源文件 / 桩目录 / 汇总行标记)。
|
||
> 新增主机侧纯逻辑单测时直接复用 harness,见 [`../../../test/host/common/README.md`](../../../test/host/common/README.md)。
|
||
|
||
### 1.4 运行方式
|
||
|
||
```powershell
|
||
# 仓库根目录(统一用 .venv)
|
||
& '.venv\Scripts\python.exe' test/host/watchdog/run_unit_test.py
|
||
```
|
||
|
||
退出码:`0`=PASS / `1`=测试失败 / `2`=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||
|
||
### 1.5 回归接入
|
||
|
||
| 项 | 说明 |
|
||
|---|---|
|
||
| **触发范围** | `App/util/task_watchdog.c` / `task_watchdog.h`,或 `test/host/watchdog/**`、`test/host/common/unit_test_harness.py` 变动 |
|
||
| **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
|
||
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
|
||
| **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` |
|
||
| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `/serial-boot-test` 故障注入(V2/V4,见 `docs/子系统设计/看门狗设计说明.md` §7 验证记录)保证**真机复位链路**正确 |
|
||
|
||
---
|
||
|
||
## 2. 测试用例
|
||
|
||
> 命名 `TC-WD-NNN`(WD = Watchdog)。全部在 `watchdog_test.c` 内,主机侧运行。
|
||
|
||
| 用例 | 断言 | 验证的设计点 |
|
||
|---|---|---|
|
||
| **TC-WATCHDOG-001** | 未启动时 `poll` 不喂狗、`is_armed()==0` | 启动前不判定(`s_started` 门卫) |
|
||
| **TC-WATCHDOG-016** | 未启动时即使全员置位也不喂、不更新 `last_mask` | 同上(位图无关) |
|
||
| **TC-WATCHDOG-014** | 重复 `task_wd_start()` 只启动 IWDG 一次 | 启动幂等(IWDG 不可逆,重复启动无意义) |
|
||
| **TC-WATCHDOG-015** | `start` 内部先喂一次(`feed_count==1`) | 首个抽样窗口从启动时刻起算,避免启动即超时 |
|
||
| **TC-WATCHDOG-002** | 50 ms 内 `poll` 不喂;累计 100 ms 喂 1 次 | 100 ms 抽样节流 |
|
||
| **TC-WATCHDOG-003** | 全员置位 → 喂狗 1 次;`last_mask==0x1F7` | 全员到位判定 + 掩码取值(掩码史:0x37→0x77→0x1F7,见设计说明变更记录 2026-10-10/12) |
|
||
| **TC-WATCHDOG-004** | 不重新置位:第二窗(空位图,各位 streak=1)v1.5 **照常喂**;`last_mask==0` 证明位已取走 | **取走清零**语义(旧位不残留);停喂只看按位连击 |
|
||
| **TC-WATCHDOG-005** | 缺 UI(bit4)单窗(streak 1 < 容忍)→ **照常喂**;`last_mask` 精确记录缺失位 | 按位分账(v1.5)+ 缺失定位 |
|
||
| **TC-WATCHDOG-006** | 同一批缺席 1..7 窗不报且**持续喂**(feed_count==8);**第 8 窗**打 `[WD] task stall` 起停喂 | `WD_MISS_TOLERANCE=8`(同一任务 800 ms)停喂触发点 |
|
||
| **TC-WATCHDOG-007** | 停喂后**每 100 ms 复报**一条 stall(现状行为) | 见 §4 已知行为("最后遗言"持续到复位) |
|
||
| **TC-WATCHDOG-008** | 恢复全员到位 → 立即喂狗 + 全部按位账本清零(v1.5:episode 内 1..7 窗已持续喂,恢复窗 feed_count==8) | 停顿可恢复(非锁死状态机) |
|
||
| **TC-WATCHDOG-009** | 99 ms 不判 / 100 ms 判 / 再 99 ms 不判 / 累计 100 ms 判 | 节流窗口**边界**(易错点) |
|
||
| **TC-WATCHDOG-010** | `tick` 跨 2^32 回绕后 100 ms 仍正确判 | 无符号差比较(回绕安全) |
|
||
| **TC-WATCHDOG-011** | `is_armed`/`last_mask`/`miss_count` 语义 | 观测接口正确性 |
|
||
| **TC-WATCHDOG-013** | 非法任务 ID(`>= TASK_WD_ID_MAX`)被忽略,不污染位图;空位图单窗(streak 1)v1.5 照常喂 | 防位图越界(`task_wd_beat` 的守卫) |
|
||
| **TC-WATCHDOG-017** | UI/NET **交替缺席** 20 窗 → 零 stall、20 窗全部持续喂(v1.5 回归钉:旧复合判据会在第 8 窗误申报) | 复合串窗根治(2026-10-25 大窗 16:24/16:28 型误升级) |
|
||
| **TC-WATCHDOG-018** | 同一位(IEC104)缺席 7 窗:不报持续喂;8/9/10 窗连报 stall 且停喂;恢复窗喂并清零账本;第二段 7 窗亚容忍串再从零计、不误报 | 真单任务停摆保留杀伤力 + 账本重启(16:39 型真链仍可抓) |
|
||
|
||
**当前结果**:47/47 PASSED(`failed=0`)。
|
||
|
||
---
|
||
|
||
## 3. 如何扩展用例
|
||
|
||
1. 在 `watchdog_test.c` 加一个 `static void tc_0NN_xxx(void)`;
|
||
2. 用 `fresh()` 开头(复位模块静态态 + 清观测计数 + 时钟归零);
|
||
3. 用 `start_and_clear()` 启动(并清掉 `start` 自身那一次喂狗);
|
||
4. `beat_all()` / `task_wd_beat(...)` 置位,`poll_advance(ms)` 推进时间并抽样;
|
||
5. `CHECK(cond, "描述")` 断言;
|
||
6. 在 `main()` 里 `fresh(); tc_0NN_xxx();` 注册。
|
||
|
||
> ⚠ **别忘 `fresh()`**:`task_wd_poll` 的节流基准 `s_last_tick` 与位图 / 计数都是模块
|
||
> 静态态,跨用例不隔离会导致假失败(本项目开发时即踩到,见 §4)。
|
||
|
||
---
|
||
|
||
## 4. 已知行为与可优化点
|
||
|
||
### 4.1 stall 日志在停喂后复报(TC-WATCHDOG-007,现状)
|
||
|
||
实现(`task_watchdog.c`):
|
||
|
||
```c
|
||
if (s_miss_streak < WD_MISS_TOLERANCE) { s_miss_streak++; }
|
||
if (s_miss_streak >= WD_MISS_TOLERANCE) { DBG_ERROR("[WD] task stall: ..."); }
|
||
```
|
||
|
||
`streak` 封顶 8 后,判定条件 `streak >= 8` 在**后续每轮**仍成立 → 每 100 ms 复报一条,
|
||
直到 IWDG 约 900 ms 后复位(约 9 条/停顿周期)。
|
||
|
||
**评估**:这是**刻意的"最后遗言"**——复位前日志窗口内信息不丢。代价是日志冗余。
|
||
若改为"每停顿周期仅报一次",需加一个 `s_stall_reported` 标志(在 `streak` 归零时清除)。
|
||
**当前保留现状**(单测按现状断言,行为变更须同步改 TC-WATCHDOG-007 与本文)。
|
||
|
||
### 4.2 测试隔离依赖 `task_wd_test_reset()`
|
||
|
||
`task_wd_poll` 的节流基准 `s_last_tick` 曾是**函数内 `static`**,`task_wd_test_reset()`
|
||
够不到它 → 跨用例基线残留 → 假失败。已改为**文件静态**并纳入复位。
|
||
(这同时是更好的生产设计:模块状态应归于模块。)
|
||
|
||
---
|
||
|
||
## 5. 与板端验证的分工
|
||
|
||
| 层 | 覆盖 | 状态 |
|
||
|---|---|---|
|
||
| **本单测(主机侧纯逻辑单测)** | 判定状态机全部逻辑分支与边界 | ✅ 已落地 |
|
||
| 板端故障注入(板端测试套件 / 主机侧回归) | 真机:单任务卡死 → 停喂 → IWDG 真复位 → `RCC_CSR` 记 `IWDGRSTF` | ✅ 已台架验收(V2/V4,2026-10-03) |
|
||
| 长跑(主机侧回归) | ≥30 min 无假复位(`miss_count` 稳定) | ✅ 已台架验收(V5,30 min / 120×FTP 零复位,2026-10-03) |
|
||
|
||
> 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因
|
||
> 寄存器的真机行为,仍需 `/serial-boot-test` 台架闭环确认。
|