794906cdc8
P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界):
net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片)
sys_log_mcu 75 条 / 310-313=99.0%、12-12
console_log 104 条 / 188-189=99.5%、15-15
cfg_file_store_mcu 34 条 / 97-97=100%、10-10
soe_store 26 条 / 78-79=98.7%、4-4
合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。
账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29,
恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母
4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件
全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对:
下降 0 个文件。
基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只
定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED
可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开
一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正
两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04),
另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。
存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、
nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。
编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27,
先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK;
id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。
文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、
12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。
本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。
10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、
net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示
全部修复,另开一轮(含测试字面量统一纯 ASCII)。
gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers
Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱);
mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0
(纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。
150 lines
9.1 KiB
C
150 lines
9.1 KiB
C
/*
|
||
* 模块名称:假 FatFs 内存卷(存储域主机侧单测共享件)
|
||
* 模块功能:给 App/log 与 App/cfg 的主机侧单测一个可脚本化的假文件系统——
|
||
* 类型与常量取自**真实 Lib/FatFs/ff.h**(FRESULT / FIL / FILINFO / FA_*
|
||
* 一字不改),本件在 fatfs_fake.c 里给出九个 f_* 原型的 RAM 实现:
|
||
* f_open / f_read / f_write / f_lseek / f_close / f_stat / f_unlink /
|
||
* f_mkdir / f_rename。
|
||
*
|
||
* 仅供 test/host/** 使用:不参与 MCU 构建,也不参与 simulator 构建。
|
||
*
|
||
* 为什么共享一份(照 test/host/common/ch395f_fake.{h,c} 的先例):
|
||
* sys_log_mcu / soe_store / cfg_file_store_mcu 三块的成败**全部**由
|
||
* 「这次 f_open 拿到什么码、f_write 写回多少字节」决定。三份假件各写一遍,
|
||
* 漂移 = 「假件与被测代码同时错而无人报错」。签名与真实头同源 → 真实 API 一改
|
||
* 就编译期报错(同 test/host/nand_port 的「真头 + 假 diskio」路子)。
|
||
*
|
||
* —— 必须复刻的三条本仓实证语义(docs/架构设计/STM32F4-Base存储架构说明.md §10.1)——
|
||
* 1) FA_OPEN_APPEND(0x30) **不含 FA_WRITE(0x02)**:本移植 FA_OPEN_APPEND =
|
||
* FA_OPEN_ALWAYS(0x10) | FA_SEEKEND(0x20),而 FA_SEEKEND 只定义在 ff.c:74、
|
||
* ff.h 里没有。所以只给 FA_OPEN_APPEND 打开的文件**能打开、能创建**,但
|
||
* f_write 恒返 FR_DENIED(7)——2026-09-20 台架「/log 写失败 x5/开机」的真根因
|
||
* (§10.1 第 11 条)。假件按真实 ff.c 一样把 mode 原值存进 fp->flag 再判
|
||
* flag & FA_WRITE,不做任何「便利放行」。
|
||
* 2) f_write 簇分配失败 = **FR_OK + 短写**(无错误码,判失败只能校验写回字节数):
|
||
* 真实 ff.c:4102 的 if (clst == 0) break; → 循环带着已写计数退出 → FR_OK。
|
||
* 假件把「每文件固定后备簇池」当介质:写到装不下就返回 FR_OK + 截断字节数;
|
||
* 另给定点注入,让「恰好短一字节」这种真机要撞坏块才有的形态可复现。
|
||
* 3) FF_USE_LFN=0 → **严格 8.3 短名**:主名 <=8、扩展 <=3、非法字符集
|
||
* * + ; < = > [ ] | 与 0x7F(ff.c create_name 非 LFN 分支逐字模拟)。
|
||
* 「.fschk」的真实死法 = 开头那个点把游标直接推进扩展域、随后 5 个字符撑爆
|
||
* 扩展域上限 3 → FR_INVALID_NAME(6);路径里任何位置出现 ':' 被 get_ldnumber
|
||
* 当盘符前缀吃下 → FR_INVALID_DRIVE(11)。两者都是**纯 RAM 里的名字判定、
|
||
* 不碰盘**(§10.1 第 10 条,含「测试用例须同时接受这两个码」的口径)。
|
||
*
|
||
* —— 其余与真实 ff.c 对齐的路径/错误码细则 ——
|
||
* - 前导分隔符被剥掉、恒从卷根开始(FF_FS_RPATH=0,见 ff.c follow_path);
|
||
* - 中间目录段不存在 → FR_NO_PATH;末段不存在且未给创建位 → FR_NO_FILE;
|
||
* - 对目录本身 f_open(无创建位)→ FR_NO_FILE;
|
||
* - FA_CREATE_NEW 撞上已存在 → FR_EXIST;FA_CREATE_ALWAYS → 截零 + 刷新时间戳;
|
||
* - 路径剥完分隔符为空 → FR_INVALID_NAME(ff.c create_name 的 i==0 分支);
|
||
* - f_stat:父目录在而对象不在 → FR_NO_FILE;父目录也不在 → FR_NO_PATH;
|
||
* - f_unlink 不存在 → FR_NO_FILE;f_mkdir 已存在 → FR_EXIST;
|
||
* - **f_rename 撞已存在的新名 → FR_EXIST、不覆盖**(ff.c「new name already in
|
||
* use」,2026-09-22 台架 E-01 暴露的那条:缺了它,一切「目标存在的覆盖写」
|
||
* 必然失败,而模拟器端 POSIX rename 的覆盖语义照不出来)。不复刻本条 =
|
||
* cfg_store_write_atomic 里「先 unlink 再 rename」那一步永远测不出必要性;
|
||
* - f_lseek 越过文件尾:只读句柄**钳到 objsize 且不报错**(ff.c 正常寻址分支),
|
||
* 可写句柄才扩展文件;
|
||
* - f_open 失败即作废句柄(ff.c「fp->obj.fs = 0」)→ 后续 f_write 拿
|
||
* FR_INVALID_OBJECT,不会拿着脏句柄继续写;
|
||
* - f_close 成功即释放句柄,并把打开期间的变更落成新的 fdate/ftime
|
||
* (真实 ff.c 在 close 里用 get_fattime 回写目录项)。
|
||
*
|
||
* —— 脚本化模型(沿用本仓既有假件风格:nand_port 的 RAM 假芯片 / lftpd_io 的假堆)——
|
||
* - 注入:按路径 + op 预置「接下来 N 次返回某码 / 只写回 n 字节 / 只读回 n 字节」;
|
||
* path 传 NULL = 不限路径;
|
||
* - 观测:逐 op 调用计数 + 最近一次入参(路径、mode、btw/bw、btr/br、lseek 偏移),
|
||
* 让「这轮压根不该碰盘」与「打开模式必须带写位」成为可断言的事;
|
||
* - 卷:条目直读直写(ff_fake_mkfile / ff_fake_mkfile_size / ff_fake_fetch)用来
|
||
* 铺前置状态——「run.log 已经有 1024 条记录」不可能靠真写 65536 字节铺出来;
|
||
* - 假时钟:ff_fake_set_dostime 控制 fdate/ftime,FAT 分钟粒度导致
|
||
* 「同分钟内同长度重写不可检出」这件事只有它能测(cfg_store_stat 的 mtime 令牌);
|
||
* - ff_fake_reset() 一次全清(卷 + 注入 + 观测 + 时钟)。
|
||
*
|
||
* 有意**不提供** f_mount / f_mkfs / f_opendir / f_readdir:三个被测模块一次都不该
|
||
* 调它们,真调了就是链接期报错——比运行时断言更硬,也不会静默通过。
|
||
*
|
||
* 创建日期:2026-10-24(P2 主机单测覆盖率批次,task-2)
|
||
*/
|
||
|
||
#ifndef FATFS_FAKE_H
|
||
#define FATFS_FAKE_H
|
||
|
||
#include <stdint.h>
|
||
|
||
/* 真实头在 Lib/FatFs/;本文件不重定义任何 FatFs 类型或宏 */
|
||
#include "ff.h"
|
||
|
||
#define FF_FAKE_MAX_ENT 16u /* 卷内条目上限(文件 + 目录) */
|
||
#define FF_FAKE_PATH_MAX 24u /* 归一化后路径上限("/log/selftest.log" = 17) */
|
||
/* 每文件的后备簇池:必须**大于生产侧单文件容量上限**(run.log / soe.log
|
||
* = 1024 条 x 64 B = 65536 B,见 sys_log.h:SYSLOG_RUN_MAX 与 soe_store.h:
|
||
* SOE_STORE_MAX),否则测"容量满 unlink 重建"时追加位落在池外,f_write 恒
|
||
* 返回 0 字节短写,轮转场景根本跑不到。写满仍然如实返回 FR_OK + 短写
|
||
*(真实 ff.c 簇分配失败的同一形态)。16 条目 x 66 KB ≈ 1 MB 静态,主机侧无压力。 */
|
||
#define FF_FAKE_CLUSTER 67584u
|
||
#define FF_FAKE_INJ_MAX 8u /* 注入槽位数 */
|
||
#define FF_FAKE_GUARD 50000 /* 跑飞保险:单个 f_* 调用数超此值直接 exit(2) */
|
||
|
||
/* 注入与计数的操作编号(fatfs_fake.c 内部同一套) */
|
||
enum {
|
||
FF_FAKE_OP_OPEN = 0,
|
||
FF_FAKE_OP_READ,
|
||
FF_FAKE_OP_WRITE,
|
||
FF_FAKE_OP_LSEEK,
|
||
FF_FAKE_OP_CLOSE,
|
||
FF_FAKE_OP_STAT,
|
||
FF_FAKE_OP_UNLINK,
|
||
FF_FAKE_OP_MKDIR,
|
||
FF_FAKE_OP_RENAME,
|
||
FF_FAKE_OP_N
|
||
};
|
||
|
||
/* 注入动作 */
|
||
enum {
|
||
FF_FAKE_ACT_RC = 0, /* 直接返回指定 FRESULT,不做任何数据搬运 */
|
||
FF_FAKE_ACT_WSHORT, /* 仅 f_write:FR_OK 但只写回 n 字节 */
|
||
FF_FAKE_ACT_RSHORT /* 仅 f_read :FR_OK 但只读回 n 字节 */
|
||
};
|
||
|
||
typedef struct {
|
||
int calls[FF_FAKE_OP_N]; /* 逐 op 调用次数(含失败的调用) */
|
||
BYTE last_open_mode; /* 最近一次 f_open 的 mode 原值(断言 FA_WRITE 位) */
|
||
char last_open_path[FF_FAKE_PATH_MAX];
|
||
char last_unlink_path[FF_FAKE_PATH_MAX];
|
||
char last_mkdir_path[FF_FAKE_PATH_MAX];
|
||
char last_rename_old[FF_FAKE_PATH_MAX];
|
||
char last_rename_new[FF_FAKE_PATH_MAX];
|
||
UINT last_write_btw; /* 请求写的字节数 */
|
||
UINT last_write_bw; /* 实际写回的字节数(短写的断言面) */
|
||
UINT last_read_btr;
|
||
UINT last_read_br;
|
||
FSIZE_t last_lseek_ofs;
|
||
int last_open_rc; /* 最近一次 f_open 的返回码 */
|
||
int last_write_rc; /* 最近一次 f_write 的返回码 */
|
||
} ff_fake_obs_t;
|
||
|
||
extern ff_fake_obs_t g_ff_obs;
|
||
|
||
/* —— 世界控制 —— */
|
||
void ff_fake_reset(void); /* 卷 + 注入 + 观测 + 时钟一次全清 */
|
||
void ff_fake_set_dostime(uint16_t fdate, uint16_t ftime); /* 假 get_fattime() 的两个域 */
|
||
|
||
/* —— 故障注入(times = 生效次数;path = NULL 表示不限路径)—— */
|
||
void ff_fake_inject_rc(const char *path, int op, FRESULT rc, int times);
|
||
void ff_fake_inject_write_short(const char *path, UINT n, int times);
|
||
void ff_fake_inject_read_short(const char *path, UINT n, int times);
|
||
|
||
/* —— 卷内容直读直写(绕开打开语义,用来铺前置状态)—— */
|
||
int ff_fake_exists(const char *path); /* 0 无 / 1 文件 / 2 目录 */
|
||
uint32_t ff_fake_size(const char *path); /* 无此文件返回 0xFFFFFFFF */
|
||
uint32_t ff_fake_time_token(const char *path); /* (fdate<<16)|ftime,同 cfg_store_stat */
|
||
uint32_t ff_fake_fetch(const char *path, uint8_t *p_buf, uint32_t off, uint32_t cap);
|
||
int ff_fake_mkfile(const char *path, const uint8_t *p_data, uint32_t len);
|
||
int ff_fake_mkfile_size(const char *path, uint32_t size); /* 只给逻辑长度(造容量满) */
|
||
int ff_fake_mkdir_direct(const char *path); /* 无条件建目录 */
|
||
int ff_fake_count_entries(void); /* 卷内条目数(含目录) */
|
||
|
||
#endif /* FATFS_FAKE_H */
|