720f841494
登记簿 N-21(报告 N-12/N-13)与 N-24(报告 N-16)两项未闭环问题修复并销项。 N-12:dhara_nand_is_free() 旧实现只读页首 64 B 判全 0xFF,会把"页首 0xFF、后段有数据"的已编程页误判空闲,dhara 遂往已编程页再编程 = 位碰撞、 journal/map 指向垃圾且不报任何错。改整页扫描(栈上 64 B 分块、页首命中即 早退,故"已编程且页首有数据"的绝大多数页成本与旧实现同价)。 N-13:报告原建议经核对不成立——本仓裁剪版 dhara 既无 DHARA_NO_PAGE(哨兵 实为 DHARA_PAGE_NONE,journal.h:46)也无 DHARA_E_UNSUPPORTED(error.h 枚举 里没有,照抄直接编译失败),且 map.c 三处 dhara_journal_copy 调用点各自已 挡掉哨兵。真风险是移植层零上界校验:越界页号被芯片地址位截断成某个真实页 → 静默读写错页。改为六个 HAL 入口统一几何上界守卫 + DHARA_E_CORRUPT_MAP。 错误码选择是刻意的:BAD_BLOCK 会让 journal.c:904 标坏无辜好块,RECOVER 会 让 map.c:275 启动回收;无错误通道的 is_free/is_bad/mark_bad 一律取保守值。 N-16 写侧:一次排空合并成"每文件一次 open/write/close"(≤8 条 = 512 B 静态缓冲)。掉电语义不变——批次仍在同一次 drain 调用内 close,而 close 回写目录项这个不同 LPN 会把 FTL 单页写回缓存逼出到 NAND;失败尾部回投 队列,条目不丢(旧实现 -2 卷忙会直接丢掉那一条)。容量轮转仍只在按条慢 路径做,批量路径发现装不下一批就返回 -3 退回。 N-16 读侧:s_read_raw() 加 8 条滑动读窗口 + 写者代数失效,API 与 UI 零改动。 验证:@build 0 错 0 警(Code 213,540 / ER_IROM1 232,280 B = 22.2%, RW_IRAM1 75,232 B = 57.4%,ZI +1,640 B 全为静态缓冲、不吃任务栈); @test PASS,且台架 journal 恢复点与修复前同轨——连续四次启动 sectors used 恒 14,094、Map head 每轮 +128 单调推进、epoch 稳定 3 → 整页 is_free 未 改变恢复语义、无双编程、无回退;stack_report 9 个任务全 PASS(defaultTask 静态最坏链 3,024 B / 余 40%,实测峰值 2,164 B / 余 57%)。 新增主机侧移植层单测 test/host/nand_port/(第 16 个 -unit-test 钩子): RAM 假芯片 + 真实 dhara map/journal 源,149 断言,含"旧算法只看页首 64 B" 对照用例(将来有人把 is_free 改回去会立刻红)与 dhara 级 resume 双编程计数 为 0 的场景。L0.5 由 15 runner / 955 断言 → 16 runner / 1,104 断言。 文档:存储架构说明 §5.3 加移植层两条硬约束;运行与自检记录设计说明 §4.4/§4.5 写批量化与读窗口;GD5F Trap Records 新增 Trap 07(含三条遗留 代价与一条待办编译期断言);登记簿 N-21/N-24 删行销项并同步 README §1/§2.0/§2.1、排期详述 C3、报告 §0.1/§0.2/§0.4/§4.x/§7 与去处表; 钩子计数 33→34、主机侧单测 15→16 全库同步。
160 lines
12 KiB
Markdown
160 lines
12 KiB
Markdown
# 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`](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` 且零芯片访问、零标坏块"
|
||
可机械断言。
|
||
|
||
## 运行方式
|
||
|
||
```bash
|
||
# @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` / `ftp_retr_abort_probe` /
|
||
`bench_verdict`)
|
||
- 全部主机侧单测(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`
|