4332142680
批 4 主体(票 task-15;规格 docs/UI设计/页面扩充详细方案.md §3.1.3 方案 A)。V0.1 只有 6 点有 硬件来源,其余 27 点在屏上就是 "--"——这是真实状态,不是页面没做完。 页面与数据 - 33 行固定名表(Flash const)+ 动态分页(8 行/页 = 5 页)。两处命名不照抄文档简写:idx0~3 屏上写「位置1~4」而非「YK1~4 位置」(遥信页出现 YK 前缀会被读成遥控对象,对应关系留在注释); idx30 写「保护闭锁」(写侧位号注释的语义即此,用编号反而丢信息)。idx4~13、idx31~32 点表未 定义功能名,如实写点号占位(YX5…、YX32),不编造功能名。 - 值映射次序:先判无效 → 再判单/双点 → 最后看位值。无效位上的位值不代表任何事实,次序颠倒就会 把垃圾位画成「复归」。 - 快照新增 yx_dp 双点位图(+8 B)。不教页面"前 4 点是双点":单双点是 104 点表事实(YX_DP_N / iec104_pts_yx_is_dp),抄进表现层就是第二真相——点表把双点段挪位或改起始下标,屏上不报错, 只会用错词形容同一个点。写侧逐点问 is_dp() 拼位图。 - UI_DATA_YX_N(33) 与 iec104_pts_yx_count() 编译期比不了(那个 #define 在协议层 .c 内),故写侧 留一条一次性 DBG_ERROR(约 90 B Flash,只在真漂移时打一次)。名表行数 = UI_DATA_YX_N 由数组 宽度法编译期守卫钉住(ARMCC C99 无 _Static_assert,本仓惯用写法)。 - 补 include 一条实测坑:DBG_ERROR 是宏,漏 include "dbg_log.h" 时 ARMCC 把它当隐式声明函数放过 编译期,错落在链接期(L6218E Undefined symbol DBG_ERROR)——报错点离根因隔了一整个链接步骤, 已在 include 处写明「DBG_TAG 必须在 include 之前」。 一个只有像素文本断言能抓到的截断 bug - 照抄批 1 的 *_PUT(dst, 源) 宏(长度取 sizeof(源)),但本表源是 const char *const 数组的 **元素(指针)**,sizeof 得 4 → 「故障总」被截成「故」,而 "YX9" 恰好 4 字节看起来完全正常。 编译器 / cppcheck / 编码钩子全不响。定位来自断言:第 2 页「过流Ⅰ段」匹配不到,同一行值列 「复归」却在原位 = 名字被截、值没错位。修法:长度取目标缓冲容量 UI_BUF_STRLEN。同名宏、不同 参数类别(数组 vs 指针),批 1 那处本身是对的,照抄才中招——教训写在宏定义上方。 模拟器与覆盖诚实性 - 固定帧发布 yx_dp = 0x0F,但刻意不把这几位置进 yx_valid:V0.1 无开入硬件,屏上就是 "--"。不用 「让它显示合位/分位」的假数据填满画面,那等于让黄金参考声称本机有开关位置遥信。 - 代价如实登记:合位/分位这条词映射当前**没有任何自动化证据**(像素恒 IV;页面依赖 LVGL 进不了 主机单测)。已写进方案 §3.1.3 落地注与契约落地进度·四,作为开入落地后的台架判据,不假装测过。 测试 - 主机单测 +2 条(TC-UIDATA-009c 双点位图原样透传、不被 valid 掩;009d 点数 33 的独立见证,注明 绊红时要同步改哪四处)→ 85/85。 - 文本断言 +33 条(yx / yxp2 两态,含页码钉住 33/8 = 5 页的分页数学);两页像素基线单独一笔。 - font-charset 本批零新增字:名表用字全部命中既有 183 字子集——「置/故/障/总/外/部」正属批 1 成组并入 KEEP_EXTRA 的那批字,第二次兑现(当时若按 extract 输出重写字符集,这些字早被洗掉, 上屏是空白,而门禁只会说「字不在清单里」)。 体积(同一棵树、构建前后的实测增量) - Code 224272→224688(+416);RO 24040→24640(+600);RW 1324→1328(+4); ZI 134844→135088(+244)→ Flash 约 +1020 B、RAM +248 B。 - RO 增量主要是 33 行名表与名称串,是"把 33 个真点名上屏"的代价,不是可省开销。 跳过的门禁(三条同族,同一根因):mem-numbers / mem-budget / size-gate(L0c)——台账与体积凭证 回填是 Lead 单写者,本分支不做静默处理,数值见上。
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"的盲区(T-2) |
e02_probe.py |
E-02 专项探针(历史审计编号) |
网络 / 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 鉴权/锁定台架腿(N-19) |
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(L0.5):探测 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,101 用例给每个 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 回绕)/ 任务主循环(N-17 的全部逻辑贡献者) |
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 移植层单测(N-21):离线编译真实 Drivers/BSP/GD5F2GQ5UE/nand_ftl.c + 真实 Lib/dhara/{journal,map}.c,以 RAM 假芯片驱动,149 断言覆盖 is_free 整页扫描(N-12)/ 六个 HAL 入口的越界守卫(N-13)/ 故障注入 / 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,共 16 个;逐个说明见docs/代码规范/pre-commit使用说明.md§2 与 AGENTS.md 门禁条)。
nand_port —— dhara NAND 移植层为什么能在 PC 上测
一句话:移植层是纯逻辑 + 假芯片,能钉死真机极难复现的越界/误判路径。
nand_ftl.c 的 dhara HAL 六个入口只做地址算术与边界判定,芯片侧全部经 11 个 SPI
原语打交道——把它们换成一块 RAM 假芯片,整层即可离线跑。而它要防的两类缺陷恰恰是
板端拿不到观测面的:
- N-12:
is_free旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判 空闲 → dhara 往已编程页再编程 = 静默数据丢失。真机要恰好撞上那种页形态才发作, 发作后只表现为"某次复位后丢数据"。假芯片把双编程做成可数事件(违规计数), 测试还内联一份旧算法做对照:同一页旧算法答"空闲"、新实现答"非空"。 - N-13:越界页/块号(含
DHARA_PAGE_NONE哨兵)旧会被地址位截断成真实页 → 静默读写/搬走无辜数据。真机上没有任何路径会主动喂进越界号。假芯片记录全部 访问次数,于是"越界 → 返回 -1 +DHARA_E_CORRUPT_MAP且零芯片访问、零标坏块" 可机械断言。
运行方式
# @test 一键闭环(推荐,需真机)
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py
# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0(T-2)
& '.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自身作为独立钩子参与门禁(见上,共 16 个) - 台架探针族分工(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— 时间同步单测(N-17 逻辑贡献者)docs/测试说明/CH395F_Test_Guide.md、FTP_Test_Guide.md