Files
STM32F4-Base/docs/测试说明/开机账本单元测试说明.md
T
wandering1 83011abb16 docs(编号): 测试验证层级文档面清号 + 修上一批改名留下的双写与错族疤(27 篇)
同族第三道(文档面)。上一批只改了 10 篇测试文档,本笔清掉余下 27 篇的层级号,并修
那批机械替换留下的两类疤。

- 层级号改语义名:各单元测试说明的「**门禁层级** | L0.5 层(介于 L0 静态门禁与 L1
  板端套件之间)」→「主机侧纯逻辑单测(介于静态门禁与板端测试套件之间)」;
  「板端(L1)」「L1/L2」「L3 人工」等表头与列值同批改写;测试文档模板规范的模板
  字段同步(它决定以后每篇测试说明怎么写这一栏)。
- 「模拟器回归」旧号(原文标 L3/L2)**不映射**成新语义名,就地写「模拟器回归」:
  它与「主机侧回归」是两回事,硬映射等于造一处新的口径错。
- 修双写疤 17 处(替换串套替换串的产物):文档道改完 Lead 点名的 8 处后中途失败,
  Lead 接手余下 9 处——测试开发标准流程 7、CI README 1、门禁说明/host-unit-tests 1、
  任务规划/测试与架构专项 1。
- 修**错族替换疤**(比双写更坏:字面改对、意思改错):ADC输入层单元测试说明与
  相量层单元测试说明的「介于 L0 静态门禁与 语义取数与有效性板端套件之间」——上一批
  把测试层的 L1 换成了采集链层名;本笔随该行改写一并修正。产品需求 §7.4 同类问题
  在上一笔(代码道)修。
- 变更记录历史行按「行内换号、日期与技术事实不动」处理(与「卡」族先例一致),
  换算表与撞族警示在 scripts/archive/id_gate/编号治理历史.md §10 / §10.1。
- 刻意未动:docs/芯片手册(外部手册原文)、UI 菜单层级残留 2 处与采集链旧层名残留
  (已登记 task-12)、test/board 的链路时延段号残留(task-11)。
- 门禁:check_doc_links --all --sections-strict 通过(195 篇、章节引用 3292 处);
  id-check-new --worktree rc=0(剩 2 处 WARN 是变更记录行里的已停发测试专项号,
  不拦提交,本笔不越族去动它);112 个改动文件 U+FFFD=0、无 CR、BOM 与 HEAD 一致;
  git diff --check 干净。
2026-10-28 17:56:21 +08:00

8.9 KiB
Raw Blame History

开机账本单元测试说明(主机侧)

模块:Boot Stats(App/util/boot_stats.c + App/util/boot_stats.h) 测试类型:主机侧纯逻辑单元测试——在 PC 上离线验证"上电次数"这块 RTC 用户 SRAM 账本的四态判定、记录校验、I2C 失败分支、幂等与饱和,以及复位原因 优先级链的唯一出处。零硬件、零烧录、毫秒级。 状态:✅ 已落地(21 断言点,MSVC /W4 与 cppcheck 均 0 告警) 日期:2026-10-27(道G task-12) 关联文档: ../../AGENTS.md(RTOS 与内存铁律、[ERR] 通道口径)、 测试开发标准流程.md(测试分层与门禁总览)、 主机单测覆盖率口径.md(覆盖率总闸如何收编本套)、 ../子系统设计/系统墙钟时间维护说明.md §13.1(同一块 70 B 用户 SRAM 的占用分配表,本模块占 0..7)、 ../子系统设计/IEC104上行设计说明.md (同一张运行报告页的另一半数据源:104 观测计数)


1. 为什么要单独做这一套

