Files
STM32F4-Base/test/host
wandering1 d64bbdc57f feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译)
- wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask
  唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃
  兜底——下一次真故障无波可录比丢一段更贵
- wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带
  锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度
- 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式
- 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样
  纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉
- test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条
  自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合
- 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定)

接线(任务层)
- adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图),
  段头按最低有效位精确记账,不经浮点回算
- adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实;
  台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行
- defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到
  关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms
  补一条告警并记历史峰值(喂狗余量可核)
- Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留
  超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的,
  半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进
- Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到
  上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数;
  段文件字节布局零改动,三份合成段哈希逐字节不变
- protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体
  零改动

人机(160×160 单色屏)
- 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器
  得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30
- 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与
  垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、
  单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据
- ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、
  手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口
- 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有
  60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形
- 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变
- 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏
  22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、
  仪器用已在跑的单轮工作耗时探针,不新加埋点

台账与文档
- 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936
  字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名;
  散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建
  重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置)
- 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据);
  §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径
  风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」
- 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项
- 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划
  索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动)
- 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件
  形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记
  「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场
- 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行
  227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退

为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与
体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账
同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道

编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串
生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
2026-10-29 19:23:15 +08:00
..

test/host/ — 主机端测试脚本

PC 侧的测试驱动与判读脚本(Python,跑在开发机上,经串口/网络与目标板交互)。

文件一览

流程与判读

脚本 作用
run_boot_test.py @test 一键闭环:开串口+清日志 → 烧录 → wait-idle → verify_log 校验(退出码 0=PASS)
verify_log.py 日志标记校验:Required / Forbidden 标记 → PASS / FAIL;可选 --require-suite-summary 校验套件汇总行(=== <名称>: N/M PASSED (failed=0) === 且 N>0),抓"没跑到汇总行/断言数为 0"的盲区
e02_probe.py 头阻塞专项探针

网络 / FTP

