Files
STM32F4-Base/test/host/vol_lock/vol_lock_test.c
T
wandering1 f1d0b5c2d9 docs(规范,门禁): 卷锁五态去裸形——本仓第一次真正还第二根护栏的债,并写清还债的算法
最反直觉的是结果里的差值:36 处裸形态号全部清零,绝对数只从 1,162 降到 1,156(−6,余量 4 → 10 格)。
作者没把差值糊过去而是查到底:check_ids 只在字面量**落不进任何已登记族**时才计不可判定,撞进已登记族的
23 处本来就记在可判定侧,删它们分子分母同缩、比例反而从 86.74% 涨到 86.77%;真正在盲区里的只有族外
7 处,其中 1 处还被变更记录行豁免 ⇒ 账面 −6,算式对平(±1 属同窗别道在飞)。**这四条机制我写进
docs/门禁说明/id-check.md §6.10 并署名作者**——它是「怎么还这根护栏的债」的第一份操作口径。

改动面:App/fs/vol_lock.c 10 处、.h 6 处、vol_lock_test.c 14 处、卷锁单元测试说明.md 3 处 ⇒ 同口径
改后 0 处。四态语义只留 vol_lock.h 的 vol_lock_stats_t 注释一处,其余三处只描述断言并指回头文件,
不抄第二真源。除 4 行声明外全是注释文字,代码行为零改动。顺带消掉一个**同形两义**的撞号源。

实测:1,162(03:15:08)→ 1,156(03:21:56),写变更记录后复量仍 1,156(本笔没吃余量);--strict exit
0;vol_lock 套件 45/45;**上限未动、刻意不跑 --update-baseline**(作者反对成立:重锚会把只减不增的线
按这次 −6 重画,白丢一次真还债的账面记录);未改 id_registry.json / 台账,未跑 --all-files。
2026-10-27 05:14:11 +08:00