被测量的用途是回答"这台装置上了多少次电"。这类账本错的时候不像错:屏幕上永远 有一个数字,格式正常、量级正常,没有任何一行日志会说它是假的。四条只能靠构造才能 复现的失效路径:

  1. I2C 读失败被当成"没有记录":一次总线抖动就把计数当 0 重来,从此从 1 往上数。 更坏的是读失败还顺手回写——那会把可能完好无损的旧记录覆盖掉,故障从"这次读不到" 升级成"永久少一笔账"。
  2. "本模块首次启用"与"RTC 电池域掉过电"被合并成一态:这两件事的文案完全不同 (前者是"账本从这一版固件开始",后者是"换过电池,历史没了")。合并就是拿前者 冒充后者。
  3. 只信 magic、不信校验和:电池欠压造成的坏区常是整片全 0 或整片全 FF,magic 那一关自然过不去;但更常见的失效是个别字节翻转——那时 magic 可能仍然是好的, 而次数会错 256 倍。这种数字最容易被人当成真的。
  4. 到顶回绕而不是饱和:回绕成个位数会被读成"刚上电几次";停在上限则一眼能看出 到头了。

真机只能覆盖"一切正常"那一条:SRAM 完好、I2C 通、计数往下加。上面四条在板端构造不 出来(要真的把 I2C 拉死、真的换电池、真的翻转一个字节),而主机侧只要给假件注入一个 返回值。这是本套存在的全部理由。


2. 用例清单(本文是这些号的定义位)

跑法见 §4。断言全部只盯结构体字段与假 SRAM 的字节,不依赖打印。

号 判据 为什么是这一条
TC-BOOTSTAT-001 SRAM 成片全 1(magic 过不去)且 RTC 未报欠压 ⇒ 判 FRESH、计数从 1 起、落盘一笔 首次启用与掉电必须分开;第一格是 1 不是 0
TC-BOOTSTAT-002 合法记录 count=7 ⇒ 判 CONTINUED、count=8,且回写后介质里也是 8 账要真的写回去,不是只写在 RAM
TC-BOOTSTAT-002b 介质不动、只重开被测模块 ⇒ 继续往上数(9) 这就是"跨掉电保持"的本体
TC-BOOTSTAT-003 记录无效且 BLF 置位 ⇒ 判 RESTARTED,绝不判 FRESH 换过电池要说换过电池
TC-BOOTSTAT-004 读失败 ⇒ 判 UNKNOWN、计数归零显示、零次回写,假 SRAM 逐字节不变 读不到不许覆盖账本
TC-BOOTSTAT-004b 同一状态里 ready=1 且 seq=UNKNOWN "不可信"必须与"零次""还没初始化"三种都能分辨
TC-BOOTSTAT-005 写失败 ⇒ 本次仍报数但 write_failed=1 这条账只会少 1,不会重复计
TC-BOOTSTAT-006 同一次开机连调两次 init ⇒ 只 +1、只写一笔 bring-up 重试不能把一次上电数成两次
TC-BOOTSTAT-007 magic 与版本都好、中段漂一位 ⇒ 被校验和挡下,报 FRESH 而不是错账 挡住"256 倍的像真数字"
TC-BOOTSTAT-008 格式版本不是当前值 ⇒ 整条作废并按当前版本重写(校验同步重算) 将来改布局时旧记录不能错位读
TC-BOOTSTAT-009 计数已达上限 ⇒ 不再自增、不再写回 饱和而不是回绕
TC-BOOTSTAT-010 BLF 读不到但记录有效 ⇒ 仍判 CONTINUED、rtc_blf=0 读不到佐证不该把好账判坏
TC-BOOTSTAT-011 SRAM 整片全 0(另一种擦除形态)⇒ FRESH 且第一格是 1 出厂态与掉电态的字节形态不同,两态都要认
TC-BOOTSTAT-020 IWDG | SFT ⇒ IWDG 崩溃钩子常以软件复位收尾,两态同置时报看门狗才有价值
TC-BOOTSTAT-020b WWDG | SFT ⇒ WWDG;单独 SFT ⇒ SFT 同上,且不能把纯软件复位误判成狗
TC-BOOTSTAT-020c POR | PIN ⇒ POR;PIN | BOR ⇒ PIN;只有 BOR ⇒ BOR;全零 ⇒ UNKNOWN 全零必须承认"不知道",不许猜一个
TC-BOOTSTAT-020d 每个编码都有可读串、全纯 ASCII、未知编码回落 unknown 无 BOM 文件里字符串必须 ASCII;开机极早期不能因编码挡启动
TC-BOOTSTAT-020e 快照暴露的就是 g_rcc_csr_latched 里那次复位原因 串口与屏幕不可能各说一套(链已收成一份真源)
TC-BOOTSTAT-030 未 init ⇒ 整表零且 ready=0 页面该显示占位,而不是"上电 0 次"
TC-BOOTSTAT-030b boot_stats_get(NULL) 无害 观测口不该因调用者手滑而炸
TC-BOOTSTAT-030c init 后连读 100 次,三类 I2C 调用计数纹丝不动 "任意上下文可读"不是口头承诺

