/* * 模块名称: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 #include #include /* ===================== 真实中间件头(与 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; }