297 lines
14 KiB
C
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.
/*
* 模块名称:Volume Lock(卷锁)主机侧单元测试
* 模块功能:在 PC 上对 App/fs/vol_lock.c 做离线验证(3 个公开函数全覆盖)。
*
* 被测对象:真实源文件 ../../../App/fs/vol_lock.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
* - cmsis_os.h -> osMutexNew / osMutexWait / osMutexRelease
* (可注入 创建失败 / 等待超时 / 获取成功)
*
* 为什么要主机侧单测(而不是只用板端套件):
* 1. 本模块最要紧的语义是 **fail-open**(锁创建失败时 `vol_lock_wait` 必须返回 1
* 且**绝不阻塞**)——这是"defaultTask 喂狗点绝不能因等锁而停摆"的最后一道保险。
* 真机上 `osMutexNew` 失败几乎不可能复现(需堆耗尽),板端套件无法构造。
* 2. 幂等性(重复 `vol_lock_init` 不得重复创建)、等待超时透传、未创建时 release 无害,
* 都是纯状态逻辑,主机侧一次钉死。
* 3. 不占 MCU 的 RAM,毫秒级完成。
*
* 覆盖点:
* TC-VOLLOCK-001 init 首次调用创建互斥量并返回 0
* TC-VOLLOCK-002 init 幂等(重复调用不再创建)
* TC-VOLLOCK-003 init 在 osMutexNew 失败时返回 -1
* TC-VOLLOCK-004 init 失败后可重试成功(s_lock 未被污染)
* TC-VOLLOCK-005 wait 成功后返回 1,且把 timeout 原样透传给 osMutexWait
* TC-VOLLOCK-006 wait 超时(osMutexWait != osOK)返回 0
* TC-VOLLOCK-007 wait 在锁未创建时自动 init,再正常获取
* TC-VOLLOCK-008 ⭐ fail-open:锁未创建且创建失败 -> 返回 1 且**不调用** osMutexWait
* TC-VOLLOCK-009 fail-open 之后 release 无害(s_lock 仍为 NULL,不调用 osMutexRelease)
* TC-VOLLOCK-010 已创建时 wait 不再调用 osMutexNew
* TC-VOLLOCK-011 release 在已持锁时调用 osMutexRelease 一次
* TC-VOLLOCK-012 release 在锁未创建时静默返回(不崩溃、不调用)
* TC-VOLLOCK-013 完整周期 init -> wait -> release -> wait -> release
* TC-VOLLOCK-014 wait(0) 的 timeout 透传为 0(defaultTask 的 try 语义)
* —— 观测计数(道G task-17「卷锁 5 态可观测性」)——
* TC-VOLLOCK-015 成功路径只涨 acquired,三个异常计数必须为 0
* TC-VOLLOCK-016 timeout=0 未取到 -> 只进 try_timeout(try 超时态,压根没等)
* TC-VOLLOCK-017 timeout=100 未取到 -> 只进 blk_timeout(分片退让态,等了没等到),
* 与 try 超时态互不冒领
* TC-VOLLOCK-018 ⭐ fail-open 仍返 1(口径不变)但 failopen 计数自增并可累加
* —— 把"谎报成功"那一态变成事后可查的证据
* TC-VOLLOCK-019 观测口:NULL 无害、重复读取不消耗证据
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-10
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
#include "cmsis_os.h"
/* ===================== CMSIS-RTOS 互斥量桩 =====================
* s_new_fail osMutexNew 返回 NULL(注入创建失败)
* s_wait_ok 0 -> osMutexWait 返回 osErrorTimeout(注入超时)
* s_fake_mutex 成功创建时返回的伪句柄(非 NULL 即可)
* s_last_timeout 记录 osMutexWait 收到的 millisec(断言透传)
*/
static int s_new_calls;
static int s_new_fail;
static int s_wait_calls;
static int s_wait_ok;
static int s_release_calls;
static uint32_t s_last_timeout;
static void *s_fake_mutex = (void *)0x1234;
osMutexId_t osMutexNew(const osMutexAttr_t *attr) {
(void)attr;
s_new_calls++;
return s_new_fail ? NULL : (osMutexId_t)s_fake_mutex;
}
osStatus_t osMutexWait(osMutexId_t mutex_id, uint32_t millisec) {
(void)mutex_id;
s_wait_calls++;
s_last_timeout = millisec;
return s_wait_ok ? osOK : osErrorTimeout;
}
osStatus_t osMutexRelease(osMutexId_t mutex_id) {
(void)mutex_id;
s_release_calls++;
return osOK;
}
/* ===================== 断言 ===================== */
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)
/* ===================== 被测代码(原样纳入生产源) ===================== */
#include "vol_lock.c"
/* ===================== 工具 ===================== */
/*
* 函数功能:把全部桩与模块静态态复位到"刚上电"初值
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:`s_lock` 是 vol_lock.c 的文件静态量(源文件已被纳入同一编译单元),
* 直接置 NULL 等价于"未初始化过"——这是白盒单测的可接受代价。
*/
static void fakes_reset(void) {
s_new_calls = 0;
s_new_fail = 0;
s_wait_calls = 0;
s_wait_ok = 1;
s_release_calls = 0;
s_last_timeout = 0xFFFFFFFFu;
s_fake_mutex = (void *)0x1234;
s_lock = NULL;
/* 观测计数同属被测模块的文件静态量(道G task-17):复位后每条 TC 才能读到
* "本条自己造出来的那几个事件",而不是前一条的残留 */
s_st_acquired = 0u;
s_st_try_timeout = 0u;
s_st_blk_timeout = 0u;
s_st_failopen = 0u;
}
/* ===================== TC-VOLLOCK-001..004:vol_lock_init ===================== */
static void tc_init(void) {
fakes_reset();
CHECK(vol_lock_init() == 0, "TC-VOLLOCK-001 first init call returns 0");
CHECK(s_new_calls == 1, "TC-VOLLOCK-001b init creates the mutex exactly once (osMutexNew)");
CHECK(s_lock != NULL, "TC-VOLLOCK-001c module static state holds the handle");
CHECK(vol_lock_init() == 0, "TC-VOLLOCK-002 repeated init still returns 0 (idempotent)");
CHECK(s_new_calls == 1, "TC-VOLLOCK-002b idempotent: no second mutex created");
fakes_reset();
s_new_fail = 1;
CHECK(vol_lock_init() == -1, "TC-VOLLOCK-003 osMutexNew failure -> init returns -1");
CHECK(s_lock == NULL, "TC-VOLLOCK-003b failure leaves no half-initialized handle");
s_new_fail = 0;
CHECK(vol_lock_init() == 0, "TC-VOLLOCK-004 retry after failure can succeed (returns 0)");
CHECK(s_new_calls == 2, "TC-VOLLOCK-004b retry really calls osMutexNew again");
}
/* ===================== TC-VOLLOCK-005..010:vol_lock_wait ===================== */
static void tc_wait(void) {
/* --- 已创建:超时值原样透传 --- */
fakes_reset();
(void)vol_lock_init();
CHECK(vol_lock_wait(100U) == 1, "TC-VOLLOCK-005 wait returns 1 on acquire success");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-005b osMutexWait called once");
CHECK(s_last_timeout == 100U, "TC-VOLLOCK-005c timeout passed through to osMutexWait unchanged");
fakes_reset();
(void)vol_lock_init();
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-014 wait(0) returns 1 on acquire (try semantics)");
CHECK(s_last_timeout == 0U, "TC-VOLLOCK-014b wait(0) timeout passes through as 0");
/* --- 超时:osMutexWait != osOK -> 返回 0 --- */
fakes_reset();
(void)vol_lock_init();
s_wait_ok = 0;
CHECK(vol_lock_wait(50U) == 0, "TC-VOLLOCK-006 osMutexWait not osOK -> wait returns 0");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-006b timeout path really attempted the wait");
/* --- 锁未创建:自动 init 后正常获取 --- */
fakes_reset();
CHECK(vol_lock_wait(10U) == 1, "TC-VOLLOCK-007 wait auto-inits and acquires when no lock exists");
CHECK(s_new_calls == 1, "TC-VOLLOCK-007b auto init created the mutex");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-007c then performed one wait");
/* --- ⭐ fail-open:创建失败必须返回 1 且绝不阻塞 --- */
fakes_reset();
s_new_fail = 1;
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-008 fail-open: returns 1 even if lock creation fails (never blocks a feed point)");
CHECK(s_wait_calls == 0, "TC-VOLLOCK-008b fail-open **does not call** osMutexWait (zero blocking)");
CHECK(s_lock == NULL, "TC-VOLLOCK-008c still no handle after fail-open");
/* --- fail-open 之后 release 无害 --- */
vol_lock_release();
CHECK(s_release_calls == 0, "TC-VOLLOCK-009 after fail-open release does not call osMutexRelease (NULL guard, silent)");
/* --- 已创建时 wait 不再创建 --- */
fakes_reset();
(void)vol_lock_init();
(void)vol_lock_wait(0U);
(void)vol_lock_wait(0U);
CHECK(s_new_calls == 1, "TC-VOLLOCK-010 repeated wait does not recreate the mutex");
CHECK(s_wait_calls == 2, "TC-VOLLOCK-010b each wait performs one wait call");
}
/* ===================== TC-VOLLOCK-011..013:vol_lock_release ===================== */
static void tc_release(void) {
/* --- 已持锁:释放一次 --- */
fakes_reset();
(void)vol_lock_init();
(void)vol_lock_wait(0U);
vol_lock_release();
CHECK(s_release_calls == 1, "TC-VOLLOCK-011 release while holding the lock calls osMutexRelease once");
/* --- 锁未创建:静默返回 --- */
fakes_reset();
vol_lock_release();
CHECK(s_release_calls == 0, "TC-VOLLOCK-012 release with no lock returns silently (no call, no crash)");
/* --- 完整周期 --- */
fakes_reset();
CHECK(vol_lock_init() == 0, "TC-VOLLOCK-013 cycle: init -> 0");
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-013b cycle: wait -> 1");
vol_lock_release();
CHECK(vol_lock_wait(100U) == 1, "TC-VOLLOCK-013c cycle: wait again -> 1 (re-acquirable)");
vol_lock_release();
CHECK(s_new_calls == 1, "TC-VOLLOCK-013d cycle: only one mutex created overall");
CHECK(s_release_calls == 2, "TC-VOLLOCK-013e cycle: released twice");
CHECK(s_last_timeout == 100U, "TC-VOLLOCK-013f cycle: second wait timeout passes through correctly");
}
/* ===================== TC-VOLLOCK-015..019:观测计数(道G task-17) =====================
* 这一组是"5 态可观测性"的机器证据:
* 四态 acquired / try_timeout / blk_timeout / failopen 各自独立自增(定义只在 vol_lock.h),
* try 超时态与分片退让态不许互相冒领(本卡唯一"语义合并"风险);零互斥态从"谎报成功"变成可查。
*/
static void tc_stats(void) {
vol_lock_stats_t st;
/* 015 成功路径:只有 acquired 动,其余三项必须仍是 0 */
fakes_reset();
(void)vol_lock_init();
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-015 try acquire succeeds");
vol_lock_get_stats(&st);
CHECK((st.acquired == 1u) && (st.try_timeout == 0u) && (st.blk_timeout == 0u)
&& (st.failopen == 0u),
"TC-VOLLOCK-015b acquired only: a successful lock never inflates any anomaly counter");
/* 016 try 超时态:timeout=0 的 try 失败 -> 只进 try_timeout */
fakes_reset();
(void)vol_lock_init();
s_wait_ok = 0;
CHECK(vol_lock_wait(0U) == 0, "TC-VOLLOCK-016 try on a held volume returns 0");
vol_lock_get_stats(&st);
CHECK((st.try_timeout == 1u) && (st.blk_timeout == 0u) && (st.acquired == 0u),
"TC-VOLLOCK-016b timeout=0 miss counts as try_timeout and NOT as blk_timeout (feed point never waited)");
/* 017 分片退让态:timeout>0 的分片等待超时 -> 只进 blk_timeout(与 try 超时态分道) */
fakes_reset();
(void)vol_lock_init();
s_wait_ok = 0;
CHECK(vol_lock_wait(100U) == 0, "TC-VOLLOCK-017 100ms slice on a held volume returns 0");
vol_lock_get_stats(&st);
CHECK((st.blk_timeout == 1u) && (st.try_timeout == 0u),
"TC-VOLLOCK-017b slice timeout counts as blk_timeout and NOT as try_timeout (waited-but-missed is a different fact)");
/* 018 零互斥态(本组核心):锁不存在时 wait 仍返 1(TC-VOLLOCK-008 的口径),但现在必须留下
* 可查证据 —— 这一态此前被谎报成"取到锁",整机没有任何地方能发现它 */
fakes_reset();
s_new_fail = 1;
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-018 fail-open still returns 1 (unchanged contract, TC-VOLLOCK-008)");
vol_lock_get_stats(&st);
CHECK((st.failopen == 1u) && (st.acquired == 0u),
"TC-VOLLOCK-018b fail-open is now provable: failopen counts up while acquired stays 0 (zero mutual exclusion)");
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-018c second fail-open call also returns 1");
vol_lock_get_stats(&st);
CHECK(st.failopen == 2u, "TC-VOLLOCK-018d fail-open accumulates (consumer latches on the delta, not on the event)");
/* 019 观测口自身:NULL 无害、读取不消耗证据、逐字段稳定 */
vol_lock_get_stats(NULL);
CHECK(1, "TC-VOLLOCK-019 get_stats(NULL) is harmless (a defensive caller must never crash the feed point)");
vol_lock_get_stats(&st);
CHECK((st.failopen == 2u) && (st.try_timeout == 0u) && (st.blk_timeout == 0u)
&& (st.acquired == 0u),
"TC-VOLLOCK-019b reading does not consume the evidence: counters survive repeated reads");
}
/* ===================== main ===================== */
int main(void) {
printf("=== Volume Lock Tests ===\n");
printf("-- vol_lock_init --\n");
tc_init();
printf("-- vol_lock_wait (incl. fail-open) --\n");
tc_wait();
printf("-- vol_lock_release --\n");
tc_release();
printf("-- observability counters (task-17) --\n");
tc_stats();
printf("\n=== Volume Lock Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}