Files
STM32F4-Base/docs/测试说明/看门狗单元测试说明.md
T
wandering1 511fd30cc6 docs: 清理全仓变更记录流水(43 篇整节删 / 23 篇留一行 / 5 篇保留)
用户裁定:文档只写现状,历史流水一律删掉,过程查 git log。

- 43 篇「变更记录 / 修订记录」整节删除:继电保护规划 12.6 KB、产品
  需求 11.8 KB、项目部署环境 5.5 KB、BSD Socket 指南 5.3 KB、架构
  总览 5.0 KB、实机测试实施指南 3.8 KB 等
- 23 篇别处仍有指向:只留节名 + 一行「本文件不再记流水账,过程查
  git log」,节号与锚点不位移(doc-links 不因此断链)
- 5 篇整表保留(被当唯一证据 / 活台账引用):看门狗设计说明、配置
  文件设计说明、RAM预算台账、内容页架构、本地工具路径环境变量方案
- 规范同步:文档规范 §5.1 改新裁定;测试文档模板规范删示例节与
  登记句;测试开发标准流程 / UI 两篇 / 实机测试实施指南 / 四篇单测
  说明 / 并行任务工作树方案 / 编号规范 的「登记变更记录」改「写进
  正文」
- 技能同步:stale-doc-count-sweep / ui-verify / id-family 三个 SKILL
  正文不再要求追加流水行(顺带消掉 SKILL.md:121「产品需求 §12」
  失效引用)
- 修 5 处悬空指针:采样有效性校验目录锚点、BSD 指南「文末变更
  记录」、docs/README 过期描述、网络自愈架构两处指向面

72 篇文档净减 600 余行;check_doc_links --all --sections-strict 可判
定比例 61% → 78%、失效引用 0;静态门禁 passed=50 failed=0;gitlint
10 条历史提交信息 0 failed。

未提交他人在飞改动:.dsh/skills/keil-build/*(%SystemDrive% 守卫)。
2026-10-28 22:00:34 +08:00

11 KiB
Raw Blame History

看门狗单元测试说明(主机侧)

模块:Task Watchdog(IWDG + 任务心跳位图,App/util/task_watchdog.c) 测试类型:主机侧纯逻辑单元测试(见 §1.5)——在 PC 上离线验证 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。 状态:✅ 已落地(47 断言,@build 0/0,已挂 pre-commit 钩子) 日期:2026-10-01(2026-10-25 v1.5 按位分账改造;改造史与设计依据见 docs/子系统设计/看门狗设计说明.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 调用,行为逐字一致, 无额外分支、无额外符号(@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, 本模块的 run_unit_test.py 只声明 TestBuild(源文件 / 桩目录 / 汇总行标记)。 新增主机侧纯逻辑单测时直接复用 harness,见 ../../test/host/common/README.md。

1.4 运行方式

# 仓库根目录(统一用 .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) ===
与板端测试的关系 互补:本单测保证判定逻辑正确;板端 @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):

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/复位原因 寄存器的真机行为,仍需 @test 台架闭环确认。