⚠ 这些号是主机侧用例号,不是板端用例号,也不是需求号;族口径沿用 docs/编号规范.md 的 TC 族(默认 check_dangling),所以本文就是定义位: 新增/删除用例必须同步改本表,否则 id-check 校验 3 会报悬空、校验 7 会报孤儿。


3. 桩的边界

头 取真实还是桩 原因
stub/main.h 桩 只给六个 RCC_CSR_*RSTF 位掩码;位号照抄 CMSIS(BOR25 / PIN26 / POR27 / SFT28 / IWDG29 / WWDG30)。真实 main.h 会拉整棵 HAL 头树,PC 上编不动。缺宏 ⇒ 编译直接失败,不会出现"桩悄悄放过"。
sd2506.h / bsp_err.h / boot_stats.h / app_main.h 真实头 都不依赖 HAL 类型,用真头才不会出现"假件与真契约漂移"
sd2506_read_sram / sd2506_write_sram / sd2506_read_ctr1 假件(本套实现) 一片 70 B 假 SRAM + 三个可注入的错误码;地址越界按真驱动契约返 BSP_ERR_PARAM
g_rcc_csr_latched 本套给出定义 真定义在 App/app/app_main.c,本套不编译它

4. 怎么跑

& '.venv\Scripts\python.exe' test/host/boot_stats/run_unit_test.py

退出码 0=PASS / 1=测试失败 / 2=环境错误。汇总行是 s_passed/s_total——分子必须是 真过了几条;曾经写成 s_total/s_total,那种情况下失败时会打印 "21/21 PASSED (failed=1)" 这种自相矛盾的串,等于把门禁的失败显示成成功。


5. 注入反证台账(这套断言有没有牙)

逐个注入、跑套件、逐字节还原(还原后复跑全绿):

注入的退化写法 变红的号 说明
读失败改成"当成读出一片空白"继续往下 TC-BOOTSTAT-004、TC-BOOTSTAT-004b 会回写、把完好账本覆盖掉
合并 FRESH 与 RESTARTED(不看 BLF) TC-BOOTSTAT-003 拿首次上电冒充 RTC 掉电
只判 magic、丢掉校验和 TC-BOOTSTAT-007 256 倍错账照发
去掉饱和判据(改成回绕) TC-BOOTSTAT-009 到顶变小数字
BLF 读失败就早退并把记录判坏 TC-BOOTSTAT-010 判定顺序写反的代价

⚠ 第一条最初是一处假变异:注入的是未声明标识符,套件 exit 2(编译错), 看着也"红"。那不算证据——断言变红才是。上表全部用能编过、能跑起来的退化写法重做过。


6. 主机侧测不出、必须上板的部分

如实登记,别在文档里当成已验证:

  • 真掉电保持:断电 ≥5 min 再上电,boot_count 应 +1 且旧值保持(同时留证 BLF 与电池电压)——主机侧的"假 SRAM 不动"只是模拟,不能证明电池域真的保住了。
  • 换电池:应落 RESTARTED 而不是 FRESH。
  • 复位原因对照:IWDG 触发 → 快照 = IWDG;软复位 → SFT;掉电上电 → POR/BOR。 三者与串口那条 reset cause: 必须同源(同一份判定链,见 TC-BOOTSTAT-020e)。
  • 写点不落在喂狗点:boot_stats_init() 在内核启动前执行,主机侧无法验证"不占 喂狗预算",需板端结合 [STACK]/task_wd 水位看。