feat(存储): 写回缓存空闲 flush 兜底——卷空闲周期 CTRL_SYNC(登记簿 N-22 收口)
道F 批 2 / task-16。FTL 单页写回缓存(nand_ftl.c s_cache_dirty)与 dhara journal 只在换页/整页写/f_close 时刷出,漏网脏页的掉电驻留窗无上限。本 提交以最小面收口:不碰 GD5F/dhara 驱动,全部走既有 FatFs CTRL_SYNC 路径。 实现 App/fs/vol_idle_sync.[ch](新增,仅 MCU 端,uvprojx App 组注册): - 四级门:STORAGE_READY 就绪门 → 5 s 节流(无符号差回绕安全判据)→ vol_lock_wait(0) try(锁被持有=文件序列在做,跳过,不改 STOR 持有期 语义)→ disk_ioctl(0, CTRL_SYNC); - clean 态脏标志+journal 双短路、零 NAND 触点;脏态最坏 = 一次 f_close 的同步段同型成本,不引入新磨损来源; - 失败按边沿 latch 一条 DBG_ERROR、成功重新武装;失败路径也成对放锁; - TEST_SUITE_STORAGE 构建整体 no-op(卷由 testTask 独占)。 接线:defaultTask 主循环 syslog_drain 之后(default_task.c v1.3)。 文档:docs/架构设计/STM32F4-Base存储架构说明.md §5.5 补空闲兜底与边界; 本板无掉电检测硬件、不做掉电前强制 flush、勿当掉电保护——口径同步落 注释与文档(sys_log_mcu.c 两处旧「暂不做」注释随实现改口)。 测试:新主机单测 test/host/vol_idle_sync/(TC-VOLSYNC-001..012,19 断言 全过);scripts/id_registry.json 建 TC-HOST-VOLSYNC 族(仿 ADCCAL 先例); .pre-commit-config.yaml 加 vol-idle-sync-unit-test hook(只加自己条目)。 越界申报(①②③ 均 lead 批准,④ 为门禁连带必要): ① App/task/default_task.c 单点接线(include + 1 调用 + 头修改记录); ② docs/架构设计/STM32F4-Base存储架构说明.md §5.5; ③ .pre-commit-config.yaml 单 hook + scripts/id_registry.json 单族; ④ scripts/check_registration.py SIM_EXEMPT 加一行(vol_idle_sync.c 模拟器 侧豁免,registration-check GAP 所需;与批准③同类别但未列名,特此申报)。 体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行): Flash(Code+RO) 237140→237328(+188 B)、RW 1224→1232(+8 B)、ZI 0; 增量 = 3 个文件级 static + 一处 DBG_ERROR 调用点;栈/堆无变化。 gate: only mem-budget/mem-numbers pending lead ledger backfill 全量 pre-commit --all-files:40 项 Passed,仅 mem-numbers(D-7 体积档)与 mem-budget 两条红 = 台账数字待回填(增量如上,属 lead 单写者禁区); 其余含 doc-links/id-check/registration/test-coverage/全部主机单测皆绿。 @build 0 错误 0 警告(Code=215932 RO=21396 RW=1232 ZI=131984)。 台架:空闲 sync 实机节奏(clean 零触点、脏态时长、无心跳扰动)属实际 测量采样类——本批跳过,留「待台架实测」。
This commit is contained in:
@@ -312,6 +312,21 @@ repos:
|
||||
files: ^(App/fs/vol_lock\.(c|h)|test/host/vol_lock/.*|test/host/common/unit_test_harness\.py)$
|
||||
pass_filenames: false
|
||||
|
||||
# 7f2) 卷空闲同步(写回缓存空闲 flush)主机侧单元测试(2026-10-25 加,道F 批 2)
|
||||
# 背景:该模块是写回缓存掉电窗口的兜底网——掉电与"脏页驻留"本身台架
|
||||
# 不可按需构造;卷忙跳过 / ioctl 失败边沿 latch / 32 位 tick 回绕 /
|
||||
# 失败路径成对放锁(不得空放)全是纯状态逻辑。本钩子用 MSVC 离线编译
|
||||
# 真实源文件(#include 纳入;桩:可控时钟 + 事件位图 + 卷忙注入 +
|
||||
# 可注入 disk_ioctl),把四级门与 latch 分支一次钉死。
|
||||
# files 含被测源与其头、就绪位属主 sys_events 的消费面 default_task.c、
|
||||
# 以及 harness 本身。
|
||||
- id: vol-idle-sync-unit-test
|
||||
name: 卷空闲同步单测(主机侧)
|
||||
entry: python test/host/vol_idle_sync/run_unit_test.py
|
||||
language: system
|
||||
files: ^(App/fs/vol_idle_sync\.(c|h)|App/task/default_task\.c|test/host/vol_idle_sync/.*|test/host/common/unit_test_harness\.py)$
|
||||
pass_filenames: false
|
||||
|
||||
# 7g) 时间同步(time_sync)主机侧单元测试(2026-10-10 加)
|
||||
# 背景:time_sync.c 是 TCP 时间同步协议状态机,也是 N-17 号看门狗风险的**全部
|
||||
# 逻辑贡献者**——"listen 重建 -> 800ms~1s 心跳间隔"由本模块的分片延时/退避/
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 模块名称:Volume Idle Sync(写回缓存空闲 flush)— MCU 实现
|
||||
* 模块功能:就绪门控 + 周期节流 + 卷锁 try + disk_ioctl(CTRL_SYNC) 的兜底
|
||||
* 状态机(设计/使用约定/边界见 vol_idle_sync.h)
|
||||
* 适用平台:STM32F407ZGTx + FreeRTOS
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 DTU 项目组 创建(道F 批 2:写回缓存空闲 flush 收口)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "vol_idle_sync.h"
|
||||
|
||||
#include "test_config.h"
|
||||
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
|
||||
#include "cmsis_os.h"
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "ff.h" /* FatFs 基础类型(diskio.h 依赖 BYTE/LBA_t 等,须先纳) */
|
||||
#include "diskio.h" /* disk_ioctl / CTRL_SYNC / DRESULT */
|
||||
#include "vol_lock.h"
|
||||
#include "sys_events.h"
|
||||
/* 本模块日志仅一条失败留痕(≤60 字符),默认行缓冲即可,不调 DBG_LINE_BUF_MAX */
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
/* 空闲同步检查周期(= 漏网脏页的最大掉电驻留窗,另加一个主循环拍 ≈50ms)。
|
||||
* 取值依据:clean 态为"脏标志 + journal"双短路(零 NAND 触点),高频不伤介质
|
||||
* 只是白跑;脏态最多一次页编程 + journal 收账,与 f_close 同型,但落在卷
|
||||
* 已空闲的窗口、远离活跃写会话。5 s 与主循环其余慢观测(栈/堆水位 2 s)同
|
||||
* 量级而更稀疏——本模块是兜底网,不是持久性主路径(主路径 = close 内同步)。
|
||||
* 锁忙(卷不空闲)同样按本周期重试:不做欠账补偿,节奏恒定好推理。 */
|
||||
#define VOL_IDLE_SYNC_PERIOD_MS 5000u
|
||||
|
||||
/* 静态变量定义区 */
|
||||
/* 状态量放文件级 static(非函数内):主机侧白盒单测(test/host/vol_idle_sync/)
|
||||
* 以 #include 纳入本文件后需要复位它们;命名带 vol 前缀避免与测试桩撞名 */
|
||||
static uint32_t s_vol_sync_last_ms; /* 上次尝试时刻(tick ms,uint32 回绕) */
|
||||
static uint8_t s_vol_sync_armed; /* 0 = 从未尝试(首拍直接放行) */
|
||||
static uint8_t s_vol_sync_fail_latch; /* 失败留痕边沿 latch:一个 episode 一条 */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:存储空闲周期同步轮询(就绪门控 → 节流 → try 卷锁 → CTRL_SYNC)
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅 defaultTask 主循环调用;内部绝不阻塞(try 锁 + 常数级耗时,
|
||||
* 最坏成本 = 一次 f_close 的同步段)
|
||||
* 函数说明:四级门,逐级变贵:
|
||||
* ① 就绪门:sys_evtHandle 未建或 STORAGE_READY 未置 → 静默
|
||||
* (挂载前 disk_ioctl 只会 RES_NOTRDY,且 bring-up 失败重试期
|
||||
* 卷状态未定,一律不碰);
|
||||
* ② 节流门:距上次尝试不足 VOL_IDLE_SYNC_PERIOD_MS → 返回。
|
||||
* 无符号差值判据((uint32)(now - last) < 周期)天然跨 32 位回绕
|
||||
* 正确,与 stack_probe_poll 同一 idiom;勿"优化"成大小比较;
|
||||
* ③ 空闲门:vol_lock_wait(0) try——锁被持有 = 有文件操作序列在做
|
||||
* (syslog 落盘 / 配置保存 / lftpd open...close),本轮跳过,
|
||||
* 绝不改变"文件持有期内不同步"的既有语义;
|
||||
* ④ 动作:disk_ioctl(0, CTRL_SYNC)——FatFs 官方幂等落盘原语
|
||||
* (f_close 尾部同一条路径):FTL 脏页刷入 + dhara_map_sync。
|
||||
* clean 时两级短路立即返回,零 NAND 操作。
|
||||
* 失败留痕:RES_ERROR 只按"边沿"打一条 DBG_ERROR(latch),持续失败
|
||||
* 不刷屏;下一次成功后 latch 复位,新 episode 再报。底层失败形态
|
||||
* (Cache flush FAIL / Map sync failed)已由 nand_ftl 就地打点,
|
||||
* 本条只补"空闲路径也救不回"的上下文,不重复堆日志锚点。
|
||||
*/
|
||||
void vol_idle_sync_poll(void) {
|
||||
const uint32_t now = (uint32_t)xTaskGetTickCount();
|
||||
DRESULT dr;
|
||||
|
||||
/* ① 就绪门 */
|
||||
if (sys_evtHandle == NULL) {
|
||||
return;
|
||||
}
|
||||
if ((osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ② 节流门(回绕安全判据,见函数说明②) */
|
||||
if ((s_vol_sync_armed != 0u)
|
||||
&& ((uint32_t)(now - s_vol_sync_last_ms)
|
||||
< (uint32_t)pdMS_TO_TICKS(VOL_IDLE_SYNC_PERIOD_MS))) {
|
||||
return;
|
||||
}
|
||||
s_vol_sync_last_ms = now;
|
||||
s_vol_sync_armed = 1u;
|
||||
|
||||
/* ③ 空闲门:try 锁,拿不到 = 卷不空闲,本轮跳过(下周期再试) */
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ④ 幂等落盘:脏页刷入 + journal 收账;clean 时零 NAND 触点 */
|
||||
dr = disk_ioctl(0, CTRL_SYNC, NULL);
|
||||
vol_lock_release(); /* 成败都放锁:同步失败不产生锁泄漏 */
|
||||
|
||||
if (dr != RES_OK) {
|
||||
if (s_vol_sync_fail_latch == 0u) {
|
||||
s_vol_sync_fail_latch = 1u;
|
||||
DBG_ERROR("[FS] idle sync FAIL (dr=%d)", (int)dr);
|
||||
}
|
||||
} else {
|
||||
s_vol_sync_fail_latch = 0u; /* 成功:重新武装,下个 episode 可再报 */
|
||||
}
|
||||
}
|
||||
|
||||
#else /* TEST_SUITE_STORAGE */
|
||||
|
||||
/*
|
||||
* 函数功能:STORAGE 套件构建下的空实现(编译期裁剪落点)
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅当 test_config.h 选中 TEST_SUITE_STORAGE 时编入本分支
|
||||
* 函数说明:该构建里挂载/盘写由 testTask 的 storage_test_run 独占(测试含
|
||||
* nand_ftl_deinit 掉电模拟),后台同步与它并发会互相踩坏 FTL/FatFs
|
||||
* 状态——必须彻底静默,而非仅靠运行时门控。
|
||||
*/
|
||||
void vol_idle_sync_poll(void) {
|
||||
}
|
||||
|
||||
#endif /* TEST_SUITE_STORAGE */
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 模块名称:Volume Idle Sync(写回缓存空闲 flush)
|
||||
* 模块功能:随 defaultTask 主循环周期轮询,在存储**空闲**时对 NAND FatFs 卷
|
||||
* 补发一次 CTRL_SYNC(FatFs 幂等落盘语义 = FTL 单页写回缓存脏页刷入
|
||||
* + dhara journal 待办收账,见 docs/架构设计/STM32F4-Base存储架构说明.md
|
||||
* §5.5),作为写回缓存持久性的兜底网:
|
||||
* - 正常写事务语义不变:f_close 内部 sync_fs 已同步,本模块零干预;
|
||||
* - 价值在漏网兜底:任何"缓存留脏、之后再无调用触发 sync"的状态
|
||||
* (异常路径中途放锁等)其掉电驻留窗从"无限期,直到下一笔写"
|
||||
* 收敛为"不超过本模块周期";
|
||||
* - 卷不空闲(卷锁被持有:syslog 落盘 / 配置保存 / lftpd
|
||||
* open...close 序列进行中)本轮跳过——不改变文件持有期(如 FTP
|
||||
* STOR 进行中)的既有语义。
|
||||
* clean 态零 NAND 触点(脏标志与 journal 双短路);脏态成本与一次
|
||||
* f_close 同型。周期 5 s(.c 内 VOL_IDLE_SYNC_PERIOD_MS)。
|
||||
* ⚠ 边界(勿当成掉电保护):本板无掉电检测硬件,"掉电前强制 flush"
|
||||
* 不可行;"单次写事务进行中掉电"的丢失边界也不因本模块改变。
|
||||
* 适用平台:STM32F407ZGTx + FreeRTOS(CMSIS-RTOS V2);仅 MCU 端,
|
||||
* 主机侧 L0.5 单测 test/host/vol_idle_sync/
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-25(道F 批 2:写回缓存空闲 flush 收口)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 DTU 项目组 创建:STORAGE_READY 门控 + 周期节流 +
|
||||
* 卷锁 try + disk_ioctl(CTRL_SYNC) + 失败边沿 latch
|
||||
*/
|
||||
|
||||
#ifndef VOL_IDLE_SYNC_H
|
||||
#define VOL_IDLE_SYNC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:存储空闲周期同步的轮询入口(内部自带就绪门控 / 节流 / 非阻塞
|
||||
* try 锁 / clean 短路,调用点每轮无条件调用即可)
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅 defaultTask 主循环调用(存储属主任务;本函数内部绝不阻塞——
|
||||
* defaultTask 是看门狗喂狗点,见 vol_lock.h 使用约定);
|
||||
* TEST_SUITE_STORAGE 构建下为空实现(卷由 testTask 独占,后台同步
|
||||
* 必须彻底静默)
|
||||
*/
|
||||
void vol_idle_sync_poll(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VOL_IDLE_SYNC_H */
|
||||
@@ -334,8 +334,10 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
* **不改掉电语义**:批次仍在同一次 syslog_drain() 调用内写完并
|
||||
* f_close;close 内部 sync_fs → disk_ioctl(CTRL_SYNC) 会调
|
||||
* ftl_flush_cache() 把 FTL 的单页写回缓存刷进 NAND(nand_ftl.c)。
|
||||
* 落盘时机与旧实现每条 close 等价,批次不跨调用驻留 RAM;残余只到
|
||||
* "单次写事务进行中掉电/复位"为止(登记簿 N-22,2026-10-22 裁定暂不做)。
|
||||
* 落盘时机与旧实现每条 close 等价,批次不跨调用驻留 RAM;漏网脏页
|
||||
* (留脏后再无调用触发刷出)已有写回缓存空闲 flush 兜底
|
||||
* (App/fs/vol_idle_sync.h:卷空闲 + 周期 CTRL_SYNC),掉电残余只到
|
||||
* "单次写事务进行中掉电/复位"为止。
|
||||
* 自愈重试与错误去重的判据与 s_append_rec 一致;容量不足时返回 -3
|
||||
* 而不是就地 unlink——"满了就重建"必须由按条慢路径统一执行,
|
||||
* 否则一批 8 条会跨越多轮重建、语义说不清。
|
||||
@@ -846,8 +848,9 @@ int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) {
|
||||
* ⚠ 2026-10-02 round 3~8 那次"复位后写入整段丢失"的根因是 dhara resume 锚错
|
||||
* checkpoint 使 journal 整段回退(docs/架构设计/STM32F4-Base存储架构说明.md
|
||||
* §5.4.1),不是这层缓存:单页缓存最多丢最后未跨页那笔(≤512 B),丢不出
|
||||
* "整段"。真正的残余(无空闲/掉电前周期 flush)= 登记簿 N-22,2026-10-22
|
||||
* 用户裁定暂不做。
|
||||
* "整段"。空闲周期 flush 兜底已接线(App/fs/vol_idle_sync.h:STORAGE_READY
|
||||
* 门控 + 周期节流 + 卷锁 try + CTRL_SYNC,脏页驻留窗由周期封顶);本板无
|
||||
* 掉电检测硬件,"掉电前强制 flush"仍是物理边界,不在该兜底承诺之内。
|
||||
* 顺序硬约束不变:s_append 自行取卷锁(非递归锁),必须在 vol_lock_release()
|
||||
* **之后**调用,绝不可在持锁状态下调用。
|
||||
* 容量护栏:> 8KB 先 unlink 重建,防长跑无界增长(8.3 合法名 wdev.log)。
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
* 耗时(task_prof_report),并在**心跳位图出现 1→0 边沿**
|
||||
* 的瞬间留一份现场快照(task_prof_report_raw,裸 UART);
|
||||
* 另给控制台排空与 syslog 落盘各加一个耗时探针
|
||||
* v1.3 2026-10-25 DTU 项目组 写回缓存空闲 flush 接线:主循环 syslog 排空
|
||||
* 之后调 vol_idle_sync_poll()(卷空闲周期 CTRL_SYNC 兜底,
|
||||
* 模块 = App/fs/vol_idle_sync.[ch],道F 批 2)
|
||||
*/
|
||||
|
||||
#include "default_task.h"
|
||||
@@ -46,6 +49,7 @@
|
||||
#include "task_watchdog.h"
|
||||
#include "task_prof.h" /* 各任务最坏耗时上报 */
|
||||
#include "vol_lock.h"
|
||||
#include "vol_idle_sync.h" /* 写回缓存空闲 flush 兜底(App/fs/vol_idle_sync.h) */
|
||||
#include "task_handles.h" /* 任务句柄与属性块的引用契约(定义处仍是 Src/freertos.c) */
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
@@ -275,6 +279,11 @@ void default_task_func(void *argument) {
|
||||
task_prof_enter(TASK_PROF_DEF_SYSLOG);
|
||||
syslog_drain();
|
||||
task_prof_exit(TASK_PROF_DEF_SYSLOG);
|
||||
/* 写回缓存空闲 flush 兜底:卷空闲(try 锁)+ 周期节流 + 就绪门控全在
|
||||
* 模块内(App/fs/vol_idle_sync.h);clean 态零 NAND 触点,脏态最坏 =
|
||||
* 一次 f_close 同型的同步段。放在 drain 之后:本拍若写过日志,close
|
||||
* 已同步过,这里随即 clean 短路——漏网脏页的驻留窗由周期封顶 */
|
||||
vol_idle_sync_poll();
|
||||
#endif
|
||||
/* 栈水位观测:首次 5s 后报告,此后每 2s 采样、出现更深峰值才打印 */
|
||||
task_wd_seg(TASK_WD_SEG_STACK);
|
||||
|
||||
@@ -937,6 +937,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\fs\vol_lock.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>vol_idle_sync.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\fs\vol_idle_sync.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>time_sync.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
|
||||
@@ -391,6 +391,16 @@ FTL 之上还有一个 **单页写回缓存 (write-back cache)**:
|
||||
- **写**: 写入缓存 → 标记脏
|
||||
- **全页写入**: 直接刷出 (跳过缓存)
|
||||
- **同步 (CTRL_SYNC)**: 刷出脏页 + dhara_map_sync
|
||||
- **空闲 flush 兜底(2026-10-25,道F 批 2)**: defaultTask 主循环按"存储就绪 +
|
||||
卷空闲(`vol_lock_wait(0)` try)+ 周期节流(5 s)"周期性下发一次 `CTRL_SYNC`,
|
||||
把"缓存留脏、之后再无调用触发 sync"的漏网脏页其掉电驻留窗封顶为一个周期;
|
||||
卷锁被持有(syslog 落盘 / 配置保存 / lftpd open...close 进行中)时跳过,
|
||||
**不改变文件持有期的既有语义**。clean 态 = 脏标志 + journal 双短路、零 NAND
|
||||
触点;脏态成本与一次 `f_close` 的同步段同型。实现:`App/fs/vol_idle_sync.[ch]`
|
||||
(单测 `test/host/vol_idle_sync/`)。
|
||||
- ⚠ **边界(勿当成掉电保护)**: 本板**无掉电检测硬件**,"掉电前强制 flush"
|
||||
不可行;单次写事务进行中掉电、以及 FTP STOR 文件持有期的落盘边界,均不因
|
||||
空闲兜底改变。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ SIM_EXEMPT = [
|
||||
("App/algo/adc_cal.c", "L1.5 标定折算纯算法 + cfg 段注册(消费者 = MCU 侧 adcTask 每拍;模拟器不跑采样任务),模拟器无消费路径"),
|
||||
("App/app/app_main.c", "固件启动/就绪屏障,MCU 专属"),
|
||||
("App/fs/vol_lock.c", "FreeRTOS 互斥量卷锁,MCU 专属"),
|
||||
("App/fs/vol_idle_sync.c", "MCU 存储属主后台空闲同步(FatFs disk_ioctl + 卷锁 + sys_events),L0.5 覆盖于 test/host/vol_idle_sync/,模拟器无消费路径"),
|
||||
("App/log/console_log.c", "MCU 控制台输出,模拟器无消费路径"),
|
||||
("App/sync/time_sync.c", "对时任务(net+RTOS),MCU 专属"),
|
||||
("App/task/adc_task.c", "固件 FreeRTOS 任务,模拟器不跑固件任务"),
|
||||
|
||||
@@ -1730,6 +1730,28 @@
|
||||
"owner_note": "B 层口径(用户裁定 2026-10-21,同 TC-HOST-ADCHEALTH/TC-HOST-ADCCAL 先例;2026-10-25 道A 卡7 建族):属主登记处 = 单测代码目录,不逐号补文档",
|
||||
"cursor_note": "游标按建族时实测最大号收录(2026-10-25):越界即视为未登记的冒号牌"
|
||||
},
|
||||
{
|
||||
"name": "TC-HOST-VOLSYNC",
|
||||
"class": "TC",
|
||||
"title": "主机侧单测用例号:写回缓存空闲 flush(test/host/vol_idle_sync,道F 批 2)",
|
||||
"pattern": "\\bTC-VOLSYNC-[0-9]{1,4}[a-z]?\\b",
|
||||
"digit_width": [
|
||||
2,
|
||||
4
|
||||
],
|
||||
"owner_docs_glob": [
|
||||
"test/host/vol_idle_sync/*"
|
||||
],
|
||||
"scope": "global",
|
||||
"frozen": false,
|
||||
"cursor": "TC-VOLSYNC-012",
|
||||
"cursor_owner_only": true,
|
||||
"check_dangling": true,
|
||||
"allow_orphans": true,
|
||||
"subitem_ok": true,
|
||||
"owner_note": "B 层口径(用户裁定 2026-10-21,同 TC-HOST-ADCHEALTH/TC-HOST-ADCCAL 先例;2026-10-25 道F 批 2 建族):属主登记处 = 单测代码目录,不逐号补文档",
|
||||
"cursor_note": "游标按建族时实测最大号收录(2026-10-25):越界即视为未登记的冒号牌"
|
||||
},
|
||||
{
|
||||
"name": "TC-HOST-IEC104",
|
||||
"class": "TC",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""写回缓存空闲 flush(vol_idle_sync)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/vol_idle_sync/vol_idle_sync_test.c(它以 #include
|
||||
纳入**生产源文件** App/fs/vol_idle_sync.c),在 PC 上离线跑就绪门控 / 节流边界 /
|
||||
卷忙跳过 / 失败边沿 latch / 成对放锁 / tick 回绕,失败即非 0 退出。
|
||||
零硬件、零烧录、毫秒级。
|
||||
|
||||
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||
|
||||
为什么要主机侧单测(而不是只用台架):
|
||||
本模块是「掉电窗口兜底网」——掉电与脏页驻留窗在台架上都不可按需复现;卷忙 /
|
||||
ioctl 失败 / 32 位 tick 回绕这些分支在真机上构造成本极高。门控、节流、latch
|
||||
全是纯状态逻辑,主机侧一次钉死。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/vol_idle_sync/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:App/fs/vol_idle_sync.h 文件头(含「无掉电检测硬件」边界声明);
|
||||
创建日期:2026-10-25
|
||||
"""
|
||||
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 目录必须**先于**生产目录,才能覆盖 test_config.h / cmsis_os(_2).h /
|
||||
# FreeRTOS.h / task.h / dbg_log.h(真实头会拖入整套 FreeRTOS/CMSIS/HAL)。
|
||||
# ff.h / diskio.h / vol_lock.h / sys_events.h 用**真实头**(接口契约与就绪位数值
|
||||
# 原汁,同 lftpd_io / nand_port 先例;本模块经真实 sys_events.h 拿就绪位定义)。
|
||||
BUILD = TestBuild(
|
||||
name="Volume Idle Sync Tests",
|
||||
src=os.path.join(HERE, "vol_idle_sync_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "fs"),
|
||||
os.path.join(ROOT, "App", "app"),
|
||||
os.path.join(ROOT, "Lib", "FatFs"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Volume Idle Sync Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 主机侧单测桩:FreeRTOS.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:最小 FreeRTOS 时基类型与宏(真实头会拉整棵 portmacro/projdefs)。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 保真点:TickType_t = uint32_t;configTICK_RATE_HZ = 1000
|
||||
* (Inc/FreeRTOSConfig.h)→ pdMS_TO_TICKS(ms) == ms。
|
||||
* 节流用例的 4999/5000 拍边界直接依赖该等式。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef FREERTOS_H
|
||||
#define FREERTOS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef uint32_t TickType_t;
|
||||
|
||||
#define configTICK_RATE_HZ 1000U
|
||||
#define pdMS_TO_TICKS(ms) ((TickType_t)((uint32_t)(ms) * (1000U / configTICK_RATE_HZ)))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FREERTOS_H */
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 主机侧单测桩:cmsis_os.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:真实 cmsis_os.h(CMSIS-RTOS V2 包装)会拖入整套 FreeRTOS;本模块
|
||||
* 只需要事件标志接口,全部收敛在 cmsis_os2.h 桩里。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef CMSIS_OS_H
|
||||
#define CMSIS_OS_H
|
||||
|
||||
#include "cmsis_os2.h"
|
||||
|
||||
#endif /* CMSIS_OS_H */
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 主机侧单测桩:cmsis_os2.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:为 App/fs/vol_idle_sync.c(含经 sys_events.h)提供主机可编译的
|
||||
* CMSIS-RTOS v2 事件标志最小子集。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 保真点(对齐真实 cmsis_os2.h):
|
||||
* - osEventFlagsId_t = void*;
|
||||
* - uint32_t osEventFlagsGet(osEventFlagsId_t)(返回当前位图,不消费——
|
||||
* 就绪位 osFlagsNoClear 语义在生产调用点之外,本桩只回静态值);
|
||||
* - 就绪位数值一律来自**真实** App/app/sys_events.h(不在本桩复刻)。
|
||||
* osEventFlagsGet 由测试单元给强符号桩(可注入位图)。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef CMSIS_OS2_H
|
||||
#define CMSIS_OS2_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *osEventFlagsId_t;
|
||||
|
||||
uint32_t osEventFlagsGet(osEventFlagsId_t ef_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CMSIS_OS2_H */
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 主机侧单测桩:dbg_log.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:DBG_* 换成计数,让"失败留痕 = 边沿 latch、成功不刷屏"可断言。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef DBG_LOG_H
|
||||
#define DBG_LOG_H
|
||||
|
||||
extern int g_vis_error_count;
|
||||
extern int g_vis_info_count;
|
||||
|
||||
#define DBG_INFO(fmt, ...) do { (void)(fmt); g_vis_info_count++; } while (0)
|
||||
#define DBG_ERROR(fmt, ...) do { (void)(fmt); g_vis_error_count++; } while (0)
|
||||
|
||||
#endif /* DBG_LOG_H */
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 主机侧单测桩:task.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:只声明 xTaskGetTickCount,由测试单元给可控时钟强符号桩
|
||||
* (驱动节流边界与 32 位回绕用例)。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef TASK_H
|
||||
#define TASK_H
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
TickType_t xTaskGetTickCount(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TASK_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 主机侧单测桩:test_config.h(vol_idle_sync 模块专用)
|
||||
* 模块功能:给主机编译一个"产品构建"形态——四个 TEST_SUITE_* 全部未定义,
|
||||
* vol_idle_sync.c 走产品分支(STORAGE 套件的 no-op 分支由板端
|
||||
* test-suite-switch 构建侧覆盖,主机侧不重复裁剪测试)。
|
||||
* ⚠ 仅供 test/host/vol_idle_sync/ 使用,不参与 MCU 构建。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef TEST_CONFIG_H
|
||||
#define TEST_CONFIG_H
|
||||
|
||||
/* 刻意不定义任何 TEST_SUITE_* / ENABLE_DESTRUCTIVE_TESTS */
|
||||
|
||||
#endif /* TEST_CONFIG_H */
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 模块名称:Volume Idle Sync(写回缓存空闲 flush)主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/fs/vol_idle_sync.c 做离线验证(产品分支全函数覆盖)。
|
||||
*
|
||||
* 被测对象:真实源文件 ../../../App/fs/vol_idle_sync.c(以 #include 原样纳入,
|
||||
* 保证测的是生产代码而非副本)。平台依赖由本文件给出的**可注入强符号桩**:
|
||||
* - xTaskGetTickCount -> 可控时钟(节流边界 + 32 位回绕)
|
||||
* - osEventFlagsGet -> 可注入就绪位图 / 句柄判空
|
||||
* - vol_lock_wait -> 可注入 空闲/忙(try 语义)+ 透传记录
|
||||
* - vol_lock_release -> 计数(断言成对、失败不泄漏)
|
||||
* - disk_ioctl -> 可注入 RES_OK/RES_ERROR + pdrv/cmd 记录
|
||||
* 事件位数值取自**真实** App/app/sys_events.h(不在桩里复刻)。
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用台架):
|
||||
* 本模块是"掉电窗口兜底网"——掉电本身不可台架复现,脏页驻留窗更不能;
|
||||
* 失败路径(卷忙 / ioctl 失败 / tick 回绕)在真机上极难按需构造。
|
||||
* 门控/节流/latch 全是纯状态逻辑,主机侧一次钉死,板端只验证接入存在性。
|
||||
*
|
||||
* 覆盖点:
|
||||
* TC-VOLSYNC-001 句柄未建 -> 完全静默(不读事件/不取锁/不 ioctl)
|
||||
* TC-VOLSYNC-002 STORAGE_READY 未置 -> 静默(读了事件但不取锁)
|
||||
* TC-VOLSYNC-003 就绪首拍:try 锁(timeout=0) + CTRL_SYNC(pdrv=0) + 放锁各一次
|
||||
* TC-VOLSYNC-004 周期内重复拍 -> 完全抑制(不取锁不 ioctl)
|
||||
* TC-VOLSYNC-005 节流边界:周期-1 拍抑制 / 满周期拍放行(1ms tick 等式)
|
||||
* TC-VOLSYNC-006 卷忙(try 失败)-> 不 ioctl、不 release(不得空放锁)
|
||||
* TC-VOLSYNC-007 卷忙跳过不欠账:节奏恒定(下次尝试 = 满周期之后)
|
||||
* TC-VOLSYNC-008 ioctl 失败一个 episode 只打一条(边沿 latch,持续失败不刷屏)
|
||||
* TC-VOLSYNC-009 成功后 latch 重新武装:下一个失败 episode 再报一条
|
||||
* TC-VOLSYNC-010 失败路径同样成对放锁(release 次数 == 有效 ioctl 次数)
|
||||
* TC-VOLSYNC-011 tick 32 位回绕:跨 0 后节流判据仍正确(该抑制抑制/该放行放行)
|
||||
* TC-VOLSYNC-012 成功路径零日志(不打 DBG_ERROR/DBG_INFO)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||
*
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ===================== 真实中间件头(与 lftpd_io/nand_port 同一份) ======== */
|
||||
#include "ff.h" /* FatFs 基础类型(diskio.h 依赖,先纳) */
|
||||
#include "diskio.h"
|
||||
|
||||
/* ===================== 平台桩(stub 目录优先于真实头) ==================== */
|
||||
#include "test_config.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "sys_events.h" /* 真实头:SYS_EVT_STORAGE_READY / sys_evtHandle extern */
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* ===================== 强符号桩 =====================
|
||||
* 时钟:非零起步(1000),避免"初值 0"与"回绕"在断言里混同 */
|
||||
static uint32_t s_tick_now;
|
||||
|
||||
TickType_t xTaskGetTickCount(void) {
|
||||
return (TickType_t)s_tick_now;
|
||||
}
|
||||
|
||||
/* 事件标志:句柄实体在本测试单元定义(真实 sys_events.h 只 extern) */
|
||||
static uint32_t s_evt_flags;
|
||||
static int s_efget_calls;
|
||||
|
||||
osEventFlagsId_t sys_evtHandle;
|
||||
|
||||
uint32_t osEventFlagsGet(osEventFlagsId_t ef_id) {
|
||||
(void)ef_id;
|
||||
s_efget_calls++;
|
||||
return s_evt_flags;
|
||||
}
|
||||
|
||||
/* 卷锁:1 = 获取成功,0 = 忙(try 语义下即"本轮跳过") */
|
||||
static int s_lock_ok;
|
||||
static int s_lock_calls;
|
||||
static int s_release_calls;
|
||||
static uint32_t s_last_wait_to;
|
||||
|
||||
int vol_lock_wait(uint32_t timeout_ms) {
|
||||
s_lock_calls++;
|
||||
s_last_wait_to = timeout_ms;
|
||||
return s_lock_ok;
|
||||
}
|
||||
|
||||
void vol_lock_release(void) {
|
||||
s_release_calls++;
|
||||
}
|
||||
|
||||
/* 磁盘控制:只接住被测代码真正会发的那一发(pdrv=0 / CTRL_SYNC) */
|
||||
static DRESULT s_diskio_res;
|
||||
static int s_diskio_calls;
|
||||
static BYTE s_diskio_pdrv;
|
||||
static BYTE s_diskio_cmd;
|
||||
|
||||
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
|
||||
(void)buff;
|
||||
s_diskio_calls++;
|
||||
s_diskio_pdrv = pdrv;
|
||||
s_diskio_cmd = cmd;
|
||||
return s_diskio_res;
|
||||
}
|
||||
|
||||
/* dbg_log 桩计数(stub/dbg_log.h 声明 extern) */
|
||||
int g_vis_error_count;
|
||||
int g_vis_info_count;
|
||||
|
||||
/* ===================== 被测代码(原样纳入生产源) ===================== */
|
||||
#include "vol_idle_sync.c"
|
||||
|
||||
/* ===================== 断言 ===================== */
|
||||
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
|
||||
#define CHECK(cond, name) do { \
|
||||
s_total++; \
|
||||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||||
} while (0)
|
||||
|
||||
#define FAKE_EVT_HANDLE ((osEventFlagsId_t)0xE7)
|
||||
#define PERIOD_TICKS 5000u /* = pdMS_TO_TICKS(VOL_IDLE_SYNC_PERIOD_MS),
|
||||
* 1 kHz tick 下与毫秒数值等值(桩保真点) */
|
||||
|
||||
/* 复位所有桩与被测模块状态量(白盒:生产源已纳入本编译单元,文件级 static
|
||||
* 可直接复位,等价"刚上电") */
|
||||
static void fakes_reset(void) {
|
||||
s_tick_now = 1000u;
|
||||
s_evt_flags = 0u;
|
||||
sys_evtHandle = NULL;
|
||||
s_efget_calls = 0;
|
||||
s_lock_ok = 1;
|
||||
s_lock_calls = 0;
|
||||
s_release_calls = 0;
|
||||
s_last_wait_to = 0xFFFFFFFFu;
|
||||
s_diskio_res = RES_OK;
|
||||
s_diskio_calls = 0;
|
||||
s_diskio_pdrv = 0xFFu;
|
||||
s_diskio_cmd = 0xFFu;
|
||||
g_vis_error_count = 0;
|
||||
g_vis_info_count = 0;
|
||||
s_vol_sync_last_ms = 0u;
|
||||
s_vol_sync_armed = 0u;
|
||||
s_vol_sync_fail_latch = 0u;
|
||||
}
|
||||
|
||||
/* 便捷:推进时钟并空转主循环拍(模拟 defaultTask 循环里的连续 poll) */
|
||||
static void advance_and_poll(uint32_t ticks) {
|
||||
s_tick_now += ticks;
|
||||
vol_idle_sync_poll();
|
||||
}
|
||||
|
||||
/* ===================== TC-VOLSYNC-001/002:就绪门控 ===================== */
|
||||
|
||||
static void tc_gate(void) {
|
||||
fakes_reset();
|
||||
vol_idle_sync_poll();
|
||||
CHECK((s_efget_calls == 0) && (s_lock_calls == 0) && (s_diskio_calls == 0),
|
||||
"TC-VOLSYNC-001 handle NULL: fully silent (no flags read, no lock, no ioctl)");
|
||||
|
||||
fakes_reset();
|
||||
sys_evtHandle = FAKE_EVT_HANDLE; /* 句柄已建但未就绪 */
|
||||
vol_idle_sync_poll();
|
||||
CHECK((s_efget_calls == 1) && (s_lock_calls == 0) && (s_diskio_calls == 0),
|
||||
"TC-VOLSYNC-002 STORAGE_READY clear: read flags then bail (no lock, no ioctl)");
|
||||
}
|
||||
|
||||
/* ===================== TC-VOLSYNC-003..007:放行/节流/卷忙 ===================== */
|
||||
|
||||
static void tc_run_throttle(void) {
|
||||
fakes_reset();
|
||||
sys_evtHandle = FAKE_EVT_HANDLE;
|
||||
s_evt_flags = SYS_EVT_STORAGE_READY;
|
||||
vol_idle_sync_poll();
|
||||
CHECK((s_lock_calls == 1) && (s_last_wait_to == 0u),
|
||||
"TC-VOLSYNC-003 first ready tick: try-lock taken with timeout=0 (non-blocking)");
|
||||
CHECK((s_diskio_calls == 1) && (s_diskio_pdrv == 0u) && (s_diskio_cmd == (BYTE)CTRL_SYNC),
|
||||
"TC-VOLSYNC-003b ioctl exactly once with pdrv=0 / CTRL_SYNC");
|
||||
CHECK(s_release_calls == 1,
|
||||
"TC-VOLSYNC-003c lock released exactly once after ioctl");
|
||||
|
||||
vol_idle_sync_poll();
|
||||
vol_idle_sync_poll();
|
||||
CHECK((s_lock_calls == 1) && (s_diskio_calls == 1),
|
||||
"TC-VOLSYNC-004 repeats within period fully suppressed (no lock, no ioctl)");
|
||||
|
||||
/* 边界:+4999 抑制 / +1(累计满 5000)放行 */
|
||||
advance_and_poll(PERIOD_TICKS - 1u);
|
||||
CHECK(s_diskio_calls == 1, "TC-VOLSYNC-005 period-1 tick: still suppressed");
|
||||
advance_and_poll(1u);
|
||||
CHECK(s_diskio_calls == 2, "TC-VOLSYNC-005b exact period tick: fires again");
|
||||
|
||||
/* 卷忙:ioctl 不加、release 不得空放(相对断言:忙尝试整段零增量) */
|
||||
{
|
||||
const int lk = s_lock_calls;
|
||||
const int rel = s_release_calls;
|
||||
s_lock_ok = 0;
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
CHECK((s_lock_calls == lk + 1) && (s_diskio_calls == 2) && (s_release_calls == rel),
|
||||
"TC-VOLSYNC-006 lock busy: attempt logged, no ioctl and NO release (never unlock what we do not hold)");
|
||||
|
||||
/* 跳过不欠账:忙的那拍消费了槽位 -> 下次尝试在一个满周期之后 */
|
||||
s_lock_ok = 1;
|
||||
advance_and_poll(PERIOD_TICKS - 1u);
|
||||
CHECK(s_lock_calls == lk + 1,
|
||||
"TC-VOLSYNC-007 busy skip consumed the slot: constant cadence");
|
||||
advance_and_poll(1u);
|
||||
CHECK((s_lock_calls == lk + 2) && (s_diskio_calls == 3) && (s_release_calls == rel + 1),
|
||||
"TC-VOLSYNC-007b next attempt exactly one full period after the skip");
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== TC-VOLSYNC-008..010/012:失败 latch / 成对放锁 ======= */
|
||||
|
||||
static void tc_fail_latch(void) {
|
||||
fakes_reset();
|
||||
sys_evtHandle = FAKE_EVT_HANDLE;
|
||||
s_evt_flags = SYS_EVT_STORAGE_READY;
|
||||
|
||||
vol_idle_sync_poll(); /* 首拍成功 */
|
||||
CHECK((g_vis_error_count == 0) && (g_vis_info_count == 0),
|
||||
"TC-VOLSYNC-012 success path logs nothing");
|
||||
|
||||
s_diskio_res = RES_ERROR;
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
CHECK(g_vis_error_count == 1, "TC-VOLSYNC-008 first failure logs exactly one line");
|
||||
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
CHECK((g_vis_error_count == 1) && (s_diskio_calls == 4),
|
||||
"TC-VOLSYNC-008b continuing failures do not spam (edge latch holds)");
|
||||
|
||||
CHECK(s_release_calls == s_diskio_calls,
|
||||
"TC-VOLSYNC-010 release paired with every attempt incl. failures (no lock leak)");
|
||||
|
||||
s_diskio_res = RES_OK;
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
CHECK((g_vis_error_count == 1) && (s_vol_sync_fail_latch == 0u),
|
||||
"TC-VOLSYNC-009 success clears latch (re-armed for next episode)");
|
||||
|
||||
s_diskio_res = RES_ERROR;
|
||||
advance_and_poll(PERIOD_TICKS);
|
||||
CHECK(g_vis_error_count == 2, "TC-VOLSYNC-009b new failing episode logs again (one line)");
|
||||
}
|
||||
|
||||
/* ===================== TC-VOLSYNC-011:tick 回绕 ===================== */
|
||||
|
||||
static void tc_wrap(void) {
|
||||
fakes_reset();
|
||||
sys_evtHandle = FAKE_EVT_HANDLE;
|
||||
s_evt_flags = SYS_EVT_STORAGE_READY;
|
||||
/* 白盒置态:上次尝试贴着 32 位顶端,当前拍刚跨过 0 */
|
||||
s_vol_sync_armed = 1u;
|
||||
s_vol_sync_last_ms = 0xFFFFF000u;
|
||||
s_tick_now = 0x0300u; /* 无符号差 = 0x1300 = 4864 < 5000 */
|
||||
vol_idle_sync_poll();
|
||||
CHECK(s_diskio_calls == 0, "TC-VOLSYNC-011 wrap: unsigned-diff still suppresses early tick");
|
||||
|
||||
s_vol_sync_last_ms = 0xFFFFF000u;
|
||||
s_tick_now = 0x0800u; /* 无符号差 = 0x1800 = 6144 >= 5000 */
|
||||
vol_idle_sync_poll();
|
||||
CHECK(s_diskio_calls == 1, "TC-VOLSYNC-011b wrap past zero fires when diff says so");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
|
||||
int main(void) {
|
||||
printf("=== Volume Idle Sync Tests ===\n");
|
||||
|
||||
printf("-- gate (readiness) --\n");
|
||||
tc_gate();
|
||||
printf("-- run / throttle / idle-skip --\n");
|
||||
tc_run_throttle();
|
||||
printf("-- failure latch / pairing --\n");
|
||||
tc_fail_latch();
|
||||
printf("-- tick wraparound --\n");
|
||||
tc_wrap();
|
||||
|
||||
printf("\n=== Volume Idle Sync Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user