feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐

UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机
OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批
预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block
(口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng
反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定
内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按
is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。

记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在
defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE);
事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身
(一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。
为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核,
丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。

为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费
两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过;
RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同
码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。

实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B;
RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS,
protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→
164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34,
既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、
不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页
连跑两次逐字节一致;字库 diff 空。

顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽
16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前
靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值
全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py
模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。

未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行;
LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND),
已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
2026-10-26 15:51:12 +08:00
parent 0f7b5a87fb
commit 77779d49d6
55 changed files with 4236 additions and 289 deletions
+11 -2
View File
@@ -7,7 +7,12 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运
App/log/sys_log_mcu.c,并纳入共享假卷 test/host/common/fatfs_fake.c),在 PC 上离线
跑 64B 定长记录格式、单条/批量两条写路径、syslog_drain 的 preboot+队列排空与
"失败回投不丢条目"、日志读写放大收口的 8 条滑动读窗口 + 写者代数失效、挂载幂等与
上电/重启分类、sys_log_evidence 的 wdev.log 取证通道,失败即非 0 退出。
上电/重启分类、sys_log_evidence 的 wdev.log 取证通道、「清除记录」的请求/落地分离
与四条判据(请求侧零盘触点、drain 内恰好一次 unlink、删后重建可继续写、
FR_NO_FILE 幂等返 0、卷占用/真失败返 -1 且不误删原文件)、以及七类记录(道F
task-10 新增 accid/alarm/start/oper/yk 五类)的存储面——逐 id 验"哪类落哪个
文件、各用各的容量上限、名字过严格 8.3",以及 quiet 投递通道的丢弃计数与
"投递侧零留痕",失败即非 0 退出。
工具链与判定委托 test/host/common/unit_test_harness.py。
为什么要主机侧单测(而不是只用台架):
@@ -20,7 +25,11 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运
3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检
全部滞留),改成幂等 + 重试后又必须防"每 2s 刷一条同样错误" → 要注入 mkdir
失败才测得到,且必须能数出"到底留痕几次"。
4. **读窗口 try 语义 / 日志读写放大收口的读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面,
4. **「清除记录」的请求 / 落地分离(2026-10-26 加)**:在 UI 按键回调里直接 f_unlink
也能删掉文件、台架当场看不出毛病,代价是把 FAT 目录项回写挂进 uiTask 的
100 ms 喂狗节拍、并与唯一写者自己的"容量满 unlink 重建"交错。这条边界只能靠
数调用钉住:请求侧零 FatFs / 零卷锁、删除只在 syslog_drain 内且恰好一次 unlink。
5. **读窗口 try 语义 / 日志读写放大收口的读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面,
只做前者会把陈旧读当正确、只做后者会退回逐条 open/seek/close 的旧实现。
桩的边界:ff.h / sys_log.h / soe_store.h / sys_clock.h / vol_lock.h 用**真实头**;
+576 -10
View File
@@ -8,7 +8,8 @@
* 失败回投不丢条目、-3 退回慢路径)
* D 读侧(日志读写放大收口的 8 条滑动窗口 + 写者代数失效 + 越界/忙/短读处置)
* 外加 syslog_mcu_mounted 的 挂载幂等与上电/重启分类、sys_log_evidence
* 的 wdev.log 取证通道(8KB 护栏 + 第二条写必须在释放卷锁之后)。
* 的 wdev.log 取证通道(8KB 护栏 + 第二条写必须在释放卷锁之后),
* 以及「清除记录」的请求 / 落地分离与四条判据(K 族,2026-10-26)。
*
* 被测对象:真实源文件 App/log/sys_log_mcu.c(#include 原样纳入,不改生产代码)。
* 假件与桩:
@@ -28,6 +29,20 @@
* 4. **读窗口 try 语义 / 日志读写放大收口的读侧**:读窗口"命中即零 I/O"与"写者一落地就必须失效重填"是同一
* 枚硬币的两面——只做前者会把陈旧读当正确,只做后者会退化成逐条 open/seek/close
* 的旧实现(UI 翻 20 条 = 20 次 FAT 往返)。两者都靠假卷的调用计数才断言得了。
* 5. **「清除记录」的请求 / 落地分离**(2026-10-26 加,K 族):最顺手的写法是在 UI
* 按键回调里直接 f_unlink —— 文件确实删了,台架当场也看不出毛病,代价是把 FAT
* 目录项回写挂进 uiTask 每 100 ms 一次的喂狗节拍,并与唯一写者自己的"容量满
* unlink 重建"交错。这条"谁在什么上下文碰盘"的边界,只有离线数调用才钉得住:
* 请求侧必须零 FatFs / 零卷锁(TC-SYSLOG-110),删除必须恰好一次 unlink 且
* 发生在排空之后(TC-SYSLOG-111/117)。真机上这条写错的表现为偶发丢一段日志
* 或一次误复位,串口里没有任何一行会指着这处代码。
* 6. **一张表的下标序 = 枚举序**(2026-10-26 加,L 族):记录类型就是"哪个文件",
* 于是 s_paths[]/s_caps[] 与 syslog_id_t 的**逐项同序**成了正确性本身。少写一项
* 编译期就报,写错序却什么都不报错——事故记录会安静地写进告警文件,屏上一切
* 正常,只有查记录的人被误导。这类"顺序即语义"的耦合只能靠逐 id 断言"我投的
* 这句出现在哪个路径、别的文件里没有"来钉(TC-SYSLOG-130),容量同理(131)。
* quiet 投递通道也在这一族:它存在的唯一理由是"投递侧不许带日志行缓冲",
* 所以必须断言**丢弃时零留痕**(132/135),否则退化回 syslog_post 也没人发现。
*
* 覆盖点(TC 号族 SYSLOG,形如 TC-SYSLOG-001,族内按功能分档;缺口在小节末尾如实登记):
* —— A 记录格式 ——
@@ -129,11 +144,69 @@
* TC-SYSLOG-081 生产路径全过严格 8.3:/log /run.log /selftest.log /wdev.log /soe.log
* TC-SYSLOG-082 FR_INVALID_NAME 反复失败 20 轮:unlink 次数有界、/log 目录与
* 邻居文件一根毛没动(自愈只针对自己的文件)
* —— K 记录删除(「清除记录」前置,2026-10-26)——
* TC-SYSLOG-110 请求侧只登记:零卷锁、零 FatFs、记录还在盘上(uiTask 唯一被
* 允许做的事就是发这个请求)
* TC-SYSLOG-111 落地在 syslog_drain(defaultTask)内:恰好一次 unlink、零 open、
* count 归零、卷锁配对、邻居文件不动、零留痕
* TC-SYSLOG-112 删后重建可继续写:清除后下一条记录把文件建回来、打开模式带写位、
* 64/64 写全、读侧从 idx 0 读到新内容
* TC-SYSLOG-113 FR_NO_FILE 幂等返 0:无留痕、卷锁配对、读侧代数照常自增
* TC-SYSLOG-114 卷占用返 -1:零盘触点、不 release(没拿到锁)、不计故障、原文件一字节不少
* TC-SYSLOG-115 请求路径遇卷忙 = 静默顺延(WAIT 不是 FAIL、请求挂回、零留痕),
* 下一轮卷空闲自己落地(用户不必重按)
* TC-SYSLOG-116 unlink 真失败 -> -1 + 留痕一次 + 文件完好;请求侧判 FAIL 且
* **绝不自动重试**(反复删自己的日志 = 写侧刚修掉的坑,别再造一遍)
* TC-SYSLOG-117 drain 内顺序本身也是判据:本轮排空出来的旧记录一起被清掉,
* 删除之后新投的条目仍落盘(清除不等于把日志关掉)
* TC-SYSLOG-118 同 id 重复请求合并为一次 unlink(请求是标志,不是计数器)
* TC-SYSLOG-119 只删自己:清 RUN 不碰 SELFTEST,也不碰取证文件 wdev.log
* TC-SYSLOG-120 三处入口的入参守卫都在取锁之前;坏 id 不污染合法 id 的状态
* TC-SYSLOG-121 删除成功 -> 读侧代数自增(屏上"已清除"而窗口还装着旧记录 = 错数据);
* 删除失败 -> 代数不变、缓存记录仍可读(盘上什么都没动)
* TC-SYSLOG-122 /log 目录不在 -> FR_NO_PATH **不算**幂等成功:-1 + 一次留痕 +
* 零 f_mkdir(名字/参数类码不得升级成卷级动作);目录回来后 FAIL 不粘
*
* 覆盖率台账(--module sys_log_mcu 自量):App/log/sys_log_mcu.c 310/313 行 =
* 99.0%、函数 12/12。未覆盖 3 行:L628(下面第三条死分支)、L727~728
* (sys_log_evidence 里 snprintf 返回 <=0 的分支——宿主 snprintf 不可能失败,
* 假件也不伪造返回值;为刷数字去伪造等于把断言写在假件规则上,宁可留空)。
* —— L 五类记录与 quiet 投递通道(道F task-10,2026-10-26)——
* TC-SYSLOG-130 七类记录各进各的文件、各带各的描述:**id -> 文件的表序就是语义**
* TC-SYSLOG-130b 卷里恰好七个记录文件,没有第八个拼错名字被顺手建出来的
* TC-SYSLOG-130c 七条生产路径逐条过严格 8.3(主名<=8 / 扩展=3 / 不以点开头)
* TC-SYSLOG-131 accid 到自己那格(512)轮转,邻居 alarm 一字节没动
* TC-SYSLOG-131b yk 用的是自己那格(256)——三档上限互不相同,能照出 s_caps[] 串格
* TC-SYSLOG-132 quiet 通道队列满:计数前进、投递侧**零留痕**、drain 侧按差值报一次
* TC-SYSLOG-133 清除记录 = 一次运维动作 -> 落一条操作记录点名被清的那份;被清那份仍归零
* TC-SYSLOG-133b 清 oper.log 自己时**不投**操作记录(否则刚删空又变 1 条,砸掉归零判据)
* TC-SYSLOG-134 quiet 的入参守卫:坏 id / NULL 既不投也不计丢弃(参数错 != 容量故障)
* TC-SYSLOG-135 启动前数组满同样计数、同样静默(另一条腿,与 F 项的两个通道分区一致)
* TC-SYSLOG-136 同内容在七类之间**逐字节相同**:记录体不编码类型 = "文件即类型"的机器表达
*
* 覆盖率台账(`scripts/check_test_coverage.py --module sys_log_mcu` 本机实跑):
* App/log/sys_log_mcu.c **403/406 行 = 99.3%、函数 22/22**。
* 上一轮(K 族)是 377/380 行、函数 18/18 —— L 族新增 4 枚函数
* (s_entry_fill / s_put / syslog_post_quiet / syslog_post_dropped)与 26 行
* 可执行代码**全部**有断言跑到:覆盖行 +26、函数 +4,未覆盖仍是同一批 3 行老账
* (一条死分支 + sys_log_evidence 里 snprintf<=0),**一条没涨**。
* ⚠ 全量门禁(40 套并集 + 基线只增不减)由提交前那一次统一跑,本行只是自量记录。
* L 族变异实测(2026-10-26,三处同时注入后逐字节还原):
* ① s_paths[] 里 accid 与 alarm 两格**对调**(就是"表序错"那个静默错法);
* ② 给 quiet 的丢弃分支补一条 `DBG_ERROR`(= 悄悄退化成 syslog_post);
* ③ 去掉"清 oper.log 自己不投操作记录"那半枚守卫。
* → 137 掉到 132:**TC-SYSLOG-130 / 131**(①)、**132 / 135**(②)、
* **133b**(③)各自转红且互不牵连——每条新纪律都被单独钉住,不是靠一锅大断言。
* App/log/sys_log_mcu.c **377/380 行 = 99.2%、函数 18/18**。
* 本轮(K 族)之前是 328/330 行、函数 13/13 —— 新增 5 个函数
* (s_clear_try / s_clear_service / syslog_clear_apply / syslog_request_clear /
* syslog_clear_state)与 50 行新增可执行代码(已覆盖 +49):新增行**全部**有断言
* 跑到,未覆盖仍是 3 行,一条没涨。
* 仍未覆盖的 3 行是同一批老账:一条死分支 + sys_log_evidence 里 snprintf 返回
* <=0 的分支——宿主 snprintf 不可能失败,假件也不伪造返回值;为刷数字去伪造
* 等于把断言写在假件规则上,宁可留空。
* 变异实测("断言有牙齿"的证据,2026-10-26,改完已逐字节还原):
* ① 在排空循环**之前**多调一次 `s_clear_service()`(净效果 = 删除抢在条目落盘之前
* 执行,尾部那次因请求已销账而空转);② 把 `FR_NO_PATH` 也算成幂等成功。
* 两处一起改 → TC-SYSLOG-115 / 115b / 117 / 117b / 122 **五条转红**(126 → 121),
* 还原后复绿 126/126。也就是说"删除必须排在条目排空之后落地"与"名字/参数类码
* 不算幂等成功"这两条本来只写在注释里的纪律,现在真的会被测试拦住。
*
* 本套照出的四条现状 —— 已在 task-4(2026-10-24)全部处置,相应断言已翻成正向:
*
@@ -172,6 +245,18 @@
* (TC-SOESTORE-022 / 022b),两侧的差异是刻意的,注释也互指。
*
* 故意没测(写在这里,不留"看起来全覆盖"的错觉):
* - 真机上 uiTask 发请求与 defaultTask 落地的并发:主机单线程编不出真竞态,本套只
* 钉"请求侧零盘触点 + 落地只在 drain 里"这两条形状;两个 volatile 单字节交接的
* 最坏竞态(读到上一次的结果 / 失败被新请求清掉)写在 sys_log.h 的函数头里。
* - quiet 通道与 post 通道在**同一轮**里交错时的抑制位归属(quiet 成功不复位 F 项
* 那两个位):这是刻意的设计(那两个位描述 post 自己的历史),TC-SYSLOG-107c 已
* 钉住两位互不干扰;"quiet 投成功把 post 的故障静默位清了"这种错向,本套没有
* 单独一条——要钉它得先给两个通道各造一套满态时序,收益不抵这套的复杂度。
* - 五类记录各自的**业务触发时序**(哪个元件边沿算事故、哪条 104 命令算操作)不在
* 本套:那是判据与协议侧的事,分别由 test/host/prot_elm 的 TC-PROTELEM-015 与
* test/host/iec104 的 TC-IEC104-030 钉;本套只管"投进来以后存储对不对"。
* - 删除之后"下一次上电会不会多写一条 power on":那是 syslog_mcu_mounted() 的
* 上电分类逻辑(E 族已测),与删除路径无关,两边各测各的。
* - 真跨任务并发:读窗口用"对齐 32 位读写在 ARM 上原子"免锁,主机 x64 + 单线程
* 编不出真竞态;本套只断言单写者语义与代数失效逻辑本身。
* - f_close 内部 sync_fs -> disk_ioctl(CTRL_SYNC) -> ftl_flush_cache() 的掉电落盘
@@ -218,6 +303,12 @@ static int s_failed;
#define P_RUN "/log/run.log"
#define P_SELFTEST "/log/selftest.log"
#define P_WDEV "/log/wdev.log"
/* 五类新记录(task-10,L 族):生产用的就是这七条路径,改名必须同步这里 */
#define P_ACCID "/log/accid.log"
#define P_ALARM "/log/alarm.log"
#define P_START "/log/start.log"
#define P_OPER "/log/oper.log"
#define P_YK "/log/yk.log"
/* ===================== 假件与生产源码纳入 ===================== */
@@ -494,18 +585,28 @@ static void reset_all(void)
s_queue = NULL;
s_fs_ready = 0u;
s_mount_fail_reported = 0u;
/* B / F 项整改新增的留痕节流位:跨场景必须显式清(否则第二个故障场景看不到留痕) */
s_requeue_reported[SYSLOG_RUN] = 0u;
s_requeue_reported[SYSLOG_SELFTEST] = 0u;
/* B / F 项整改新增的留痕节流位:跨场景必须显式清(否则第二个故障场景看不到留痕)。
* ⚠ 按 SYSLOG_COUNT 循环清,不要逐 id 手写——task-10 把 id 从 2 扩到 7 之后,
* 任何"逐枚点名"的清理都会静默漏掉新增的那几格,症状是"单独跑绿、连跑偶红"。 */
(void)memset(s_requeue_reported, 0, sizeof(s_requeue_reported));
s_post_qfull_reported = 0u;
s_post_pboot_full_reported = 0u;
/* quiet 投递通道(L 族):丢弃计数与 drain 侧已上报水位都要归零,
* 否则上一个场景的丢弃会让本场景第一条留痕被当成"已经报过" */
s_quiet_drop = 0u;
s_quiet_reported = 0u;
(void)memset(s_rd_win, 0, sizeof(s_rd_win));
s_rd_win_id = -1;
s_rd_win_base = 0u;
s_rd_win_cnt = 0u;
s_rd_win_gen = 0u;
s_rd_gen[SYSLOG_RUN] = 0u;
s_rd_gen[SYSLOG_SELFTEST] = 0u;
(void)memset(s_rd_gen, 0, sizeof(s_rd_gen)); /* 同上:按数组清,扩 id 不会漏 */
/* 「清除记录」的请求 / 结果交接(本套新增,K 族):两枚都必须显式清,
* 否则上一个场景留下的 WAIT/FAIL 会让下一个场景第一眼就读到错状态 */
/* 「清除记录」的请求 / 结果交接(K 族):两枚按数组清,理由同 s_rd_gen——
* 五类新记录每一类都可被清除,漏清一格就是上一场景的 WAIT/FAIL 咬下一场景。 */
(void)memset(s_clear_req, 0, sizeof(s_clear_req));
(void)memset(s_clear_st, 0, sizeof(s_clear_st)); /* 0 == SYSLOG_CLEAR_NONE */
/* 卷状态:/log 由 defaultTask 挂载时建好;本套多数场景直接假定已建,
* "没建 -> 补建" 那条路径单独用 TC-SYSLOG-060/061 测 */
(void)ff_fake_mkdir_direct("/log");
@@ -1838,6 +1939,469 @@ static void tc_post_throttle(void)
"TC-SYSLOG-107c the two channels keep independent throttle bits");
}
/* ===================== K 记录删除(「清除记录」前置) ===================== */
/*
* 这一族钉的是"谁在什么上下文把文件删掉",加上删除本身的四条判据:
* 计数归零(111)、文件不在时幂等返 0(113)、卷占用/真失败返 -1 且不误删原文件
* (114/116)、删后重建可继续写(112)。
* 为什么值得离线测:最顺手的写法是在 UI 按键回调里直接 f_unlink —— 台架上当场看不出
* 毛病,代价是把 FAT 目录项回写挂进 uiTask 每 100 ms 一次的喂狗节拍,并与唯一写者
* 自己的"容量满 unlink 重建"交错。请求/落地分开之后,这条边界只能靠"数请求侧的盘
* 触点"和"数删除发生在哪一轮"钉住(110/111/117 干的就是这件事)。
* 一处限度说清楚:本目录的卷锁假件不记录超时入参,所以"绝不阻塞"只能断言成
* "try 失败 = 零盘触点 + 不 release + 不计故障"(114),测不到传进去的超时值到底
* 是不是 0;要测那一层得给共用的卷锁假件加一列观测,不属本套的范围。
*/
static void tc_clear(void)
{
char ts[SYSLOG_TS_LEN + 1u];
char desc[SYSLOG_DESC_MAX + 1u];
uint8_t rec[SYSLOG_REC_SIZE];
uint32_t i;
int ok;
printf("-- TC-SYSLOG-110..121 record clear (request / land split) --\n");
/* 110 请求侧:登记就是登记 —— 一把锁都不取、一个 FatFs 调用都没有、记录还在盘上。
* 这是"UI 只能发请求"的全部技术含义:按键回调在 uiTask,那侧不许碰存储。 */
reset_all();
ready_only();
(void)s_append(SYSLOG_RUN, "old1");
(void)s_append(SYSLOG_RUN, "old2");
{
int open_base = g_ff_obs.calls[FF_FAKE_OP_OPEN];
int unlink_base = g_ff_obs.calls[FF_FAKE_OP_UNLINK];
int lock_base = s_lock_waits;
int rel_base = s_lock_releases;
ok = (syslog_request_clear(SYSLOG_RUN) == 0)
&& (syslog_count(SYSLOG_RUN) == 2u)
&& (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_WAIT)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == open_base)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == unlink_base)
&& (s_lock_waits == lock_base)
&& (s_lock_releases == rel_base);
CHECK(ok, "TC-SYSLOG-110 request only books the ask: zero volume lock, zero FatFs, records still on the volume");
}
/* 111 落地发生在 drain(defaultTask)里:恰好一次 unlink(不 open)、计数归零、
* 锁成对、零留痕,且邻居文件 selftest.log 一字节没动 */
(void)s_append(SYSLOG_SELFTEST, "neighbour");
{
int open_base = g_ff_obs.calls[FF_FAKE_OP_OPEN];
syslog_drain();
ok = (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_OK)
&& (syslog_count(SYSLOG_RUN) == 0u)
&& (ff_fake_exists(P_RUN) == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (strcmp(g_ff_obs.last_unlink_path, "/LOG/RUN.LOG") == 0) /* 观测位存的是归一化后的 8.3 大写名 */
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == open_base)
&& (s_lock_seq_err == 0)
&& (g_sys_err_count == 0)
&& (ff_fake_exists(P_SELFTEST) == 1)
&& (ff_fake_size(P_SELFTEST) == SYSLOG_REC_SIZE);
}
CHECK(ok, "TC-SYSLOG-111 drain lands it: one unlink and zero open, count 0, lock paired, no trace, neighbour intact");
/* 112 判据"删后重建可继续写":文件被删之后,下一条记录必须把文件建回来并写全。
* 陷阱一在这里才有牙齿 —— 只有"文件不存在"这一条路才走 f_open 的创建分支,
* 少了 FA_WRITE 位,平时写得好好的,清除之后从此每条静默写不进去。 */
ok = (s_append(SYSLOG_RUN, "after-clear") == 0)
&& (ff_fake_exists(P_RUN) == 1)
&& (syslog_count(SYSLOG_RUN) == 1u)
&& (g_ff_obs.last_open_mode == (BYTE)(FA_WRITE | FA_OPEN_APPEND))
&& (g_ff_obs.last_write_bw == SYSLOG_REC_SIZE);
CHECK(ok, "TC-SYSLOG-112 after a clear the next record recreates the file with the write bit set and lands 64/64 bytes");
ok = ok && (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == 0)
&& (strcmp(desc, "after-clear") == 0);
CHECK(ok, "TC-SYSLOG-112b the recreated file is readable from index 0 (cleared history never comes back)");
/* 113 幂等:文件本来就不存在 -> 返 0、只留一次 unlink、零留痕、卷锁照样配对 */
reset_all();
ready_only();
{
int rel_base = s_lock_releases;
uint32_t gen = s_rd_gen[SYSLOG_RUN];
ok = (ff_fake_exists(P_RUN) == 0)
&& (syslog_clear_apply(SYSLOG_RUN) == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_sys_err_count == 0)
&& (s_lock_releases == rel_base + 1)
&& (s_lock_seq_err == 0)
&& (s_rd_gen[SYSLOG_RUN] == gen + 1u);
}
CHECK(ok, "TC-SYSLOG-113 FR_NO_FILE is an idempotent success: 0 back, no trace, lock released, window bumped");
/* 114 卷占用:公开 API 返 -1,一个盘操作都没有、不 release(没拿到锁)、不计故障、
* 原文件一字节不少。"try 失败不许 release"是漏锁 = FTP 永久等不到卷的老账。 */
reset_all();
ready_only();
(void)s_append(SYSLOG_RUN, "keepme");
{
int open_base = g_ff_obs.calls[FF_FAKE_OP_OPEN];
int unlink_base = g_ff_obs.calls[FF_FAKE_OP_UNLINK];
int rel_base = s_lock_releases;
lock_busy_next(1);
ok = (syslog_clear_apply(SYSLOG_RUN) == -1)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == open_base)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == unlink_base)
&& (s_lock_releases == rel_base)
&& (s_lock_seq_err == 0)
&& (g_sys_err_count == 0)
&& (ff_fake_size(P_RUN) == SYSLOG_REC_SIZE)
&& (syslog_count(SYSLOG_RUN) == 1u);
}
CHECK(ok, "TC-SYSLOG-114 busy volume -> -1 with zero disk touch, no release, no fault counted, original file intact");
/* 115 走请求路径时卷占用 = 静默顺延,不是失败:状态仍是 WAIT、请求挂回、零留痕;
* 下一轮卷空闲后同一个请求自己落地(用户不必重按一次按钮)。 */
reset_all();
ready_only();
syslog_drain(); /* 建队列,本轮空转 */
(void)s_append(SYSLOG_RUN, "keepme");
(void)syslog_request_clear(SYSLOG_RUN);
lock_busy_next(1);
g_sys_err_count = 0;
syslog_drain(); /* 这一轮的清除拿锁失败 */
ok = (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_WAIT)
&& (ff_fake_exists(P_RUN) == 1)
&& (g_sys_err_count == 0)
&& (s_lock_seq_err == 0);
CHECK(ok, "TC-SYSLOG-115 busy while landing defers: ask kept, state WAIT (not FAIL), file untouched, silent");
syslog_drain(); /* 卷空闲 -> 落地 */
ok = ok && (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_OK)
&& (syslog_count(SYSLOG_RUN) == 0u);
CHECK(ok, "TC-SYSLOG-115b the deferred ask lands on the next round without a second press");
/* 116 真失败(非 FR_NO_FILE):apply 返 -1 + 留痕一次;请求侧判 FAIL 且绝不自动
* 重试 —— 反复 unlink 自己的日志文件是本项目在写侧刚修掉的坑(名字/参数类
* 错误码同名同因,重试只会每轮删一次历史),删除路径不许再造一遍。 */
reset_all();
ready_only();
(void)s_append(SYSLOG_RUN, "precious");
ff_fake_inject_rc(P_RUN, FF_FAKE_OP_UNLINK, FR_DISK_ERR, 4);
ok = (syslog_clear_apply(SYSLOG_RUN) == -1)
&& (g_sys_err_count == 1)
&& (ff_fake_exists(P_RUN) == 1)
&& (ff_fake_size(P_RUN) == SYSLOG_REC_SIZE);
CHECK(ok, "TC-SYSLOG-116 a real unlink error -> -1, one trace, the log file itself is still there");
reset_all();
ready_only();
syslog_drain();
(void)s_append(SYSLOG_RUN, "precious");
(void)syslog_request_clear(SYSLOG_RUN);
ff_fake_inject_rc(P_RUN, FF_FAKE_OP_UNLINK, FR_DISK_ERR, 4);
syslog_drain();
{
int unlink_base = g_ff_obs.calls[FF_FAKE_OP_UNLINK];
ok = (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_FAIL)
&& (ff_fake_exists(P_RUN) == 1);
syslog_drain(); /* 不许自己再删一次 */
ok = ok && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == unlink_base)
&& (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_FAIL);
}
CHECK(ok, "TC-SYSLOG-116b a failed clear latches FAIL and is never retried on its own (re-press is the retry)");
/* 117 drain 内的顺序是判据的一部分:本轮排空出来的旧记录必须一起被清掉
* (先删后写 = 白清,count 不归零);而删除**之后**投的条目要留下
* (否则"清除记录"变成了"关掉日志")。 */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain(); /* 建队列 */
for (i = 0u; i < 3u; i++) {
syslog_post(SYSLOG_RUN, "old");
}
(void)syslog_request_clear(SYSLOG_RUN);
syslog_drain(); /* 排空 3 条 -> 然后删除 */
ok = (syslog_count(SYSLOG_RUN) == 0u) && (ff_fake_exists(P_RUN) == 0)
&& (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_OK);
CHECK(ok, "TC-SYSLOG-117 the clear runs after the entry drain: the queued backlog does not survive it");
syslog_post(SYSLOG_RUN, "new");
syslog_drain();
ok = ok && (syslog_count(SYSLOG_RUN) == 1u)
&& (raw_at(P_RUN, 0u, rec) != 0)
&& (memcmp(rec + SYSLOG_DESC_OFFS, "new", 3u) == 0);
CHECK(ok, "TC-SYSLOG-117b a record posted after the clear still lands: clearing is not disabling logging");
/* 118 同一 id 连点两次只登记一次(合并成一次删除,不是计数器) */
reset_all();
ready_only();
ok = (syslog_request_clear(SYSLOG_RUN) == 0) && (syslog_request_clear(SYSLOG_RUN) == 0)
&& (s_clear_req[SYSLOG_RUN] == 1u);
syslog_drain();
ok = ok && (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_OK)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (s_clear_req[SYSLOG_RUN] == 0u);
CHECK(ok, "TC-SYSLOG-118 duplicate asks merge into exactly one unlink (the ask is a flag, not a counter)");
/* 119 只删自己:清 RUN 不许碰 SELFTEST,也不许碰取证文件 wdev.log ——
* 三个名字都是严格 8.3 短名,逐个对应,不存在"通配删除"。 */
reset_all();
ready_only();
(void)s_append(SYSLOG_RUN, "r");
(void)s_append(SYSLOG_SELFTEST, "s");
(void)ff_fake_mkfile_size(P_WDEV, 128u);
ok = (syslog_clear_apply(SYSLOG_RUN) == 0)
&& (ff_fake_exists(P_RUN) == 0)
&& (ff_fake_exists(P_SELFTEST) == 1)
&& (ff_fake_exists(P_WDEV) == 1)
&& (strcmp(g_ff_obs.last_unlink_path, "/LOG/RUN.LOG") == 0);
CHECK(ok, "TC-SYSLOG-119 clearing one id never touches the other log or the watchdog evidence file");
/* 120 入参守卫:三处公开入口都必须在取锁之前挡住,并且不污染合法 id 的状态 */
reset_all();
ready_only();
ok = (syslog_request_clear((syslog_id_t)SYSLOG_COUNT) == -1)
&& (syslog_clear_apply((syslog_id_t)SYSLOG_COUNT) == -1)
&& (syslog_clear_state((syslog_id_t)99u) == SYSLOG_CLEAR_NONE)
&& (s_lock_waits == 0)
&& (s_lock_releases == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 0);
CHECK(ok, "TC-SYSLOG-120 out-of-range id is refused before the lock and the disk (a guard behind the lock occupies the volume for nothing)");
reset_all();
ready_only();
ok = (syslog_request_clear(SYSLOG_RUN) == 0)
&& (syslog_request_clear((syslog_id_t)SYSLOG_COUNT) == -1)
&& (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_WAIT)
&& (syslog_clear_state((syslog_id_t)SYSLOG_COUNT) == SYSLOG_CLEAR_NONE);
CHECK(ok, "TC-SYSLOG-120b a rejected request leaves a valid id's pending ask and state untouched");
/* 121 读侧:删除成功必须让读者的 8 条窗口作废(否则屏上显示"已清除"而记录还在);
* 失败路径不许自增 —— 盘上什么都没动,窗口里仍是真数据。 */
reset_all();
ready_only();
for (i = 0u; i < 3u; i++) {
(void)s_append(SYSLOG_RUN, "w");
}
{
uint32_t gen = 0u;
ok = (syslog_get_record(SYSLOG_RUN, 2u, ts, sizeof(ts), desc, sizeof(desc)) == 0);
gen = s_rd_gen[SYSLOG_RUN];
ok = ok && (syslog_clear_apply(SYSLOG_RUN) == 0)
&& (s_rd_gen[SYSLOG_RUN] == gen + 1u)
&& (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == -1);
}
CHECK(ok, "TC-SYSLOG-121 a successful clear invalidates the reader window (no records left to show)");
reset_all();
ready_only();
(void)s_append(SYSLOG_RUN, "keep");
ok = (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == 0);
{
uint32_t gen = s_rd_gen[SYSLOG_RUN];
ff_fake_inject_rc(P_RUN, FF_FAKE_OP_UNLINK, FR_DISK_ERR, 1);
ok = ok && (syslog_clear_apply(SYSLOG_RUN) == -1)
&& (s_rd_gen[SYSLOG_RUN] == gen)
&& (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == 0)
&& (strcmp(desc, "keep") == 0);
}
CHECK(ok, "TC-SYSLOG-121b a failed clear neither bumps the generation nor loses the cached valid record");
/* 122 /log 目录本身不在:假件与真实 ff.c 一样落 FR_NO_PATH。这条刻意**不算**幂等
* 成功(记录读不到是真的,但成因是目录没了而不是"被清空",把卷异常洗成
* "清除成功"是另一种撒谎),而且本函数一个卷级动作都不做:不补目录、
* 不重开、不碰别的对象——补目录归 syslog_mcu_mounted() 每 2s 的重试。 */
reset_all();
ready_only();
ok = (f_unlink("/log") == FR_OK) && (ff_fake_exists("/log") == 0);
{
int mkdir_base = g_ff_obs.calls[FF_FAKE_OP_MKDIR];
ok = ok && (syslog_clear_apply(SYSLOG_RUN) == -1)
&& (g_ff_obs.calls[FF_FAKE_OP_MKDIR] == mkdir_base)
&& (g_ff_obs.calls[FF_FAKE_OP_MKDIR] == 0)
&& (ff_fake_exists("/log") == 0)
&& (g_sys_err_count == 1);
}
CHECK(ok, "TC-SYSLOG-122 FR_NO_PATH is not counted as an idempotent success: -1, one trace, and zero f_mkdir (no volume-level action on a name/param code)");
(void)ff_fake_mkdir_direct("/log"); /* 目录回来了(模拟挂载门补建成功) */
ok = (syslog_request_clear(SYSLOG_RUN) == 0)
&& (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_WAIT);
syslog_drain();
ok = ok && (syslog_clear_state(SYSLOG_RUN) == SYSLOG_CLEAR_OK)
&& (syslog_count(SYSLOG_RUN) == 0u);
CHECK(ok, "TC-SYSLOG-122b FAIL is not sticky: once the directory is back a new request clears normally");
}
/* ===================== L 五类记录与 quiet 投递通道(task-10) ===================== */
/* 七类记录一枚描述、一枚路径:描述自带类别号,用来证明"id -> 文件"的对应关系
* 与两端表序一致(表序错 = 事故安静地写进告警文件,屏上看不出任何毛病)。 */
static const char *const s_kind_desc[SYSLOG_COUNT] = {
"k0run", "k1selftest", "k2accid", "k3alarm", "k4start", "k5oper", "k6yk"
};
static const char *const s_kind_path[SYSLOG_COUNT] = {
P_RUN, P_SELFTEST, P_ACCID, P_ALARM, P_START, P_OPER, P_YK
};
static void tc_kinds(void)
{
uint8_t raw[SYSLOG_REC_SIZE];
int ok;
uint32_t i;
int base_entries;
printf("-- TC-SYSLOG-130..136 five record kinds + quiet posting channel --\n");
/* 130 一类一个文件:每类只进自己那份,且没有第八个文件被顺手建出来 */
reset_all();
ready_only();
base_entries = ff_fake_count_entries();
for (i = 0u; i < (uint32_t)SYSLOG_COUNT; i++) {
(void)s_append((syslog_id_t)i, s_kind_desc[i]);
}
ok = 1;
for (i = 0u; i < (uint32_t)SYSLOG_COUNT; i++) {
if (ff_fake_size(s_kind_path[i]) != SYSLOG_REC_SIZE) { ok = 0; break; }
if (!raw_at(s_kind_path[i], 0u, raw)) { ok = 0; break; }
if (memcmp(raw + SYSLOG_DESC_OFFS, s_kind_desc[i], strlen(s_kind_desc[i])) != 0) { ok = 0; break; }
if (raw[SYSLOG_DESC_OFFS + strlen(s_kind_desc[i])] != 0u) { ok = 0; break; }
}
CHECK(ok, "TC-SYSLOG-130 each kind lands in its own file and carries its own description (id-to-path table order is semantics)");
CHECK(ff_fake_count_entries() == (base_entries + (int)SYSLOG_COUNT),
"TC-SYSLOG-130b exactly seven record files exist, none created under a mistyped 8.3 name");
/* 130c 五类新路径都过严格 8.3(081 只覆盖老三类 + wdev;扩 id 必须一起扩这条) */
ok = 1;
for (i = 0u; i < (uint32_t)SYSLOG_COUNT; i++) {
/* 上面已经真打开写成功过;这里再核一次名字本身:主名 <=8、扩展 = log、
* 不以点开头、不含非法字符 */
const char *p = s_kind_path[i];
const char *slash = strrchr(p, '/');
size_t body = 0u, ext = 0u;
const char *q;
if ((slash == NULL) || (slash[1] == '.')) { ok = 0; break; }
for (q = slash + 1; (*q != '\0') && (*q != '.'); q++) { body++; }
if (*q == '.') { for (q++; *q != '\0'; q++) { ext++; } }
if ((body == 0u) || (body > 8u) || (ext != 3u)) { ok = 0; break; }
}
CHECK(ok, "TC-SYSLOG-130c all seven production paths are strict 8.3 short names (FF_USE_LFN=0)");
/* 131 各用各的容量上限:accid 到自己那格(512)轮转,且不动邻居 */
reset_all();
ready_only();
(void)ff_fake_mkfile_size(P_ACCID, (SYSLOG_ACCIDENT_MAX - 1u) * SYSLOG_REC_SIZE);
(void)ff_fake_mkfile_size(P_ALARM, (SYSLOG_ALARM_MAX - 1u) * SYSLOG_REC_SIZE);
(void)put_rec(SYSLOG_ACCIDENT, "cap-a1");
ok = (syslog_count(SYSLOG_ACCIDENT) == SYSLOG_ACCIDENT_MAX)
&& (ff_fake_size(P_ACCID) == (SYSLOG_ACCIDENT_MAX * SYSLOG_REC_SIZE));
(void)put_rec(SYSLOG_ACCIDENT, "cap-a2"); /* 到顶 -> 删除重建,只剩本次一条 */
ok = ok && (syslog_count(SYSLOG_ACCIDENT) == 1u)
&& (ff_fake_size(P_ACCID) == SYSLOG_REC_SIZE);
CHECK(ok && (ff_fake_size(P_ALARM) == ((SYSLOG_ALARM_MAX - 1u) * SYSLOG_REC_SIZE)),
"TC-SYSLOG-131 the accident log rotates on its own cap and leaves the alarm log alone");
/* 131b yk 用的是自己那格(256),不是抄来的 512/1024 —— 三档上限互不相同,
* 所以这一条能区分出"s_caps[] 串格"这种安静错误 */
reset_all();
ready_only();
(void)ff_fake_mkfile_size(P_YK, (SYSLOG_YK_MAX - 1u) * SYSLOG_REC_SIZE);
(void)put_rec(SYSLOG_YK, "cap-y1");
ok = (syslog_count(SYSLOG_YK) == SYSLOG_YK_MAX);
(void)put_rec(SYSLOG_YK, "cap-y2");
ok = ok && (syslog_count(SYSLOG_YK) == 1u) && (ff_fake_size(P_YK) == SYSLOG_REC_SIZE);
CHECK(ok, "TC-SYSLOG-131b the telecontrol log rotates on its own 256-record cap (per-id caps are not shared)");
/* 132 quiet 通道(队列侧):丢弃要计数,但投递侧一条日志都不许打
* ——这正是它存在的唯一理由,退化成 syslog_post 的别名必须被照出来 */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain(); /* 建队列 */
g_sys_err_count = 0;
for (i = 0u; i < (SYSLOG_QUEUE_MAX + 4u); i++) {
syslog_post_quiet(SYSLOG_ACCIDENT, "q");
}
ok = (s_q_cnt == SYSLOG_QUEUE_MAX) && (syslog_post_dropped() == 4u)
&& (g_sys_err_count == 0);
syslog_drain(); /* 落盘 + drain 侧按差值补一条 */
ok = ok && (syslog_count(SYSLOG_ACCIDENT) == SYSLOG_QUEUE_MAX)
&& (g_sys_err_count == 1) && (syslog_post_dropped() == 4u);
syslog_drain(); /* 计数没前进 -> 不得重复报 */
CHECK(ok && (g_sys_err_count == 1),
"TC-SYSLOG-132 quiet drops are counted and reported once from the writer side, never at the posting site");
/* 133 清除记录 = 一次真实运维动作 -> 落操作记录;且不影响被清那份的归零判据 */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain();
syslog_post(SYSLOG_RUN, "before clear");
syslog_drain();
ok = (syslog_count(SYSLOG_RUN) == 1u);
ok = ok && (syslog_request_clear(SYSLOG_RUN) == 0);
syslog_drain(); /* 删除落地 + 投递那条操作记录 */
ok = ok && (syslog_count(SYSLOG_RUN) == 0u)
&& (syslog_clear_state(SYSLOG_RUN) == (int)SYSLOG_CLEAR_OK);
syslog_drain(); /* 操作记录这才落盘 */
ok = ok && (syslog_count(SYSLOG_OPER) == 1u) && raw_at(P_OPER, 0u, raw)
&& (memcmp(raw + SYSLOG_DESC_OFFS, "clear run.log", 13u) == 0);
CHECK(ok, "TC-SYSLOG-133 clearing a log writes one operation record naming the file, while the cleared log stays at zero");
/* 133b 反过来:清 oper.log 自己时不得投递操作记录,否则"清除成功"这一条
* 会把刚删空的文件又写成 1 条,直接砸掉 task-4 的「count 归零」判据 */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain();
syslog_post(SYSLOG_OPER, "an operation");
syslog_drain();
ok = (syslog_count(SYSLOG_OPER) == 1u);
ok = ok && (syslog_request_clear(SYSLOG_OPER) == 0);
syslog_drain();
ok = ok && (syslog_clear_state(SYSLOG_OPER) == (int)SYSLOG_CLEAR_OK)
&& (syslog_count(SYSLOG_OPER) == 0u);
syslog_drain(); /* 再排一轮:真投了自我记录,这轮就写回去 */
CHECK(ok && (syslog_count(SYSLOG_OPER) == 0u) && (syslog_post_dropped() == 0u),
"TC-SYSLOG-133b clearing the operation log itself posts nothing, so the zeroed-count criterion survives");
/* 134 守卫:越界 id / NULL 描述在 quiet 通道上既不投递也不计丢弃
* ("被守卫挡下"与"满了被丢掉"是两件事,混起来就等于把参数错报成容量故障) */
reset_all();
ready_only();
s_kernel_state = osKernelRunning;
syslog_drain();
g_sys_err_count = 0;
syslog_post_quiet((syslog_id_t)SYSLOG_COUNT, "bad-id");
syslog_post_quiet(SYSLOG_RUN, NULL);
ok = (syslog_post_dropped() == 0u) && (s_q_cnt == 0u) && (s_preboot_cnt == 0u)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0) && (g_sys_err_count == 0);
CHECK(ok, "TC-SYSLOG-134 quiet channel guards bad id and NULL desc without counting them as drops and without touching the volume");
/* 135 启动前数组侧同样计数、同样静默(两个通道各自独立,与 F 项那两个抑制位
* 的分区方式一致;漏掉这一侧就会"运行期有账、启动期没账") */
reset_all();
ready_only(); /* 内核未跑 -> 走 s_preboot */
g_sys_err_count = 0;
for (i = 0u; i < (SYSLOG_QUEUE_MAX + 3u); i++) {
syslog_post_quiet(SYSLOG_START, "p");
}
ok = (s_preboot_cnt == SYSLOG_QUEUE_MAX) && (syslog_post_dropped() == 3u)
&& (g_sys_err_count == 0);
s_kernel_state = osKernelRunning;
syslog_drain();
CHECK(ok && (syslog_count(SYSLOG_START) == SYSLOG_QUEUE_MAX) && (g_sys_err_count == 1),
"TC-SYSLOG-135 pre-boot array overflow is counted and traced from the writer side too (8 slots kept, 3 lost)");
/* 136 五类记录与老两类**逐字节同构**:同一枚 s_format_rec,记录体里没有类型字段
* ——这就是"文件即类型"这条决策的机器表达(task-11 页面因此只需一套解析) */
reset_all();
{
uint8_t a[SYSLOG_REC_SIZE];
uint8_t b[SYSLOG_REC_SIZE];
s_now = 1760234567u;
s_clock_ok = 1;
s_format_rec(SYSLOG_RUN, "same text", a);
s_format_rec(SYSLOG_YK, "same text", b);
ok = (memcmp(a, b, SYSLOG_REC_SIZE) == 0);
s_format_rec(SYSLOG_ACCIDENT, "same text", b);
ok = ok && (memcmp(a, b, SYSLOG_REC_SIZE) == 0);
s_format_rec(SYSLOG_OPER, "same text", b);
ok = ok && (memcmp(a, b, SYSLOG_REC_SIZE) == 0);
}
CHECK(ok, "TC-SYSLOG-136 a record body does not encode its kind: same content is byte-identical across all seven kinds");
}
/* ===================== 入口 ===================== */
int main(void)
@@ -1855,6 +2419,8 @@ int main(void)
tc_extra_branches();
tc_selfheal_codes();
tc_post_throttle();
tc_clear();
tc_kinds();
printf("\n=== Sys Log Mcu Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);