Files
STM32F4-Base/test/host/nand_port/run_unit_test.py
T
wandering1 720f841494 fix(storage): dhara 移植层 is_free 整页精度 + HAL 越界守卫;日志读写批量化
登记簿 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 全库同步。
2026-10-15 12:34:39 +08:00

63 lines
3.2 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 -*-
"""dhara NAND 移植层(nand_ftl)的主机侧单元测试运行器(L0.5,纯标准库)。
做什么:
用本机 MSVC 编译 test/host/nand_port/nand_port_test.c(它以 #include 纳入
**生产源文件** Drivers/BSP/GD5F2GQ5UE/nand_ftl.c),在 PC 上离线跑 dhara NAND
HAL 六个入口(is_bad / mark_bad / erase / prog / is_free / read / copy)的
几何守卫与整页判空,失败即非 0 退出。零硬件、零烧录、毫秒级。
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
`test/host/common/unit_test_harness.py`(本文件只做声明)。
为什么要主机侧单测(而不是只用板端套件):
移植层是**纯地址算术 + 边界判定**,芯片侧只经 11 个 SPI 原语打交道——把它换成
一块 RAM 假芯片,整层就完全可在 PC 上跑。而它要防的两类缺陷恰恰是真机极难复现的:
- N-12:is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 →
dhara 往已编程页再编程 = 静默数据丢失(真机要恰好撞上那种页形态才发作);
- N-13:越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 →
静默读写/搬走无辜数据(真机上没有任何路径会主动喂进越界号)。
假芯片把"双编程"做成可数事件、并记录全部访问次数,于是"越界不产生任何芯片
访问""越界标坏块次数为 0"这类判据可机械断言——这是板端永远拿不到的观测面。
用法(仓库根目录):
python test/host/nand_port/run_unit_test.py
或统一用 .venv: & '.venv\\Scripts\\python.exe' test/host/nand_port/run_unit_test.py
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
设计依据:docs/问题记录/项目审查与优化建议报告.md N-12/N-13(报告 R-3 建议的
`test/host/dhara/` 移植层单测,落位为 test/host/nand_port/);
创建日期:2026-10-15
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
# harness 位于 test/host/common/
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
# 顺序敏感:stub 目录必须**先于**生产目录,才能覆盖 gd5f2gq5ue.h(真实头会拖入
# main.h + 整套 ST HAL/CMSIS,PC 上不可编译)与 dbg_log.h(真实头依赖 sys_clock)。
# nand.h / journal.h / map.h / error.h(Lib/dhara)与 ff.h / diskio.h / ffconf.h
# (Lib/FatFs)用**真实头**——接口契约原汁,桩与真实 API 一旦漂移编译期即报错。
BUILD = TestBuild(
name="NAND Port Tests",
src=os.path.join(HERE, "nand_port_test.c"),
include_dirs=[
os.path.join(HERE, "stub"),
os.path.join(ROOT, "Drivers", "BSP", "GD5F2GQ5UE"),
os.path.join(ROOT, "Lib", "dhara"),
os.path.join(ROOT, "Lib", "FatFs"),
HERE,
],
summary_marker="=== NAND Port Tests:",
)
if __name__ == "__main__":
sys.exit(run_host_unit_test(BUILD))