Files
STM32F4-Base/test/host/sys_log_mcu/run_unit_test.py
T
wandering1 77779d49d6 feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机
OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批
预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block
(口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng
反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定
内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按
is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。

记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在
defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE);
事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身
(一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。
为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核,
丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。

为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费
两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过;
RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同
码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。

实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B;
RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS,
protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→
164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34,
既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、
不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页
连跑两次逐字节一致;字库 diff 空。

顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽
16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前
靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值
全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py
模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。

未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行;
LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND),
已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
2026-10-26 15:51:12 +08:00

82 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运行器(L0.5,纯标准库)。
做什么:
用本机 MSVC 编译 test/host/sys_log_mcu/sys_log_mcu_test.c(以 #include 纳入**生产源**
App/log/sys_log_mcu.c,并纳入共享假卷 test/host/common/fatfs_fake.c),在 PC 上离线
跑 64B 定长记录格式、单条/批量两条写路径、syslog_drain 的 preboot+队列排空与
"失败回投不丢条目"、日志读写放大收口的 8 条滑动读窗口 + 写者代数失效、挂载幂等与
上电/重启分类、sys_log_evidence 的 wdev.log 取证通道、「清除记录」的请求/落地分离
与四条判据(请求侧零盘触点、drain 内恰好一次 unlink、删后重建可继续写、
FR_NO_FILE 幂等返 0、卷占用/真失败返 -1 且不误删原文件)、以及七类记录(道F
task-10 新增 accid/alarm/start/oper/yk 五类)的存储面——逐 id 验"哪类落哪个
文件、各用各的容量上限、名字过严格 8.3",以及 quiet 投递通道的丢弃计数与
"投递侧零留痕",失败即非 0 退出。
工具链与判定委托 test/host/common/unit_test_harness.py。
为什么要主机侧单测(而不是只用台架):
1. **FA_OPEN_APPEND 不含 FA_WRITE**(存储架构 §10.1 第 11 条):漏写位时 f_open
会成功、文件甚至被创建出来,只有 f_write 恒返 FR_DENIED —— 真机表现是
"/log 写失败 x5/开机",2026-09-20 才定位到根因。假卷按真实 ff.c 判 fp->flag
就能把这条做成必然失败的断言。
2. **f_write 簇分配失败 = FR_OK + 短写**(同一页第 11 条后半):只判 fr != FR_OK
等于把丢记录当写成功;短写注入只有假卷给得出。
3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检
全部滞留),改成幂等 + 重试后又必须防"每 2s 刷一条同样错误" → 要注入 mkdir
失败才测得到,且必须能数出"到底留痕几次"。
4. **「清除记录」的请求 / 落地分离(2026-10-26 加)**:在 UI 按键回调里直接 f_unlink
也能删掉文件、台架当场看不出毛病,代价是把 FAT 目录项回写挂进 uiTask 的
100 ms 喂狗节拍、并与唯一写者自己的"容量满 unlink 重建"交错。这条边界只能靠
数调用钉住:请求侧零 FatFs / 零卷锁、删除只在 syslog_drain 内且恰好一次 unlink。
5. **读窗口 try 语义 / 日志读写放大收口的读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面,
只做前者会把陈旧读当正确、只做后者会退回逐条 open/seek/close 的旧实现。
桩的边界:ff.h / sys_log.h / soe_store.h / sys_clock.h / vol_lock.h 用**真实头**;
main.h 是桩,只为截断 sys_clock.h -> sd2506.h -> main.h -> stm32f4xx_hal.h;
cmsis_os.h / dbg_log.h 本目录桩。soe_store_drain() 用**计数桩**(本套只断言
"每次 drain 各调一次"这个 B9 挂接点,SOE 落盘本身归 test/host/soe_store/)。
FatFs 由 test/host/common/fatfs_fake.c 给 RAM 实现(**显式纳入,不用目录通配**)。
sys_clock_* / vol_lock_* / osMessageQueue* 强符号桩写在本测试单元里。
用法(仓库根目录):
& '.venv\\Scripts\\python.exe' test/host/sys_log_mcu/run_unit_test.py
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
设计依据:docs/子系统设计/运行与自检记录设计说明.md §4.4/§4.5/§5.3、
docs/架构设计/STM32F4-Base存储架构说明.md §10.1 第 10/11 条、
docs/测试说明/主机单测覆盖率口径.md §9 P2 第 2 项
创建日期:2026-10-24
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
# 顺序敏感:stub 必须最先(覆盖 cmsis_os.h / dbg_log.h / main.h),其余全用真实头。
# 假卷 fatfs_fake.c 由测试单元 #include 纳入(同 ch395f_fake.c 惯例,显式列文件、不通配)。
BUILD = TestBuild(
name="Sys Log Mcu Tests",
src=os.path.join(HERE, "sys_log_mcu_test.c"),
include_dirs=[
os.path.join(HERE, "stub"),
os.path.join(ROOT, "test", "host", "common"),
os.path.join(ROOT, "App", "log"),
os.path.join(ROOT, "App", "util"),
os.path.join(ROOT, "App", "fs"),
os.path.join(ROOT, "Drivers", "BSP"),
os.path.join(ROOT, "Drivers", "BSP", "SD2506"),
os.path.join(ROOT, "Lib", "FatFs"),
HERE,
],
summary_marker="=== Sys Log Mcu Tests:",
)
if __name__ == "__main__":
sys.exit(run_host_unit_test(BUILD))