脚本 作用
ch395f_socket_test.py Socket 通信测试(回显服务器 / 文件收发)
ftp_test.py FTP 基本功能测试
ftp_up_dl_test.py FTP 上传下载测试
ftp_speed_test.py FTP 传输速度测试
ftp_capture.py FTP 流量抓取
ftp_regression.py FTP 回归套件
ftp_abort_probe.py FTP 中止场景探针(TRAP-NET-18;RST→450 / FIN→226,--check-abort;2026-10-13 自 MDK-ARM/ 迁入)
ftp_integrity_test.py FTP 上传 SHA-256 完整性多轮回归
n30_repro.py 背靠背 STOR 复现器(TRAP-NET-20 常备回归腿)
relisten_stress.py 监听快速复连压力(TRAP-NET-17 / E1:--port --n --gap-ms [--hold-ms])
e11_keepalive_probe.py keepalive 黑洞判死探针(E11)
e6_soak.py E6 24h 挂机驱动(常态 + 每小时/每 4h E1 压测注入;判据读串口 U15 probe 计数)
pasv_probe.py PASV 数据通道时序探针
ch395f_reuse_host.py Socket 复用/中止形态 PC 对端(bench 配套)
ftp_auth_bench.py FTP 鉴权/锁定台架腿
iec104_master.py IEC 60870-5-104 主站客户端(B6/B7):连设备 :2404 跑 STARTDT → 总召(actcon/四组背景扫描/actterm)→ 经 C_SE 两段写临时压低 OC1 定值确定性触发 YX16 变位(COT=3)→ 恢复 → YK11 两段式 → C_RD 读回;B7 持久化模式 --b7-pre/--b7-post/--b7-mark/--b7-check(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、定值与压板态跨复位保持(含非默认 7.5 往返);纯标准库,输出纯 ASCII 可直接重定向归档为台架证据(判据 exit 0/1/2)

时间 / 时延

脚本 作用
latency_peer.py 链路时延测试的 PC 对端(peer)
time_sync_server.py 时间同步推送服务(对时源)

主机侧单元测试(纯逻辑,无硬件)

文件 作用
common/unit_test_harness.py 通用单测 harness(主机侧纯逻辑单测):探测 MSVC → 拼 INCLUDE/LIB → 编译 → 运行 → 判定退出码。各模块运行器只做声明式配置(TestBuild),不含工具链细节
watchdog/run_unit_test.py 看门狗判定状态机单测:声明 TestBuild 后调 harness,用 MSVC 离线编译真实 App/util/task_watchdog.c(可观测桩驱动),39 用例穷举边界。零硬件、秒级(退出码 0=PASS/1=FAIL/2=环境错误)
watchdog/watchdog_test.c 测试单元本体(#include 生产源码 + 平台钩子桩)
watchdog/stub/ 桩头文件(main.h/iwdg.h/dbg_log.h),覆盖 MCU 版以在 PC 上编译
sys_clock/run_unit_test.py 系统时钟换算单测:离线编译真实 App/util/sys_clock.c,以可控时钟 + 阴影 RTC 桩驱动,100 断言点覆盖闰年/月末/世纪/2038 上限/星期/30 天惰性再基准化/RTC 故障降级
sys_clock/sys_clock_test.c 测试单元本体(#include 生产源码 + 强符号桩;不改生产代码)
sys_clock/stub/ 桩头文件(main.h/FreeRTOS.h/task.h/dbg_log.h),覆盖 MCU 版以在 PC 上编译
cfg_file/run_unit_test.py 配置引擎单测:离线编译真实 App/cfg/cfg_file.c,以内存虚拟文件系统桩驱动,149 断言点覆盖 INI 解析/四种值类型编解码/段注册校验/load-save-poll 契约/超长守卫/故障注入
cfg_file/cfg_file_test.c 测试单元本体(#include 生产源码 + cfg_store_* 内存 FS 后端桩;不改生产代码)
cfg_file/stub/ 桩头文件(dbg_log.h),覆盖 MCU 版以在 PC 上编译
phasor/run_unit_test.py 相量层(L2)单测:离线编译真实 App/algo/phasor.c + sample_ring.c(无需任何桩,纯算法),57 用例覆盖傅氏系数表 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区原子交换
phasor/phasor_test.c 测试单元本体(#include 两个生产源码 + 合成任意波形)
adc_input/run_unit_test.py ADC 输入层(L1)单测:离线编译真实 App/algo/adc_input.c,103 用例给每个 Rank 注入互异码值逐通道钉死映射,并覆盖 DMA 挂载失败 / 乒乓半区选择(撕裂帧防线) / 超拍丢帧 / 停摆 / HAL 回调句柄守卫 / 停摆拍陈旧帧 / 参数错等真机难复现分支
adc_input/adc_input_test.c 测试单元本体(#include 生产源码 + HAL / TPAFE 可注入桩)
adc_input/stub/ 桩头文件(adc.h / tpafe5160.h)
vol_lock/run_unit_test.py 卷锁单测:离线编译真实 App/fs/vol_lock.c,33 断言点覆盖创建 / 获取 / 释放契约(fail-open 不变量:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传
vol_lock/vol_lock_test.c 测试单元本体(#include 生产源码 + CMSIS 互斥桩)
vol_lock/stub/ 桩头文件(cmsis_os.h)
time_sync/run_unit_test.py 时间同步单测:离线编译真实 App/sync/time_sync.c,80 断言点覆盖分片喂狗延时 / 监听退避 / 时间包重组(半包·粘连·EOF·真错·预算耗尽·tick 回绕)/ 任务主循环(探针窗口心跳缺口的全部逻辑贡献者)
time_sync/time_sync_test.c 测试单元本体(#include 生产源码 + 网络 / 事件 / RTOS 可脚本化桩 + longjmp 跳出 for(;;) 任务循环)
time_sync/stub/ 桩头文件(FreeRTOS.h / task.h / cmsis_os2.h / net_socket.h / sys_clock.h / sys_events.h / dbg_log.h)
iec104/run_unit_test.py IEC 60870-5-104 协议栈单测(B4):离线编译真实 App/protocol/iec104/ 三模块 + 数据源(prot_set/prot_elm/phasor/sample_ring),82 用例覆盖 CP56 往返 / APCI 半帧·粘帧·坏帧 / STARTDT 握手 / 总召分片+RBT / COV 死区 / k=12 窗满 / w=8 触发 S / t1·t2·t3 / N(R) 倒退判故障 / 遥控·定值两段式与 PN / C_CS / 未知类型·地址错误应答
nand_port/run_unit_test.py dhara NAND 移植层单测:离线编译真实 Drivers/BSP/GD5F2GQ5UE/nand_ftl.c + 真实 Lib/dhara/{journal,map}.c,以 RAM 假芯片驱动,149 断言覆盖 is_free 整页扫描 / 六个 HAL 入口的越界守卫 / 故障注入 / dhara map"写入 → 重新 resume"场景
nand_port/nand_port_test.c 测试单元本体(#include 生产源码 + RAM 假芯片;内联一份"只看页首 64 B"的旧算法对照,证明断言能咬人)
nand_port/stub/ 桩头文件(gd5f2gq5ue.h 芯片几何与 SPI 原语声明 / dbg_log.h 日志宏计数)

详见 docs/测试说明/看门狗单元测试说明.md、docs/测试说明/系统时钟单元测试说明.md、 docs/测试说明/配置文件单元测试说明.md、docs/测试说明/ADC输入层单元测试说明.md、 docs/测试说明/相量层单元测试说明.md、docs/测试说明/卷锁单元测试说明.md、 docs/测试说明/时间同步单元测试说明.md; harness 用法见 common/README.md。 已挂 pre-commit 钩子(各模块同名 *-unit-test,共 22 个;逐个说明见 docs/代码规范/pre-commit使用说明.md §2 与 AGENTS.md 门禁条)。

nand_port —— dhara NAND 移植层为什么能在 PC 上测

一句话:移植层是纯逻辑 + 假芯片,能钉死真机极难复现的越界/误判路径。

nand_ftl.c 的 dhara HAL 六个入口只做地址算术与边界判定,芯片侧全部经 11 个 SPI 原语打交道——把它们换成一块 RAM 假芯片,整层即可离线跑。而它要防的两类缺陷恰恰是 板端拿不到观测面的:

  • is_free 旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判 空闲 → dhara 往已编程页再编程 = 静默数据丢失。真机要恰好撞上那种页形态才发作, 发作后只表现为"某次复位后丢数据"。假芯片把双编程做成可数事件(违规计数), 测试还内联一份旧算法做对照:同一页旧算法答"空闲"、新实现答"非空"。
  • 越界页/块号(含 DHARA_PAGE_NONE 哨兵)旧会被地址位截断成真实页 → 静默读写/搬走无辜数据。真机上没有任何路径会主动喂进越界号。假芯片记录全部 访问次数,于是"越界 → 返回 -1 + DHARA_E_CORRUPT_MAP 且零芯片访问、零标坏块" 可机械断言。

运行方式

# @test 一键闭环(推荐,需真机)
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py

# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py `
    --required "=== Storage Tests:" --forbidden "ERROR;HardFault;[ERR]" `
    --require-suite-summary "Storage Tests"

# 看门狗判定状态机单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/watchdog/run_unit_test.py

# 系统时钟换算单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/sys_clock/run_unit_test.py

# 配置引擎单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/cfg_file/run_unit_test.py

# 相量层(L2)单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/phasor/run_unit_test.py

# ADC 输入层(L1)单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/adc_input/run_unit_test.py

# 卷锁单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/vol_lock/run_unit_test.py

# 时间同步单测(纯逻辑,无需真机)
& '.venv\Scripts\python.exe' test/host/time_sync/run_unit_test.py

# dhara NAND 移植层单测(纯逻辑 + RAM 假芯片,无需真机)
& '.venv\Scripts\python.exe' test/host/nand_port/run_unit_test.py

串口端口配置在 .serialtest/config.json(单一配置源,当前 COM6/115200)。

⚠️ 注意

  • PYTHONPATH/路径敏感:脚本按仓库根相对路径读写,从仓库根执行
  • 本目录被 pre-commit 门禁的 cppcheck / 代码规范排除(test/ 豁免); 但各模块的 run_unit_test.py 自身作为独立钩子参与门禁(见上,共 22 个)
  • 台架探针族分工(2026-10-13 起):自愈/Trap 常备回归腿在本目录(relisten_stress / ftp_abort_probe / n30_repro / e02_probe / e6_soak 等);MDK-ARM/*.py 只余被文档当作 可信判据引用过的三件(ftp_freeze_probe / tcp_reconnect_probe / bench_verdict)—— 2026-10-22 清掉了一批既无人引用、又因设备侧启用鉴权而再也登录不上的历史一次性探针 (MDK-ARM/ftp_retr_abort_probe.py 与整个 MDK-ARM/bench_diag/(4 个诊断脚本)),需要时从 git 历史取回;ftp_freeze_probe.py 因被 Trap 记录当复现配方而保留,已改用设备账号登录。
  • 全部主机侧单测(16 个模块目录,含本目录新增的 nand_port/)都需要本机装有 MSVC (VS2019/2022/2026 的 C++ 工具链);无则脚本报环境错误(退出码 2)

相关文档

  • docs/测试说明/自动化测试流程.md — @test 细节与失败模式
  • docs/测试说明/测试开发标准流程.md — 测试分层与门禁
  • docs/测试说明/看门狗单元测试说明.md — 主机侧单测(用例 / 平台层 / 扩展方法)
  • docs/测试说明/系统时钟单元测试说明.md — 系统时钟换算单测(用例 / 桩 / Zeller 缺陷记录)
  • docs/测试说明/配置文件单元测试说明.md — 配置引擎单测(内存 FS 桩 / 抓出的 P0)
  • docs/测试说明/ADC输入层单元测试说明.md — ADC 输入层单测(逐 Rank 钉死映射)
  • docs/测试说明/相量层单元测试说明.md — 相量层单测(傅氏系数 / 双区交换)
  • docs/测试说明/卷锁单元测试说明.md — 卷锁单测(fail-open 不变量)
  • docs/测试说明/时间同步单元测试说明.md — 时间同步单测(探针窗口心跳缺口逻辑贡献者)
  • docs/测试说明/CH395F_Test_Guide.md、FTP_Test_Guide.md