test(host): 断言字面量统一纯 ASCII,30 文件 1535 行

用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
This commit is contained in:
2026-10-24 20:57:46 +08:00
parent 5a806d8a26
commit 924ac3d428
31 changed files with 1626 additions and 1542 deletions
@@ -29,7 +29,7 @@
| gcc / gcov / clang / llvm-cov / lcov / gcovr(**Windows 原生侧**) | PATH 上没有;再全盘扫 `Program Files*`、`C:\msys64`、`scoop`、choco、`.platformio`、`%LOCALAPPDATA%\Programs` → **0 处**(OpenCppCoverage 也没有) | Windows 侧走不了 gcov 这条最主流的路(但 WSL 里有,见 §2.1) |
| `cl /coverage`、`cl /fsanitize=coverage` | **MSVC 14.51 已不认**:只报 `warning D9002: 忽略未知选项`,**然后照常编译成功** | ⚠ 最阴的一种:不报错、静默没有插桩 → 覆盖率恒为 0 或假数据,绝不可用 |
| `Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe`(17.0) | 文件在,但 `collect` / `analyze` / `instrument` **一律只打印"用法: CodeCoverage <command>"** 并返回 -1 | 已 deprecated 且子命令不可用 |
| **`Microsoft.CodeCoverage.Console.exe`**(mscoverage 18.x,随 VS 装在 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\`) | `collect -f cobertura` 直接吃 MSVC 原生 exe + PDB,产出标准 Cobertura XML;**子进程退出码原样透传**(实测子进程 `return 3` → `collect` 也返回 3) | ✅ **本脚本用它**("加载期动态插桩",不是编译期插桩) |
| **`Microsoft.CodeCoverage.Console.exe`**(mscoverage 18.x,随 VS 装在 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console`) | `collect -f cobertura` 直接吃 MSVC 原生 exe + PDB,产出标准 Cobertura XML;**子进程退出码原样透传**(实测子进程 `return 3` → `collect` 也返回 3) | ✅ **本脚本用它**("加载期动态插桩",不是编译期插桩) |
### 2.1 GCC 到底行不行:能编、能跑、**能出分支覆盖**——结论是**不用**(换编译器就是换分母)
@@ -50,7 +50,7 @@
分支这一维——不值当。代价如实记在 §7 第 1 条:本口径的绿灯**只证明行与函数被测到**。
探测顺序:环境变量 `DTU_MS_COVERAGE`(设了就必须有效,**不静默回退**,与 `DTU_MSVC_CL` 同口径)
→ 各 VS 安装根的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\` → PATH。
→ 各 VS 安装根的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console` → PATH。
找不到即以退出码 2 失败并打印上述现实,**不会**假装通过。
## 3. 怎么跑
@@ -77,7 +77,7 @@
退出码:`0` 全过且无回退 / `1` 有单测失败或覆盖率回退 / `2` 环境错误(无 cl、无 mscoverage、
编译失败、XML 缺失)/ `3` 用法错误。全量实测约 **25 s**(16 套 × 编译 + 插桩运行)。
中间产物(exe / PDB / Cobertura XML)一律落 `%TEMP%\dtu_test_cov\<模块>\`,**不进仓库**,
中间产物(exe / PDB / Cobertura XML)一律落 `%TEMP%\dtu_test_cov\<模块>`,**不进仓库**,
所以本脚本不需要新增任何 `.gitignore` 规则。
## 4. 统计面:进什么、不进什么
@@ -153,7 +153,7 @@
7. **头文件里的代码归 `.h`。** 统计面含 `.h`(静态内联与被展开的行落在头文件上);当前 16 套
几乎没触及 `.h`,表里看不到它们——不要以为头文件"天生 100%"。
8. **依赖 `Microsoft.CodeCoverage.Console.exe`。** 探测顺序:`DTU_MS_COVERAGE`(设了就必须
有效、**不静默回退**)→ 各 VS 安装根的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\` →
有效、**不静默回退**)→ 各 VS 安装根的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console` →
PATH;找不到即退出码 2(**不是 0**)。2026-10-24 起该结果直接红掉 `test-coverage` 钩子(用户裁定
FAIL 语义),并已合成自证:把 `DTU_MS_COVERAGE` 指到一个不存在的文件 → 钩子 Failed、脚本退出码 2、
打印可执行的 `[ENV]` 提示。
@@ -550,9 +550,92 @@ uint16_t ch395f_link_change_count(void);
7. **新增断言要用「逐条退回生产码」验一次牙**(变异自证):把四条修复逐条退回旧实现,失败集合单调增长 6 → 11 → 18 → 22 条、每步都是上一步的超集,改回即 127/127 全绿——**没有这一步,一条空转断言就要被交上去**。
8. **任务书里给的临时缺陷标签会被编号门禁当自造编号拦**——本批 Lead 在任务书里用的两个「单字母+连字符+序号」临时标签被原样抄进生产注释,一次冒出 8 类台账外错误(连本文档举这个反例时把字面量写出来,都会被同一根闸再拦一次,故此处只能描述形态、不能给样例)。纪律:**Lead 给的内部标签不得写进代码**,注释只写行为(如「两套 IP 字节序统一为 POSIX 网络序」),要指用例就指已登记的 `TC-*` 号。
### 第 2 轮:测试字面量统一纯 ASCII(2026-10-24,单独一次提交)
用户裁定「统一」。口径来自 AGENTS 铁律「**字符串字面量必须纯 ASCII**」(无 BOM 文件里奇数个汉字会吞掉结束引号,
偶数才侥幸通过)——**中文只写注释**。四名工人按目录分四组并行、写范围互斥,25 套 / 30 个 `.c` 全部改完。
**两套口径必须分清(本轮最大的方法论收获)**:
| 口径 | 做法 | 结果 | 用途 |
|---|---|---|---|
| 粗口径(普查) | 按行切引号,跳过以 `*` / `//` / `/*` 开头的行 | 34 文件 / **1,552** 行 | 判断"谁还没动" |
| 严格口径(交付判据) | 状态机把块/行注释替换成**等宽空格**(保住行列)后再取字符串字面量判 `ord>127` | 30 文件 / **1,535** 行 | 交付与验收 |
差值 = **4 个文件的命中全在注释里**(`net_socket/ns_peers.c`、`net_socket/stub/ns_rtos.h`、
`common/ch395f_fake.h`、`common/fatfs_fake.c`:行尾或块注释续行里的中文 + ASCII 引号)——**一处都不该改**,
改了等于把中文从注释里搬走、正好违背口径。教训写死成纪律:**粗口径绝不能当交付判据**,否则要么白改注释、
要么留下"这行为什么没改"的悬案(本批两名工人各自独立撞见并如实上报,没有一人硬凑数字)。
**Lead 独立终检(不采信自报)五条硬判据 → 30 文件漂移 0**:
1. 每个文件的 **TC 号集合**与 HEAD 逐字相同(防"改标签顺手改号"与"批量替换串位");
2. **断言宏调用点数**(`CHECK`/`ASSERT` 等)与 HEAD 相同;
3. **行数与 HEAD 完全相等**——这条专抓 `read` 全文回写把尾部(含 `main()`)吃掉的截断,第 1 轮真发生过两次;
4. **剥注释后代码区非 ASCII 字符 = 0**(含字符字面量),残留必须归零;
5. 字节卫生:BOM 与 HEAD 一致、无 CR、无 U+FFFD、末尾恰好一个换行。
另用一条更硬的机械证明替代"眼看 diff":**逐行把两侧字面量整体替换成空串后,工作版与 HEAD 必须逐行相等**
(`only string literals changed`)——任何落到引号外的差异当场失败。
**"没手滑动逻辑"的证据 = 覆盖率复量逐字不变**:各套 `--module` 与基线一致(`net_socket` 887/889·53/53、
`console_log` 199/200·16/16、`sys_log_mcu` 324/326·13/13、`cfg_file_store_mcu` 97/97·10/10、
`net_listener` 125/126、`lftpd` 304/473、`iec104_link` 367/405 …);每套 runner 的断言数改前后相同
(如 `iec104` 97/97、`sys_clock` 100/100、`adc_input` 103/103、`net_cfg` 45/45)。**基线本轮不需要重登**(生产码零改动)。
⚠ **`@build` 对本轮没有覆盖力**:`test/host/**` 不进 Keil 编译面也不进模拟器 CMake,字面量改动只能靠
16 个 `*-unit-test` 钩子 + `test-cppcheck` + `test-coverage` 这三根闸验证——**别把"编译过了"当测试面改动的证据**。
**本轮新手法(值得抄进任何"批量改标签"作业)**:
1. **TC 号逐字相等守卫**:从文件字节取原文(不手敲),提取原文与译文各自的 TC 号要求**逐字相等**,不等即拒绝写入。
它拦下的是「测试标签与被测事实错位」——断言照样绿,却永久指向错误的号位(本轮真的拦下一次 092 被写成 093)。
2. **批量替换的键必须是内容,不能是顺序/位置**:按位置对齐 HEAD 与工作版会在字面量数量不等时**串位污染**
(本轮真实发生过:`net_inet_addr("255.255.255.255")` 被换成一条断言名)。正确做法是 HEAD 字节 + 按字面量文本精确匹配,
**凡未命中映射且含非 ASCII 的字面量直接报错终止**,其余字节一字节不动。
3. **自查至少要有一维能发现「自己多改了东西」**:残留计数 + 改动形状(不含引号的改动行 = 0)+ 行数与 HEAD 对比,三样缺一就漏。
本轮暴露事故的正是"残留本该 0 却报 11"——只看 rc 与断言条数会一路放行到提交。
4. **新串必须「不引入任何编号形态」**(本批真实翻车点):一句 `CHECK` 的英文标签里被写回了一条**已登记族之外**的「单字母+连字符+序号」编号,
而 HEAD 那行早就在上一轮整改中把它换成了平实描述——**翻译等于把债重新引进来**。批量翻译的守卫除「原文与译文 TC 号逐字相等」之外,
还必须断言「新串不得出现任何 已登记前缀族之外 的编号形态」(字母+连字符+数字一律拦)。
⚠ 同时暴露一条验收口径的教训:Lead 的自动验收器只比对了 `TC-*` 形的 id 集合,**没覆盖其它已登记族**(L 系 / E 系 / N 系 / S 系 / TRAP 系编号),
这一条是**编号门禁闸**拦下来的,不是我的验收器——「结构不变式」只覆盖自己想到的维度时,绝不能替代真闸。
⚠ 写这条手法时,Lead 又把该编号的字面量当样例写进本文档、被同一根闸再拦一次(第 1 轮举临时标签反例时已中过一次)。**结论:门禁不看语境,文档举例只能描述形态、不能贴字面量。**
5. **替换说明符不变量(比「禁止裸 `%`」更强)**:新旧字面量的**转换说明符序列必须逐位相同**(个数/顺序/宽度一致)。
「禁止新增 `%`」只挡一半——丢了 `%` 是 UB(实参没被消费)、多了也是 UB(多消费栈垃圾),而逼人把 `%` 写成 `%%`
等于把说明符偷偷变成字面文本,编译器最多给个 C4473 就放过。另注:断言名是当**实参**打印的,本身不会被二次解释,
真正要防的是「标签被当格式串」的实现——批量改标签前顺手扫一眼宏定义。
6. **生成脚本的脚本,转义层数会翻倍**:经模板字符串生成 Python 源码时正则里的 `\\s` 被吃成字面量反斜杠+s,正则永不匹配,
于是 418 条断言名的 TC 前缀**全部静默丢失**。要么显式 `chr(92)`,要么像本 Lead 会话一样改用「JS 组装 JSON 规格 +
Python 按规格带断言应用」,把转义面收敛到一处。
**协作口径(并行作业的板子纪律)**:
- **先改板再派工**:拆别人在做的范围时,必须先 `team_task_update` 收缩原任务写范围,再建新任务并派工。
本轮 Lead(我)建 G3a 时板上已回"write scopes overlap with task-9"却照样派工,属于用队友的规避动作补自己的疏漏——
工人-side 的补救是查 `mtime` 确认文件未被动过再让出,比外部检查及时,但这不该依赖个人自觉。
- **多名 agent 共用 `%TEMP%`,通用文件名本身就是竞态**:本轮一名工人写 `r2_verify.py` 撞上队友同名脚本,
是「文件未读不许改」的守卫阻止了覆盖;若当时改用直接 fs 写入就会**静默毁掉别人的工件**。纪律:临时文件一律带组前缀。
- 任务书里的条数要写清是**字面量条数**还是**断言总数**(本轮 `adc_input` 89 条字面量 vs 103 条断言),
否则工人会拿两套数互相印证出假结论。
- **三个计数单位别混用**(本批全踩过):① **行** = 含非 ASCII 字面量的行数(Lead 终检口径,本轮 1,535);
② **条** = 字面量出现次数(HEAD 全量 1,538;个别行含 2 条);③ **numstat 的 ± 行数** ≠ 替换条数——
git 会在相邻改动块之间重排空行(实测 `adc_input` 出现 90 行对 / 89 条替换,多出的那对两侧皆空、净零)。
同一批工作因此出现过 1,552 / 1,538 / 1,535 三个「都对」的数字——**报数前先声明用的是哪一个**。
- **分母只算 `.c`/`.h`**:`test/host` 下的 runner 与桩脚本是 `.py`,HEAD 上有 44 条中文 docstring,
属另一套口径(不经 ARMCC 代码页,翻它没有工程收益);混进分母会直接虚高普查数。
- 取改前原始字节一律 `git show HEAD:<路径>`:**不要**用 `git checkout` 取版本(会丢弃未提交工作,是铁律),
也别拿工作区文件当「改前基线」——队友可能正在改它。
**后续项(本轮不做)**:① 把「剥注释词法扫描」收进 `scripts/` 当门禁(需用户点头,并先定 `test/` 是否纳入
`coding-standard` 的口径——该钩子现在 `exclude` 了 `test/`);② `test/host/iec104/` 里「`TC-` 直接接三位序号、不带模块码」这类**简写形态**
归族(属编号登记与可追溯性改动,牵 `id_registry.json`,不能顺手做);③ `runner .py` 的中文 docstring **不动**
(口径是 `.c/​.h`,且 Python 源不经 ARMCC 代码页,翻它无工程收益)。
**给下一批的可复用手法与坑**(完整清单写在各套件 `.c` 文件头,这里只留跨模块通用的):
1. **测试 TU 里 `#define fputc <改名符号>` 再纳入生产源**:`App/log/console_log.c`:492 的
`int fputc(int, FILE*)` 与宿主 CRT 撞符号(`libucrt.lib(fputc.obj) error LNK2005` + `LNK1169`)。
改名纳入后函数体与行覆盖一字不动;顺带实测确认**宿主 printf 不走用户 `fputc`**(测试输出不会被吞进环形缓冲)。
@@ -660,3 +743,4 @@ P2 5 个 2,226 行、P3 29 个 4,814 行明确不做**(四条对得上:13+5+
绕过 netTask、`console_log.c` 冷却哨兵与空位判据),**生产码一行未改**,逐条登记在 §9「P2 落地」表内待裁定;
§8 第 1 条同步改为「清单已吃穿」 |
| 2026-10-24 | **第 1 轮修复(同日续)**:用户裁定「全部修」,11 条现状里 **10 条已处置 + 1 条撤销**(`net_recv_locked` 那条是误报:非阻塞分支已能报 EOF,`:1885` 核过)。生产码改动跨 `sys_log_mcu.c`(整批回投 / 文案与节流 / 删不可达截断支 / 新增 `s_is_selfheal_code` 收敛自愈触发集 / post 两层留痕节流)、`soe_store.c`(注释更正 + 触发集同源更保守子集)、`cfg_file_store_mcu.c`(`FR_NO_SYSTEM` 不存在)、`sys_log.h`(读侧容量契约)、`net_socket.c/.h/net_types.h`(`s_addr` 统一到 POSIX 网络序 + UDP 两入口执行任务守卫,新码 `NET_ERR_NOT_IN_NETTASK = -12`)、`net_socket_sim.c`(同步 + 有意不对称)、`console_log.c/.h`(冷却独立标志 / 空位判据收进临界区 / 未初始化分诊 / 三本账 + 新读口)。**`@build` 0 错 0 警**(`Code=213720 RO=18860 RW=1204 ZI=131500`);全量闸 **33/33 全绿**、`5605/6275(89.3%)`、`396/429(92.3%)`、逐文件比对**下降 0**。设计文档同步:`控制台单写者日志设计说明.md`(§4.1 口径 + §13-O2 销项 + v1.5)、`运行与自检记录设计说明.md`(§4.4/§4.5 四条)、`STM32F4-Base存储架构说明.md`(§10.1 第 10 条推论)、`CH395F_Trap_Records.md`(TRAP-NET-21 后续)、`BSD_Socket_API_使用指南.md`(新增 §9.0 唯一约定)。本轮新增处置表、后续项 5 条、台架复验 7 条、新踩坑 8 条见 §9 |
| 2026-10-24 | **第 2 轮:测试字面量统一纯 ASCII**(单独提交):用户裁定「统一」,口径 = AGENTS 铁律「字符串字面量必须纯 ASCII」(无 BOM 文件里奇数个汉字会吞掉结束引号),中文只写注释。四名工人四组互斥目录并行,25 套 / 30 个 `.c` 的非 ASCII 字面量全部改成等值英文。粗口径普查 34 文件/1,552 行、严格口径(剥注释后取字面量)30 文件/**1,535** 行——差值那 4 个文件的命中全在注释里(`ns_peers.c`、`net_socket/stub/ns_rtos.h`、`common/ch395f_fake.h`、`common/fatfs_fake.c`),按口径**不改**。**Lead 独立终检五条硬判据**(TC 号集合不变 / 断言宏计数不变 / **行数与 HEAD 相等** / 代码区非 ASCII=0 / 字节卫生)→ **30 文件漂移 0**;每套断言数与 `--module` 覆盖率复量逐字不变(`net_socket` 887/889·53/53、`console_log` 199/200·16/16、`sys_log_mcu` 324/326·13/13、`adc_input` 103/103、`sys_clock` 100/100…),生产码零改动故基线不需重登。⚠ `@build` 对 `test/host/**` 无覆盖力,本轮证据只能来自 `*-unit-test` + `test-cppcheck` + `test-coverage`。方法、两版事故与协作纪律见 §9「第 2 轮」小节 |
+89 -89
View File
@@ -213,20 +213,20 @@ static void tc_init(void) {
(void)adc_input_poll(); /* 单拍无新帧 = 相位伪影,不计故障 */
(void)adc_input_poll(); /* 连续第二拍无新帧 = 真停摆 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-001 连续两拍无新帧 -> 停摆计数 = 1");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-001 two rounds in a row with no new frame -> stall count = 1");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 4321),
"TC-ADCIN-002 poll 后 UA 可取数且值正确");
"TC-ADCIN-002 after poll UA is readable and the value is right");
CHECK(adc_input_init() == 0, "TC-ADCIN-003 adc_input_init 返回 0(映射自检通过)");
CHECK(adc_input_init() == 0, "TC-ADCIN-003 adc_input_init returns 0 (mapping self-check passed)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN,
"TC-ADCIN-004 init 后 STM32 状态码 = NOT_RUN");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-003B init 挂载 DMA 恰好一次");
CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003C DMA 长度 = 2 x 9(乒乓双缓冲)");
CHECK(s_dma_buf != NULL, "TC-ADCIN-003D DMA 目的缓冲地址已交下来");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005B init 后 DMA 完成计数 = 0");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-005 init 后 STM32 失败计数归零");
"TC-ADCIN-004 after init the STM32 status code = NOT_RUN");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-003B init arms DMA exactly once");
CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003C DMA length = 2 x 9 (ping-pong double buffer)");
CHECK(s_dma_buf != NULL, "TC-ADCIN-003D the DMA destination buffer address was handed down");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005B after init the DMA completion counter = 0");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-005 after init the STM32 failure counter is zeroed");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2),
"TC-ADCIN-006 init 后 TPAFE / STM32 两侧均无有效数据");
"TC-ADCIN-006 after init neither TPAFE nor STM32 side holds valid data");
}
/* ===================== TC-ADCIN-010..01A:desc 映射表 ===================== */
@@ -262,8 +262,8 @@ static void tc_desc(void) {
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 5u),
"TC-ADCIN-018 TEMP -> STM32 CH5");
CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-ADCIN-019 越界(ADC_SEM_COUNT) -> NULL");
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01A 负数语义 -> NULL");
CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-ADCIN-019 out of range (ADC_SEM_COUNT) -> NULL");
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01A negative index -> NULL");
}
/* ===================== TC-ADCIN-020:map_check ===================== */
@@ -272,7 +272,7 @@ static void tc_map_check(void) {
/* 真实映射表自洽:TPAFE 下标 < 8 且每个 STM32 通道号都出现在扫描 Rank 内。
* 反向路径(表不自洽 -> -1)需在运行时改表,而 k_adc_map / k_stm32_rank_ch 均为
* 编译期 static const、无运行时输入可使其不自洽 -> 结构上不可达(见文件末说明)。 */
CHECK(map_check() == 0, "TC-ADCIN-020 映射表自洽(TPAFE 下标与 STM32 Rank 均在范围内)");
CHECK(map_check() == 0, "TC-ADCIN-020 mapping table is self-consistent (TPAFE index and STM32 Rank both in range)");
}
/* ===================== TC-ADCIN-021..02C:stm32_scan 成功路径 ===================== */
@@ -284,22 +284,22 @@ static void tc_take_ok(void) {
(void)adc_input_init();
dma_beat(100); /* 前半区一帧:Rank i -> 100+i */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-021 stm32_take 取到新帧 -> 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-022 状态码 = RC_OK");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-023 成功不计入失败");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-024 DMA 只在 init 挂一次(每拍不重启)");
CHECK(adc_input_stm32_seq() == 1u, "TC-ADCIN-025 DMA 完成计数 = 1");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-021 stm32_take got a new frame -> 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-022 status code = RC_OK");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-023 a success is not counted as a failure");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-024 DMA is armed once at init (never restarted per round)");
CHECK(adc_input_stm32_seq() == 1u, "TC-ADCIN-025 DMA completion counter = 1");
/* Rank -> 通道号映射:第 i 个 Rank = 100+i。
* k_stm32_rank_ch = [CH0..CH7, CH14],故 CH3->103 / CH5->105 / CH6->106 / CH14->108。 */
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-026 UB 取到 CH14(rank9)=108");
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-ADCIN-027 UC 取到 CH3(rank4)=103");
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-ADCIN-028 IB 取到 CH6(rank7)=106");
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-ADCIN-029 IC 取到 CH1(rank2)=101");
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02A IN 取到 CH2(rank3)=102");
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02B TEMP 取到 CH5(rank6)=105");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-026 UB reads CH14(rank9)=108");
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-ADCIN-027 UC reads CH3(rank4)=103");
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-ADCIN-028 IB reads CH6(rank7)=106");
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-ADCIN-029 IC reads CH1(rank2)=101");
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02A IN reads CH2(rank3)=102");
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02B TEMP reads CH5(rank6)=105");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02C stm32_take 不置 TPAFE 有效标志");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02C stm32_take does not set the TPAFE valid flag");
}
/* ============ TC-ADCIN-080..085:乒乓双缓冲(读刚完成半区,不读正在写的半区) ============
@@ -314,27 +314,27 @@ static void tc_dma_pingpong(void) {
dma_beat(100); /* 第 1 帧 -> 前半区(半传输回调) */
dma_fill(1u, 900u); /* 模拟 DMA 正在写后半区(值 900 系) */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-080 第一拍取帧成功");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-080 first round fetch succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108),
"TC-ADCIN-081 读的是刚完成的前半区(108),不是正在写的后半区");
"TC-ADCIN-081 reads the just completed half (108), not the half being written");
dma_beat(200); /* 第 2 帧 -> 后半区(传输完成回调) */
dma_fill(0u, 800u); /* 模拟 DMA 正在写前半区(值 800 系) */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-082 第二拍取帧成功");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-082 second round fetch succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208),
"TC-ADCIN-083 第二拍读到后半区(208)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA 完成计数 = 2");
"TC-ADCIN-083 second round reads the later half (208)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA completion counter = 2");
/* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-ADCIN-085 UC 同帧内一致(203)");
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-ADCIN-085 UC stays consistent within the frame (203)");
/* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记 1 点丢帧 */
dma_beat(300);
dma_beat(400);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-086 超拍后仍取到最新帧");
CHECK(adc_input_stm32_gaps() == 1u, "TC-ADCIN-087 seq 前进 2 -> 记 1 点丢帧");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-ADCIN-088 取到的是最新帧(408)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-089 丢帧不计入停摆(两类故障分开)");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-086 after skipping rounds the newest frame is still fetched");
CHECK(adc_input_stm32_gaps() == 1u, "TC-ADCIN-087 seq advanced by 2 -> records 1 dropped frame");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-ADCIN-088 what came back is the newest frame (408)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-089 a drop is not counted as a stall (two fault classes stay apart)");
/* 第二档:上面只跨了 1 帧(前进 2 记 1 点),"次数"与"点数"数值相同、钉不出单位。
* 这里连投三帧不消费 = 一次跨 2 帧,必须记 2 点(累计 1+2=3),把 2026-10-21 改的
@@ -355,18 +355,18 @@ static void tc_hw_errs(void) {
HAL_ADC_ErrorCallback(&hadc1);
HAL_ADC_ErrorCallback(&hadc1);
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-090 错误回调累计 = 2");
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-090 error callback total = 2");
HAL_ADC_ErrorCallback(&hadc_other);
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-091 别的 ADC 句柄不得计入(hadc 守卫)");
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-091 other ADC handles must not count (hadc guard)");
HAL_ADC_ConvHalfCpltCallback(&hadc_other);
HAL_ADC_ConvCpltCallback(&hadc_other);
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-092 别的句柄的完成回调不得推进 seq");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-092 a completion callback from another handle must not advance seq");
HAL_ADC_ErrorCallback(&hadc1);
CHECK(adc_input_stm32_hw_errs() == 3u, "TC-ADCIN-093 hw_errs 与 errs 分开计数、互不干扰");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs 不污染 errs");
CHECK(adc_input_stm32_hw_errs() == 3u, "TC-ADCIN-093 hw_errs and errs count separately and never interfere");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs does not pollute errs");
}
/* ===================== TC-ADCIN-030..034:DMA 挂载失败 ===================== */
@@ -377,10 +377,10 @@ static void tc_init_dma_fail(void) {
fakes_reset();
s_start_dma_rc = HAL_ERROR;
CHECK(adc_input_init() == -2, "TC-ADCIN-030 HAL_ADC_Start_DMA 失败 -> init 返回 -2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-ADCIN-031 状态码 = RC_START");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-032 失败计数 +1");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-034 挂载失败后 STM32 侧无有效数据");
CHECK(adc_input_init() == -2, "TC-ADCIN-030 HAL_ADC_Start_DMA failure -> init returns -2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-ADCIN-031 status code = RC_START");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-032 failure counter +1");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-034 after an arming failure the STM32 side holds no valid data");
}
/* ===================== TC-ADCIN-035..03A:本拍无新帧(停摆) ===================== */
@@ -392,21 +392,21 @@ static void tc_take_stall(void) {
(void)adc_input_init();
/* 没有任何 DMA 完成回调 = TIM3 未启动 / ADC 停摆 / DMA 出错 */
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-035 无新帧 -> RC_STALL(-2)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-036 状态码 = RC_STALL");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-037 单拍无新帧不计故障(相位伪影)");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-038 停摆期间 seq 不前进");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-039 停摆拍 STM32 侧无有效数据");
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-035 no new frame -> RC_STALL(-2)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-036 status code = RC_STALL");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-037 a single round without a new frame is not a fault (phase artefact)");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-038 seq does not advance while stalled");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-039 a stalled round leaves the STM32 side without valid data");
(void)stm32_take(); /* 连续第二拍无新帧 = 真停摆 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03A 连续两拍无新帧 -> 停摆计数 = 1");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03A two rounds in a row with no new frame -> stall count = 1");
(void)stm32_take(); /* 第三拍仍无新帧 */
CHECK(adc_input_stm32_errs() == 2u, "TC-ADCIN-03B 停摆持续 -> 每拍累计 = 2");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03C 停摆不计入丢帧(两类故障分开)");
CHECK(adc_input_stm32_errs() == 2u, "TC-ADCIN-03B an ongoing stall accumulates every round = 2");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03C a stall is not counted as a drop (two fault classes stay apart)");
dma_beat(100); /* 恢复 */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-03D 恢复后取帧成功");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E 恢复拍前进 1 -> 不算丢帧");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-03D after recovery the fetch succeeds");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E the recovery round advances by 1 -> not a drop");
}
/* ============ TC-ADCIN-070..078:停摆拍不得沿用上一拍旧值(陈旧帧) ============
@@ -424,24 +424,24 @@ static void tc_take_stale(void) {
fakes_reset();
(void)adc_input_init();
dma_beat(100);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-070 前置:首拍成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-071 前置:UB 取到 108(有效态)");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-070 precondition: first round succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-071 precondition: UB reads 108 (valid state)");
/* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-072 第二拍无新帧");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-073 停摆拍不得沿用上一拍值 -> -2");
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-ADCIN-074 停摆拍连其它 STM32 通道也不得出数");
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-ADCIN-074B 同上(TEMP 亦 -2)");
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-072 second round has no new frame");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-073 a stalled round must not reuse the previous value -> -2");
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-ADCIN-074 a stalled round must not emit any other STM32 channel either");
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-ADCIN-074B same as above (TEMP also -2)");
dma_beat(200);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-075 第三拍恢复成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-ADCIN-076 恢复拍重新可取数(自愈)");
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-075 third round recovers successfully");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-ADCIN-076 the recovery round can be read again (self-healing)");
/* --- 挂载失败支路:同样不得沿用(init 失败后整层无有效数据) --- */
fakes_reset();
s_start_dma_rc = HAL_ERROR;
(void)adc_input_init();
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-078 挂载失败后不得取到任何值 -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-078 after an arming failure no value may be read -> -2");
}
/* ===================== TC-ADCIN-040..045:raw 参数守卫与无数据 ===================== */
@@ -453,11 +453,11 @@ static void tc_raw_guards(void) {
(void)adc_input_init();
CHECK(adc_input_raw(ADC_SEM_UA, NULL) == -1, "TC-ADCIN-040 out=NULL -> -1");
CHECK(adc_input_raw(ADC_SEM_COUNT, &v) == -1, "TC-ADCIN-041 ch 越界 -> -1");
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-ADCIN-042 ch 负数 -> -1");
CHECK(v == 0, "TC-ADCIN-043 参数错时不写 out");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-044 TPAFE 无有效帧 -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-045 STM32 无有效扫描 -> -2");
CHECK(adc_input_raw(ADC_SEM_COUNT, &v) == -1, "TC-ADCIN-041 ch out of range -> -1");
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-ADCIN-042 ch negative -> -1");
CHECK(v == 0, "TC-ADCIN-043 on an argument error out is not written");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-044 TPAFE has no valid frame -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-045 STM32 has no valid scan -> -2");
}
/* ===================== TC-ADCIN-046..04B:raw TPAFE 取数与补码 ===================== */
@@ -478,13 +478,13 @@ static void tc_raw_tpafe(void) {
s_fake_tpafe[7] = 1007;
s_snapshot_rc = 1;
CHECK(adc_input_poll() == 1, "TC-ADCIN-046 poll 透传 TPAFE 快照码 1");
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-ADCIN-047 快照请求 8 通道");
CHECK(s_snapshot_dst != NULL, "TC-ADCIN-048 快照目的缓冲非空");
CHECK(adc_input_poll() == 1, "TC-ADCIN-046 poll passes through the TPAFE snapshot code 1");
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-ADCIN-047 snapshot requests 8 channels");
CHECK(s_snapshot_dst != NULL, "TC-ADCIN-048 the snapshot destination buffer is not NULL");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 3006), "TC-ADCIN-049 UA -> buf[6]=3006");
CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-ADCIN-04A UN -> buf[5]=2005");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == -1234),
"TC-ADCIN-04B IA -> buf[2]=-1234(补码负值原样保留)");
"TC-ADCIN-04B IA -> buf[2]=-1234 (two's-complement negative kept as is)");
}
/* ===================== TC-ADCIN-050..058:poll 契约 ===================== */
@@ -497,16 +497,16 @@ static void tc_poll(void) {
(void)adc_input_init();
s_snapshot_rc = 0;
dma_beat(100);
CHECK(adc_input_poll() == 0, "TC-ADCIN-050 快照无新帧 -> poll 返回 0");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-051 无新帧时 TPAFE 侧无效");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-052 同拍 STM32 取帧照常执行");
CHECK(adc_input_poll() == 0, "TC-ADCIN-050 snapshot with no new frame -> poll returns 0");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-051 without a new frame the TPAFE side is invalid");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-052 the STM32 fetch in the same round still runs");
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
fakes_reset();
(void)adc_input_init();
s_snapshot_rc = -1;
CHECK(adc_input_poll() == -1, "TC-ADCIN-053 快照参数错 -> poll 返回 -1");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-054 参数错时 TPAFE 侧无效");
CHECK(adc_input_poll() == -1, "TC-ADCIN-053 snapshot argument error -> poll returns -1");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-054 on an argument error the TPAFE side is invalid");
/* --- 已有有效帧后再"无新帧":保持上次有效值(不清零) --- */
fakes_reset();
@@ -517,7 +517,7 @@ static void tc_poll(void) {
s_snapshot_rc = 0;
(void)adc_input_poll();
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 777),
"TC-ADCIN-055 已有有效帧后无新帧不清零");
"TC-ADCIN-055 an existing valid frame is not zeroed when no new frame arrives");
/* --- STM32 失败不阻断 TPAFE、不影响 poll 返回值 --- */
fakes_reset();
@@ -525,10 +525,10 @@ static void tc_poll(void) {
s_fake_tpafe[2] = 55;
s_snapshot_rc = 1;
/* 不注入任何 DMA 完成 = STM32 侧停摆 */
CHECK(adc_input_poll() == 1, "TC-ADCIN-056 STM32 停摆不影响 poll 返回值(仍为 1)");
CHECK(adc_input_poll() == 1, "TC-ADCIN-056 an STM32 stall does not change the poll return value (still 1)");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == 55),
"TC-ADCIN-057 STM32 停摆不影响 TPAFE 取数");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-058 STM32 停摆另由状态码暴露");
"TC-ADCIN-057 an STM32 stall does not affect TPAFE data");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-058 an STM32 stall is exposed through the status code instead");
}
/* ===================== TC-ADCIN-060..066:rc / errs 观测 ===================== */
@@ -537,24 +537,24 @@ static void tc_rc_errs(void) {
fakes_reset();
(void)adc_input_init();
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN, "TC-ADCIN-060 init 后未扫描 = NOT_RUN(-3)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-061 init 后失败计数 = 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN, "TC-ADCIN-060 after init, before any scan = NOT_RUN(-3)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-061 after init the failure counter = 0");
dma_beat(100);
(void)stm32_take(); /* 成功 */
CHECK((adc_input_stm32_rc() == ADC_INPUT_RC_OK) && (adc_input_stm32_errs() == 0u),
"TC-ADCIN-062 成功: rc=OK 且 errs 不变");
"TC-ADCIN-062 success: rc=OK and errs unchanged");
(void)stm32_take(); /* 停摆 1(单拍 = 相位伪影,不计) */
(void)stm32_take(); /* 停摆 2 -> 计 1 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-063 连续两拍停摆累计 = 1");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-064 rc = 最近一次(STALL)");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-063 two consecutive stalled rounds accumulate = 1");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-064 rc = the most recent result (STALL)");
dma_beat(200);
(void)stm32_take(); /* 恢复成功 */
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-065 恢复成功后 rc=OK");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-066 errs 单调递增(不因成功回落)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-067 seq 只由 DMA 完成回调推进");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-065 after a successful recovery rc=OK");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-066 errs increases monotonically (a success never lowers it)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-067 seq is advanced only by the DMA completion callback");
}
/* ===================== main ===================== */
@@ -260,7 +260,7 @@ static void tc_read(void)
size_t n = 999u;
int ok;
printf("-- TC-CFGFST-001..007 读 --\n");
printf("-- TC-CFGFST-001..007 read path --\n");
/* 001 成功 + 只读模式 */
reset_all();
@@ -269,14 +269,14 @@ static void tc_read(void)
&& (memcmp(buf, "net ip=192.168.1.100\n", 20u) == 0)
&& (g_ff_obs.last_open_mode == (BYTE)FA_READ)
&& (s_lock_waits == 1) && (s_lock_releases == 1) && (s_lock_seq_err == 0);
CHECK(ok, "TC-CFGFST-001 读成功:内容与长度正确,打开模式就是 FA_READ(不带写位)");
CHECK(ok, "TC-CFGFST-001 read ok: content and length correct, opened with FA_READ only (no write bit)");
/* 002 空指针守卫在取锁之前 */
reset_all();
ok = (cfg_store_read(P_CFG, NULL, sizeof(buf), &n) == -1)
&& (cfg_store_read(P_CFG, buf, sizeof(buf), NULL) == -1);
CHECK(ok && (s_lock_waits == 0) && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-CFGFST-002 空指针 -> -1 且不取卷锁(守卫顺序正确)");
"TC-CFGFST-002 NULL pointer -> -1 and no volume lock taken (guard order correct)");
/* 003 卷忙 */
reset_all();
@@ -284,13 +284,13 @@ static void tc_read(void)
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == -1);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0) && (s_lock_releases == 0)
&& (s_lock_seq_err == 0),
"TC-CFGFST-003 卷忙 -> -1:零 FatFs、不 release(没拿到锁不放锁)");
"TC-CFGFST-003 volume busy -> -1: zero FatFs calls, no release (never release a lock we do not hold)");
/* 004 文件缺失:必须已放锁 */
reset_all();
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == -1);
CHECK(ok && (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-CFGFST-004 文件缺失 -> -1 **且已释放卷锁**(漏放 = 之后所有卷操作全瘫)");
"TC-CFGFST-004 missing file -> -1 **and the volume lock is released** (leaking it paralyses every later volume call)");
/* 005 cap > 文件大小 */
reset_all();
@@ -298,7 +298,7 @@ static void tc_read(void)
n = 999u;
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == 0) && (n == 3u)
&& (g_ff_obs.last_read_btr == 64u) && (g_ff_obs.last_read_br == 3u);
CHECK(ok, "TC-CFGFST-005 要 64 只给 3 -> nread=3 且仍算成功(短读不是错误)");
CHECK(ok, "TC-CFGFST-005 asked 64 got 3 -> nread=3 and still success (a short read is not an error)");
/* 006 f_read 失败 */
reset_all();
@@ -307,7 +307,7 @@ static void tc_read(void)
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == -1);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_CLOSE] == 1)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-CFGFST-006 读失败 -> -1 + close 已配对 + 放锁");
"TC-CFGFST-006 read failure -> -1, close paired, lock released");
/* 007 介质短读:现状是"返回 0 + nread 如实变小",调用方靠 nread 定长度 */
reset_all();
@@ -315,7 +315,7 @@ static void tc_read(void)
ff_fake_inject_read_short(P_CFG, 4u, 1);
n = 999u;
ok = (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == 0) && (n == 4u);
CHECK(ok, "TC-CFGFST-007 注入短读 -> 返回 0、nread=4(上层按 nread 截断使用)");
CHECK(ok, "TC-CFGFST-007 injected short read -> returns 0 with nread=4 (caller truncates by nread)");
}
/* ===================== 原子写 ===================== */
@@ -326,7 +326,7 @@ static void tc_write_atomic(void)
size_t n;
int ok;
printf("-- TC-CFGFST-010..020 原子写(E-01 核心)--\n");
printf("-- TC-CFGFST-010..020 atomic write (core of E-01) --\n");
/* 010 首启写(目标不存在) */
reset_all();
@@ -337,7 +337,7 @@ static void tc_write_atomic(void)
&& (strcmp(g_ff_obs.last_rename_old, "/CONFIG.TMP") == 0)
&& (strcmp(g_ff_obs.last_rename_new, "/CONFIG.INI") == 0)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-CFGFST-010 首启写:TMP -> rename -> 目标 2 字节、TMP 不留痕");
"TC-CFGFST-010 first write: TMP -> rename -> target 2 bytes, no TMP left behind");
/* 011 覆盖写:假卷的 f_rename 对已存在目标返回 FR_EXIST —— 这就是 E-01 */
reset_all();
@@ -349,7 +349,7 @@ static void tc_write_atomic(void)
&& (ff_fake_fetch(P_CFG, buf, 0u, sizeof(buf)) == 3u)
&& (memcmp(buf, "new", 3u) == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == unlinks_before + 1),
"TC-CFGFST-011 覆盖写:unlink 目标恰一次再 rename -> 新内容落地(E-01)");
"TC-CFGFST-011 overwrite: unlink target exactly once then rename -> new content lands (E-01)");
}
/* 同一条的"反面":直接 rename 到已存在目标必败(证明上一步的 unlink 不是多余的) */
{
@@ -359,7 +359,7 @@ static void tc_write_atomic(void)
(void)ff_fake_mkfile(P_TMP, (const uint8_t *)"x", 1u);
fr = f_rename(P_TMP, P_CFG);
CHECK(fr == FR_EXIST,
"TC-CFGFST-011b 假件保真:目标已存在时 f_rename 返回 FR_EXIST、不覆盖");
"TC-CFGFST-011b fake fidelity: f_rename returns FR_EXIST when the target exists, no overwrite");
if (fr == FR_OK) {
(void)f_close(&probe);
}
@@ -374,7 +374,7 @@ static void tc_write_atomic(void)
* TMP 一定被清掉、锁一定释放、绝不留半成品 */
CHECK(ok && (ff_fake_exists(P_TMP) == 0) && (ff_fake_exists(P_CFG) == 0)
&& (s_lock_releases == 2) && (s_lock_seq_err == 0),
"TC-CFGFST-012 rename 失败 -> -1 + TMP 清理 + 放锁( unlink→rename 窗口如实存在)");
"TC-CFGFST-012 rename failure -> -1, TMP cleaned, lock released (the unlink->rename window really exists)");
/* 013 TMP 打不开 */
reset_all();
@@ -384,7 +384,7 @@ static void tc_write_atomic(void)
CHECK(ok && (ff_fake_size(P_CFG) == 4u) && (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_RENAME] == 0)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-CFGFST-013 TMP 打开失败 -> -1 且**目标原内容一个字节没动**");
"TC-CFGFST-013 TMP open failure -> -1 and **not one byte of the target changed**");
/* 014 短写 = 失败:TMP 被删、目标不动、不 rename */
reset_all();
@@ -398,7 +398,7 @@ static void tc_write_atomic(void)
&& (ff_fake_fetch(P_CFG, buf, 0u, sizeof(buf)) == 11u)
&& (memcmp(buf, "good-config", 11u) == 0)
&& (s_lock_seq_err == 0),
"TC-CFGFST-014 TMP 短写判失败 -> 删 TMP、不 rename、目标仍是旧内容");
"TC-CFGFST-014 TMP short write is a failure -> TMP deleted, no rename, target keeps old content");
/* 015 len = 0 合法(清空配置) */
reset_all();
@@ -407,7 +407,7 @@ static void tc_write_atomic(void)
n = 999u;
CHECK(ok && (ff_fake_size(P_CFG) == 0u)
&& (cfg_store_read(P_CFG, buf, sizeof(buf), &n) == 0) && (n == 0u),
"TC-CFGFST-015 写 0 字节成功(清空配置),回读 nread=0");
"TC-CFGFST-015 writing 0 bytes succeeds (config cleared), read-back gives nread=0");
/* 016 卷忙:整条链零 I/O */
reset_all();
@@ -418,13 +418,13 @@ static void tc_write_atomic(void)
&& (g_ff_obs.calls[FF_FAKE_OP_RENAME] == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 0)
&& (ff_fake_size(P_CFG) == 4u) && (s_lock_releases == 0),
"TC-CFGFST-016 卷忙 -> -1:不建 TMP、不动目标、不 release");
"TC-CFGFST-016 volume busy -> -1: no TMP created, target untouched, no release");
/* 017 p_buf NULL */
reset_all();
ok = (cfg_store_write_atomic(P_CFG, NULL, 3u) == -1);
CHECK(ok && (s_lock_waits == 0) && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-CFGFST-017 p_buf NULL -> -1 且不取锁");
"TC-CFGFST-017 p_buf NULL -> -1 and no lock taken");
/* 017b 目标路径本身是 NULL:s_build_tmp_path 的空指针分支(读函数有守卫,
* 写函数**没有** p_path 的显式守卫,全靠推导函数把它挡在取锁之前) */
@@ -433,7 +433,7 @@ static void tc_write_atomic(void)
ok = (cfg_store_write_atomic(NULL, (const uint8_t *)"x", 1u) == -1);
CHECK(ok && (s_lock_waits == 0) && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0)
&& (ff_fake_size(P_CFG) == 4u),
"TC-CFGFST-017b p_path NULL -> -1:不取锁、不开文件、目标不动");
"TC-CFGFST-017b p_path NULL -> -1: no lock, no file open, target untouched");
/* 018 临时名由目标推导(同目录) */
reset_all();
@@ -443,22 +443,22 @@ static void tc_write_atomic(void)
&& (ff_fake_exists("/cfg/config.TMP") == 0) /* 已被 rename 走,不是留档 */
&& (strcmp(g_ff_obs.last_rename_new, "/CFG/A.INI") == 0)
&& (ff_fake_size("/cfg/a.ini") == 1u),
"TC-CFGFST-018 目标是 /cfg/a.ini -> 临时文件落在同目录 /cfg/config.TMP");
"TC-CFGFST-018 target /cfg/a.ini -> temp file lands in the same directory /cfg/config.TMP");
CHECK(strcmp(cfg_store_tmp_path(), P_TMP) == 0,
"TC-CFGFST-018b getter 仍是卷根 /config.TMP(与 s_path_tmp 同源)");
"TC-CFGFST-018b getter still reports the volume root /config.TMP (same source as s_path_tmp)");
/* 019 目录太长 -> 推导失败,且发生在取锁之前 */
reset_all();
ok = (cfg_store_write_atomic("/this/is/a/very/deep/dir/name/x.ini",
(const uint8_t *)"y", 1u) == -1);
CHECK(ok && (s_lock_waits == 0) && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-CFGFST-019 临时名缓冲放不下 -> -1 且不取锁(不拿半截路径去开文件)");
"TC-CFGFST-019 temp name does not fit the buffer -> -1, no lock (never open a file with a truncated path)");
/* 020 陷阱一在本模块同样成立 */
reset_all();
(void)cfg_store_write_atomic(P_CFG, (const uint8_t *)"m", 1u);
CHECK((g_ff_obs.last_open_mode & FA_WRITE) != 0u,
"TC-CFGFST-020 写 TMP 的句柄必带 FA_WRITE(这里用 CREATE_ALWAYS,不是 APPEND)");
"TC-CFGFST-020 the TMP handle must carry FA_WRITE (CREATE_ALWAYS here, not APPEND)");
reset_all();
{
static FIL probe;
@@ -470,7 +470,7 @@ static void tc_write_atomic(void)
fr2 = f_write(&probe, "abc", 3u, &bw);
(void)f_close(&probe);
CHECK((fr1 == FR_OK) && (fr2 == FR_DENIED) && (bw == 0u),
"TC-CFGFST-020b 只给 FA_CREATE_ALWAYS -> 打开成功但 f_write 恒 FR_DENIED");
"TC-CFGFST-020b FA_CREATE_ALWAYS alone -> open succeeds but f_write is always FR_DENIED");
}
}
@@ -484,7 +484,7 @@ static void tc_stat_remove(void)
int rel;
int ok;
printf("-- TC-CFGFST-030..033 状态令牌与删除 --\n");
printf("-- TC-CFGFST-030..033 stat token and remove --\n");
/* 030 size + 令牌 */
reset_all();
@@ -498,7 +498,7 @@ static void tc_stat_remove(void)
ok = ok && (cfg_store_stat(P_CFG, &st) == 0) && (st.mtime == tok2)
&& (tok1 != tok2);
CHECK(ok && (s_lock_seq_err == 0) && (s_lock_releases == s_lock_waits),
"TC-CFGFST-030 令牌 = (fdate<<16)|ftime,日期进位一定换令牌");
"TC-CFGFST-030 token = (fdate<<16)|ftime, a date rollover must change the token");
/* 031 盲区:同一分钟、同长度重写 -> 令牌不变 */
reset_all();
@@ -510,7 +510,7 @@ static void tc_stat_remove(void)
(void)cfg_store_write_atomic(P_CFG, (const uint8_t *)"bbbb", 4u);
(void)cfg_store_stat(P_CFG, &st);
CHECK(ok && (st.size == 4u) && (st.mtime == before),
"TC-CFGFST-031 同分钟同长度重写 -> 令牌不变(poll 检不出,设计已接受)");
"TC-CFGFST-031 rewrite in the same minute with the same length -> token unchanged (poll cannot see it, accepted by design)");
}
/* 032 stat 三种 -1 */
@@ -526,7 +526,7 @@ static void tc_stat_remove(void)
ff_fake_inject_rc(P_CFG, FF_FAKE_OP_STAT, FR_DISK_ERR, 1);
ok = ok && (cfg_store_stat(P_CFG, &st) == -1) && (s_lock_releases == rel + 1)
&& (s_lock_seq_err == 0);
CHECK(ok, "TC-CFGFST-032 stat:NULL 守卫/缺失/卷忙/读错误四种出口的放锁配对");
CHECK(ok, "TC-CFGFST-032 stat: lock pairing on all four exits (NULL guard / missing / busy / read error)");
/* 033 remove */
reset_all();
@@ -539,7 +539,7 @@ static void tc_stat_remove(void)
lock_busy_next(1);
ok = ok && (cfg_store_remove(P_CFG) == -1) && (s_lock_releases == rel + 2)
&& (s_lock_seq_err == 0);
CHECK(ok, "TC-CFGFST-033 remove:成功 / 已不存在的 -1 / 卷忙的 -1 与放锁配对");
CHECK(ok, "TC-CFGFST-033 remove: success / -1 when already gone / -1 when busy, each with lock pairing");
}
/* ===================== 路径与 8.3(陷阱二) ===================== */
@@ -559,7 +559,7 @@ static void tc_names(void)
FRESULT fr_colon;
int ok;
printf("-- TC-CFGFST-040..042 路径合规与「名字错不升级成卷操作」 --\n");
printf("-- TC-CFGFST-040..042 path legality, name errors never escalate to volume ops --\n");
/* 040 生产路径真的可用(改名即静默失败的第一道防线) */
reset_all();
@@ -568,7 +568,7 @@ static void tc_names(void)
&& (f_close(&probe) == FR_OK)
&& (f_open(&probe, cfg_store_tmp_path(), FA_WRITE | FA_CREATE_ALWAYS) == FR_OK)
&& (f_close(&probe) == FR_OK);
CHECK(ok, "TC-CFGFST-040 /config.ini 与 /config.TMP 都是合法 8.3,真能在卷上建出来");
CHECK(ok, "TC-CFGFST-040 /config.ini and /config.TMP are valid 8.3 names and really get created on the volume");
/* 041 短名违规分档 */
reset_all();
@@ -585,7 +585,7 @@ static void tc_names(void)
CHECK((fr_body == FR_INVALID_NAME) && (fr_ext == FR_INVALID_NAME)
&& (fr_bad == FR_INVALID_NAME) && (fr_dot == FR_INVALID_NAME)
&& (fr_colon == FR_INVALID_DRIVE),
"TC-CFGFST-041 分档:体>8 / 扩展>3 / 非法字符 / 点开头 -> FR_INVALID_NAME;':' -> FR_INVALID_DRIVE");
"TC-CFGFST-041 classes: body>8 / ext>3 / bad char / leading dot -> FR_INVALID_NAME; ':' -> FR_INVALID_DRIVE");
/* 042 名字类错误码绝不升级成卷操作:非法目标名走一遍原子写 */
reset_all();
@@ -599,7 +599,7 @@ static void tc_names(void)
CHECK(ok && ((uint32_t)ff_fake_count_entries() == ents_before)
&& (ff_fake_size(P_CFG) == 4u) && (s_lock_seq_err == 0)
&& (s_lock_releases == rel + 3),
"TC-CFGFST-042 非法名只让本次调用失败:条目数不变、已有配置不动、锁把把放过");
"TC-CFGFST-042 an illegal name fails only this call: entry count unchanged, existing config intact, lock always released");
/* 非法目标名进原子写:推导出的 TMP 也非法 -> 第一步开文件就失败,
* 绝不能把已经存在的目标删掉 */
reset_all();
@@ -608,7 +608,7 @@ static void tc_names(void)
ok = (cfg_store_write_atomic("/badname-tooooo-long.ini", (const uint8_t *)"x", 1u) == -1);
CHECK(ok && (ff_fake_size(P_CFG) == 4u)
&& ((uint32_t)ff_fake_count_entries() == ents_before),
"TC-CFGFST-042b 非法目标名的原子写整条失败,**不碰已有配置文件**");
"TC-CFGFST-042b an atomic write to an illegal target fails whole, **never touching the live config file**");
}
/* ===================== 堆 ===================== */
@@ -618,7 +618,7 @@ static void tc_heap(void)
void *p;
void *q;
printf("-- TC-CFGFST-050..053 分配后端(优化调研 P0 第 1 条)--\n");
printf("-- TC-CFGFST-050..053 allocation backend (P0 item 1 of the optimisation study) --\n");
/* 050 成对归还 */
reset_all();
@@ -626,7 +626,7 @@ static void tc_heap(void)
(void)cfg_store_free(p);
CHECK((p != NULL) && (s_heap_alloc_calls == 1) && (s_heap_free_calls == 1)
&& (s_heap_used == 0u) && (s_heap_free_err == 0),
"TC-CFGFST-050 alloc/free 走 FreeRTOS 堆且成对归还(借多少还多少)");
"TC-CFGFST-050 alloc/free go through the FreeRTOS heap and return in pairs (give back what was taken)");
/* 051 分配失败如实返回 NULL */
reset_all();
@@ -635,19 +635,19 @@ static void tc_heap(void)
q = cfg_store_alloc(8u);
cfg_store_free(p); /* NULL 必须安全 */
CHECK((p == NULL) && (q == NULL) && (s_heap_free_calls == 0),
"TC-CFGFST-051 堆见底 -> cfg_store_alloc 返回 NULL(MicroLIB 512B 的真形态)");
"TC-CFGFST-051 heap exhausted -> cfg_store_alloc returns NULL (the real MicroLIB 512B shape)");
/* 052 free(NULL) 安全 + 不计数 */
reset_all();
cfg_store_free(NULL);
CHECK((s_heap_free_calls == 0) && (s_heap_free_err == 0),
"TC-CFGFST-052 cfg_store_free(NULL) 不触碰分配器");
"TC-CFGFST-052 cfg_store_free(NULL) does not touch the allocator");
/* 053 heap_free 转发 */
reset_all();
(void)cfg_store_alloc(100u);
CHECK(cfg_store_heap_free() == (FAKE_HEAP_CAP - 100u),
"TC-CFGFST-053 cfg_store_heap_free 原样转发 xPortGetFreeHeapSize");
"TC-CFGFST-053 cfg_store_heap_free forwards xPortGetFreeHeapSize verbatim");
}
/* ===================== 入口 ===================== */
+124 -124
View File
@@ -493,27 +493,27 @@ static void tc_params(void)
{
char buf[DBG_CON_LINE_MAX + 128U];
printf("-- TC-CONLOG-000..006 参数校验与截断 --\n");
printf("-- TC-CONLOG-000..006 argument checks and truncation --\n");
reset_all();
printf("(probe) 这行走宿主 CRT\n");
printf("(probe) this line goes through the host CRT\n");
fflush(stdout);
CHECK(con_uart_tx_n == 0U && used_now() == 0U,
"TC-CONLOG-000 宿主 printf 不经过本模块 fputc(否则测试输出会被吞进环)");
"TC-CONLOG-000 host printf does not pass through this module fputc (else test output would be swallowed into the ring)");
reset_all();
con_uart_txe_stuck = 1; /* 通道卡死:任何字节都不该上线 */
CHECK(dbg_console_write_line(0, 5U) == -1, "TC-CONLOG-001 write_line(NULL) 返回 -1");
CHECK(dbg_console_write_line("abc", 0U) == -1, "TC-CONLOG-002 write_line(len=0) 返回 -1");
CHECK(dbg_console_write_line(0, 5U) == -1, "TC-CONLOG-001 write_line(NULL) returns -1");
CHECK(dbg_console_write_line("abc", 0U) == -1, "TC-CONLOG-002 write_line(len=0) returns -1");
dbg_console_write_raw(0, 5U);
dbg_console_write_raw("abc", 0U);
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-003 两路 NULL/0 都不碰 UART");
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-004 参数非法不计入丢弃");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-003 both NULL and len=0 never touch the UART");
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-004 invalid arguments are not counted as drops");
reset_all();
memset(buf, 'A', sizeof(buf));
CHECK(dbg_console_write_line(buf, sizeof(buf)) == (int)DBG_CON_LINE_MAX,
"TC-CONLOG-005 超过 DBG_CON_LINE_MAX 的行按 192 截断(返回值即交付字节数)");
CHECK(con_uart_tx_n == DBG_CON_LINE_MAX, "TC-CONLOG-006 截断后恰好上线 192 字节");
"TC-CONLOG-005 a line longer than DBG_CON_LINE_MAX is truncated to 192 (return value is the delivered byte count)");
CHECK(con_uart_tx_n == DBG_CON_LINE_MAX, "TC-CONLOG-006 after truncation exactly 192 bytes reach the wire");
}
/* ============ TC-CONLOG-010..018 写者未注册 = 无锁直发 ============ */
@@ -521,26 +521,26 @@ static void tc_unattached(void)
{
char big[DBG_CON_CHUNK_MAX + 64U];
printf("-- TC-CONLOG-010..018 写者未注册:无锁直发 --\n");
printf("-- TC-CONLOG-010..018 writer not attached: lock-free direct send --\n");
reset_all();
CHECK(dbg_console_writer_ready() == 0U, "TC-CONLOG-010 attach 之前 writer_ready() == 0");
CHECK(dbg_console_write_line("hello", 5U) == 5, "TC-CONLOG-011 未注册时整行直发,返回行长");
CHECK(tx_is(0U, "hello"), "TC-CONLOG-012 直发内容原样上线(没经过环形缓冲)");
CHECK(used_now() == 0U, "TC-CONLOG-013 直发不入环(head == tail)");
CHECK(con_evt_sets == 0U, "TC-CONLOG-014 直发不置 SYS_EVT_CONSOLE_TX(还没写者可唤醒)");
CHECK(dbg_console_writer_ready() == 0U, "TC-CONLOG-010 writer_ready() == 0 before attach");
CHECK(dbg_console_write_line("hello", 5U) == 5, "TC-CONLOG-011 before attach the whole line is sent directly, returning its length");
CHECK(tx_is(0U, "hello"), "TC-CONLOG-012 direct send reaches the wire verbatim (never went through the ring)");
CHECK(used_now() == 0U, "TC-CONLOG-013 direct send does not enter the ring (head == tail)");
CHECK(con_evt_sets == 0U, "TC-CONLOG-014 direct send does not set SYS_EVT_CONSOLE_TX (no writer to wake yet)");
CHECK(con_crit_enter == 0U,
"TC-CONLOG-015 直发零临界区 = 2026-10-10 逐字节交错的根因(不是丢整行)");
"TC-CONLOG-015 direct send uses zero critical sections = the 2026-10-10 byte-interleaving root cause (not whole-line loss)");
reset_all();
memset(big, 'B', sizeof(big));
dbg_console_write_raw(big, sizeof(big));
CHECK(con_uart_tx_n == DBG_CON_CHUNK_MAX, "TC-CONLOG-016 write_raw 有界:只发 256 字节");
CHECK(con_uart_tx_n == DBG_CON_CHUNK_MAX, "TC-CONLOG-016 write_raw is bounded: only 256 bytes are sent");
reset_all();
con_uart_txe_after = 3; /* 前 3 次不就绪(TXE 抖动,未超上界) */
CHECK(dbg_console_write_line("CD", 2U) == 2, "TC-CONLOG-017 TXE 抖动后仍发全");
CHECK(dbg_console_write_line("CD", 2U) == 2, "TC-CONLOG-017 after TXE jitter the whole line still goes out");
CHECK(tx_is(0U, "CD") && con_delay_calls == 0U,
"TC-CONLOG-018 直发既不等 tick 也不入环(ISR/崩溃钩子可用的前提)");
"TC-CONLOG-018 direct send neither waits on tick nor enters the ring (premise for ISR and crash hooks)");
}
/* ============ TC-CONLOG-020..037 注册后走环形缓冲 ============ */
@@ -550,24 +550,24 @@ static void tc_ring(void)
static char oversize[3000]; /* 超长入环的源(>= 钳位后的 2048B) */
uint32_t i;
printf("-- TC-CONLOG-020..027 注册后:生产者入环、写者发送 --\n");
printf("-- TC-CONLOG-020..027 after attach: producers enter the ring, writer sends --\n");
reset_all();
attach_writer();
CHECK(dbg_console_writer_ready() == 1U, "TC-CONLOG-020 attach 之后 writer_ready() == 1");
CHECK(dbg_console_writer_ready() == 1U, "TC-CONLOG-020 writer_ready() == 1 after attach");
CHECK(s_con_writer_task == (TaskHandle_t)&s_task_writer,
"TC-CONLOG-021 attach 记下的正是调用方的任务句柄(自等保护的依据)");
CHECK(dbg_console_write_line("netTask up\r\n", 12U) == 12, "TC-CONLOG-022 入环成功返回行长");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-023 生产者绝不碰 UART");
CHECK(used_now() == 12U, "TC-CONLOG-024 整行一次入环(used = 12)");
"TC-CONLOG-021 attach records exactly the caller task handle (basis of the self-wait guard)");
CHECK(dbg_console_write_line("netTask up\r\n", 12U) == 12, "TC-CONLOG-022 a successful ring entry returns the line length");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-023 producers never touch the UART");
CHECK(used_now() == 12U, "TC-CONLOG-024 the whole line enters the ring at once (used = 12)");
CHECK(con_evt_sets == 1U && con_evt_flags == SYS_EVT_CONSOLE_TX,
"TC-CONLOG-025 入环后置真实事件位 SYS_EVT_CONSOLE_TX(bit6)唤醒写者");
"TC-CONLOG-025 after entering the ring the real event bit SYS_EVT_CONSOLE_TX (bit6) wakes the writer");
CHECK(con_crit_enter == 1U && con_crit_exit == 1U && con_crit_max_depth == 1U,
"TC-CONLOG-026 整行入环恰好一层临界区(行级原子的实现依据)");
"TC-CONLOG-026 one line takes exactly one critical-section level (implementation basis of line atomicity)");
writer_drain();
CHECK(tx_is(0U, "netTask up\r\n"), "TC-CONLOG-027 写者排空:上线字节与入环内容逐字节相同");
CHECK(tx_is(0U, "netTask up\r\n"), "TC-CONLOG-027 writer drain: wire bytes are byte-identical to what entered the ring");
printf("-- TC-CONLOG-028..032 分块 256B 与多生产者不交错 --\n");
printf("-- TC-CONLOG-028..032 256B chunking and no interleaving between producers --\n");
reset_all();
attach_writer();
memset(line, 'C', sizeof(line));
@@ -575,12 +575,12 @@ static void tc_ring(void)
(void)dbg_console_write_line(line, sizeof(line));
}
writer_drain();
CHECK(con_uart_block_n == 4U, "TC-CONLOG-028 960B 按 DBG_CON_CHUNK_MAX 切成 4 块");
CHECK(con_uart_block_n == 4U, "TC-CONLOG-028 960B is split into 4 blocks by DBG_CON_CHUNK_MAX");
CHECK(con_uart_block_len[0] == 256U && con_uart_block_len[1] == 256U
&& con_uart_block_len[2] == 256U && con_uart_block_len[3] == 192U,
"TC-CONLOG-029 块长 256/256/256/192(单次发送上限真被执行)");
"TC-CONLOG-029 block sizes 256/256/256/192 (the single-send ceiling is really enforced)");
CHECK(con_uart_tx_n == 960U && con_uart_txe_reads == 960U,
"TC-CONLOG-030 每字节恰好装载一次 DR(就绪即写,没有重复发)");
"TC-CONLOG-030 every byte is loaded into DR exactly once (write when ready, no resend)");
reset_all();
attach_writer();
@@ -592,36 +592,36 @@ static void tc_ring(void)
(void)dbg_console_write_line("[C] 33333\r\n", 11U);
writer_drain();
CHECK(tx_is(0U, "[A] 11111\r\n[B] 22222\r\n[C] 33333\r\n"),
"TC-CONLOG-031 三个生产者各写一行 -> 上线仍是三行连续原文(G1 行级原子)");
"TC-CONLOG-031 three producers, one line each -> the wire still shows three intact lines (G1 line atomicity)");
CHECK(strstr((const char *)&con_uart_tx[5], "[B] 22222") != 0,
"TC-CONLOG-032 行边界没被打断(没有任何一行被插进另一行中间)");
"TC-CONLOG-032 no line boundary was broken (no line spliced into another)");
printf("-- TC-CONLOG-033..037 跨环尾:两段拷贝 + 按连续段分块 --\n");
printf("-- TC-CONLOG-033..037 ring wrap: two-segment copy + chunking by contiguous run --\n");
reset_all();
attach_writer();
s_con_head = 4090U;
s_con_tail = 4090U;
(void)dbg_console_write_line("0123456789ABCDEF", 16U);
CHECK(s_con_head == 10U && used_now() == 16U,
"TC-CONLOG-033 跨环尾入环后 head 回绕到 10(4090+16-4096)");
"TC-CONLOG-033 a wrapped line leaves head at 10 (4090+16-4096)");
CHECK(memcmp(&con_ring_store[4090], "012345", 6) == 0
&& con_ring_store[0] == (uint8_t)'6' && con_ring_store[9] == (uint8_t)'F'
&& con_ring_store[10] == 0U,
"TC-CONLOG-034 回绕两段拷贝位置正确(前 6B 填到环尾、余 10B 从 0 起)");
"TC-CONLOG-034 both wrap segments land correctly (first 6B fill to the ring end, remaining 10B start at 0)");
writer_drain();
CHECK(con_uart_block_n == 2U && con_uart_block_len[0] == 6U
&& con_uart_block_len[1] == 10U,
"TC-CONLOG-035 drain 按连续段分块:6 + 10(绝不把环中间当连续段读越界)");
CHECK(tx_is(0U, "0123456789ABCDEF"), "TC-CONLOG-036 回绕行的上线内容与顺序都对");
"TC-CONLOG-035 drain chunks by contiguous run: 6 + 10 (never reads past the ring end)");
CHECK(tx_is(0U, "0123456789ABCDEF"), "TC-CONLOG-036 the wrapped line reaches the wire with correct content and order");
reset_all();
attach_writer();
/* 源缓冲必须给到 3000B:con_append 只钳长度,不会钳读取范围,
* 拿 192B 的栈数组当 2048B 源就是越界读(插桩跑直接 0xC0000005) */
memset(oversize, 'D', sizeof(oversize));
CHECK(con_append(oversize, (uint16_t)3000U) == 0, "TC-CONLOG-037 直调 con_append 超长仍成功");
CHECK(con_append(oversize, (uint16_t)3000U) == 0, "TC-CONLOG-037 calling con_append directly with an over-long buffer still succeeds");
CHECK(used_now() == (DBG_CON_RING_SIZE / 2U),
"TC-CONLOG-038 入环长度钳在 RING/2=2048(保证任何一行都恒可入环)");
"TC-CONLOG-038 ring entry length is clamped to RING/2=2048 (so any line can always eventually enter)");
}
/* ============ TC-CONLOG-040..042 写者自记录 / 重入 / 未注册 drain ============ */
@@ -630,7 +630,7 @@ static void tc_writer_self(void)
char line[DBG_CON_LINE_MAX];
uint32_t i;
printf("-- TC-CONLOG-040..042 写者自记录先排空 / drain 重入保护 --\n");
printf("-- TC-CONLOG-040..042 writer self-logging drains first / drain re-entry guard --\n");
reset_all();
attach_writer();
memset(line, 'E', sizeof(line));
@@ -638,11 +638,11 @@ static void tc_writer_self(void)
(void)dbg_console_write_line(line, sizeof(line));
}
con_task_self = (TaskHandle_t)&s_task_writer; /* 写者自己记一行 */
CHECK(dbg_console_write_line("F", 1U) == 1, "TC-CONLOG-040 写者自记录不丢行");
CHECK(dbg_console_write_line("F", 1U) == 1, "TC-CONLOG-040 a writer logging about itself loses no line");
CHECK(con_delay_calls == 0U,
"TC-CONLOG-041 写者自记录先排空再入环:绝不进等待(否则就是等自己 -> 死锁)");
"TC-CONLOG-041 writer self-logging drains before entering: it never waits (waiting for itself would deadlock)");
CHECK(con_uart_tx_n == 4032U && used_now() == 1U && dbg_console_dropped() == 0U,
"TC-CONLOG-042 老行全部上线、新行入环,零丢弃");
"TC-CONLOG-042 old lines all reach the wire, the new line enters the ring, zero drops");
reset_all();
attach_writer();
@@ -650,56 +650,56 @@ static void tc_writer_self(void)
s_con_draining = 1U; /* 模拟正处于 drain 之内 */
writer_drain();
CHECK(con_uart_tx_n == 0U && used_now() == 4U,
"TC-CONLOG-043 drain 重入保护:嵌套调用直接让位(写者自记录不触发递归 drain)");
"TC-CONLOG-043 drain re-entry guard: a nested call yields at once (writer self-logging causes no recursive drain)");
reset_all();
(void)dbg_console_write_line("early\r\n", 7U); /* 未注册 -> 已直发 */
con_uart_tx_n = 0U;
writer_drain();
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-044 写者未注册时 drain 不做任何事");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-044 with no writer attached drain does nothing");
}
/* ============ TC-CONLOG-050..062 缓冲满 / 丢弃 / 可观测 ============ */
static void tc_full_drop(void)
{
printf("-- TC-CONLOG-050..053 缓冲满:DWT 硬上界丢弃且不覆盖旧行 --\n");
printf("-- TC-CONLOG-050..053 ring full: DWT hard ceiling drops without overwriting old lines --\n");
reset_all();
attach_writer();
fill_ring(10U); /* 只剩 10B 可用 */
con_dwt_step = SystemCoreClock / 4U; /* 一次判据 = 250ms > 200ms 上界 */
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == -1,
"TC-CONLOG-050 环满 + DWT 上界到 -> 丢这一行(返回 -1)");
CHECK(dbg_console_dropped() == 1U, "TC-CONLOG-051 丢弃计数 +1");
"TC-CONLOG-050 ring full + DWT ceiling reached -> this line is dropped (returns -1)");
CHECK(dbg_console_dropped() == 1U, "TC-CONLOG-051 drop counter +1");
CHECK(con_delay_calls == 0U,
"TC-CONLOG-052 有 DWT 时用周期判据,不必先烧 200 个 tick(K4)");
"TC-CONLOG-052 with DWT the cycle criterion is used, no need to burn 200 ticks first (K4)");
CHECK(s_con_head == 4085U && used_now() == 4085U && con_uart_tx_n == 0U,
"TC-CONLOG-053 丢弃不推进 head、也不生产者自己发(没半写、更没覆盖未发出的旧行)");
"TC-CONLOG-053 a drop does not advance head and the producer does not send itself (no half write, no overwrite of pending lines)");
printf("-- TC-CONLOG-054..056 等待中出现空间 -> 入环成功 --\n");
printf("-- TC-CONLOG-054..056 space appears while waiting -> line enters the ring --\n");
reset_all();
attach_writer();
fill_ring(10U);
con_dwt_step = 0U; /* 时间静止:只能靠写者腾地方 */
con_delay_hook = bg_drain_hook; /* 每等一轮 = 写者排空一次 */
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == 20,
"TC-CONLOG-054 等一轮后腾出空间 -> 整行入环成功");
"TC-CONLOG-054 after one wait round space frees up -> the whole line enters the ring");
CHECK(con_delay_calls == 1U && dbg_console_dropped() == 0U,
"TC-CONLOG-055 只等了一轮,且不算丢弃");
"TC-CONLOG-055 only one wait round happened and it is not counted as a drop");
con_delay_hook = 0;
CHECK(con_uart_tx_n == 4085U, "TC-CONLOG-056 腾地方的正是写者(老行已上线)");
CHECK(con_uart_tx_n == 4085U, "TC-CONLOG-056 it really was the writer that freed the space (old lines reached the wire)");
printf("-- TC-CONLOG-057..059 无 DWT 支路:等待上界 = DBG_CON_WAIT_MS 个 tick --\n");
printf("-- TC-CONLOG-057..059 no-DWT branch: wait ceiling = DBG_CON_WAIT_MS ticks --\n");
reset_all();
con_dwt_ctrl_sticky = 0; /* 调试单元被关 -> DWT 使能失败 */
attach_writer();
fill_ring(10U);
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-057 DWT 使能失败时 s_con_dwt_up 保持 0");
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-057 s_con_dwt_up stays 0 when DWT enable fails");
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == -1,
"TC-CONLOG-058 无 DWT 时仍会丢(判据退回 tick 计数,不会无限等)");
"TC-CONLOG-058 without DWT a drop still happens (criterion falls back to tick counting, never waits forever)");
CHECK(con_delay_calls == DBG_CON_WAIT_MS && dbg_console_dropped() == 1U,
"TC-CONLOG-059 无 DWT 的等待上界恰为 DBG_CON_WAIT_MS(200) 次 vTaskDelay");
"TC-CONLOG-059 the no-DWT wait ceiling is exactly DBG_CON_WAIT_MS(200) vTaskDelay calls");
printf("-- TC-CONLOG-060..062 丢弃可观测:[ERR] 行直发不经环 --\n");
printf("-- TC-CONLOG-060..062 drop observability: the [ERR] line is sent raw, not through the ring --\n");
reset_all();
attach_writer();
fill_ring(10U);
@@ -708,10 +708,10 @@ static void tc_full_drop(void)
con_dwt_step = 0U;
writer_drain();
CHECK(tx_contains("[ERR] CONSOLE dropped 1 lines"),
"TC-CONLOG-060 排空末尾刷出 [ERR] CONSOLE dropped 1 lines(丢行永不静默,@test 判 FAIL)");
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-061 刷完即清零(下一轮不重复告警)");
"TC-CONLOG-060 the drain tail flushes [ERR] CONSOLE dropped 1 lines (loss is never silent, @test marks FAIL)");
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-061 the counter is cleared once flushed (next round does not repeat the warning)");
CHECK(used_now() == 0U,
"TC-CONLOG-062 告警行走裸发:不入环 -> 不会 con_append->drain 递归");
"TC-CONLOG-062 the warning goes out raw: not through the ring -> no con_append->drain recursion");
}
/* ============ TC-CONLOG-070..081 TX 僵尸通道:自愈 / 冷却 / 半块 ============ */
@@ -729,21 +729,21 @@ static void fault_setup(uint32_t tick0, int repair)
static void tc_tx_fault(void)
{
printf("-- TC-CONLOG-070..074 软复位没治好:故障即止 + 留痕只能靠标志 --\n");
printf("-- TC-CONLOG-070..074 soft reset did not heal: stop at the fault + trace only via flag --\n");
reset_all();
fault_setup(5000U, 0);
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-070 发送超时 -> tx_fault 计数 +1");
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-070 send timeout -> tx_fault counter +1");
CHECK(s_mx_init_calls == 1U && con_nvic_clears == 1U
&& con_rcc_disables == 1U && con_rcc_enables == 1U,
"TC-CONLOG-071 自愈完整一遍:清 pending -> 断时钟 -> 供时钟 -> 重初始化");
"TC-CONLOG-071 full self-heal sequence: clear pending -> gate off clock -> gate on clock -> re-init");
CHECK(used_now() == (DBG_CON_RING_SIZE - 1U) - DBG_CON_CHUNK_MAX,
"TC-CONLOG-072 故障即止:只丢当前 256B 块,其余留环里等下轮(让位喂狗)");
"TC-CONLOG-072 stop at the fault: only the current 256B block is lost, the rest waits in the ring (yields to the watchdog)");
CHECK(con_uart_tx_n == 0U,
"TC-CONLOG-073 通道没治好时连那条 WARN 也上不了线(裸发同样有界、不重试)");
"TC-CONLOG-073 while the channel is unhealed even that WARN cannot get out (raw send is bounded too, no retry)");
CHECK(dbg_console_tx_fault_log_take() == 1U && dbg_console_tx_fault_log_take() == 0U,
"TC-CONLOG-074 留痕标志读后清:写者据此在 drain 之外补记 run.log(断调用图成环)");
"TC-CONLOG-074 the trace flag is read-then-clear: the writer uses it to append run.log outside drain (breaks the call graph cycle)");
printf("-- TC-CONLOG-075..077 软复位治好了:WARN 上线且只一次 --\n");
printf("-- TC-CONLOG-075..077 soft reset healed: the WARN goes out exactly once --\n");
reset_all();
con_tick_now = 7000U;
attach_writer();
@@ -752,14 +752,14 @@ static void tc_tx_fault(void)
con_dwt_step = SystemCoreClock / 4U;
s_mx_repair = 1; /* 重初始化把通道治好 */
writer_drain();
CHECK(tx_contains("[WARN] CONSOLE tx fault"), "TC-CONLOG-075 首次故障的 WARN 行成功上线");
CHECK(tx_contains("[WARN] CONSOLE tx fault"), "TC-CONLOG-075 the first fault WARN line reaches the wire");
CHECK(used_now() == 0U && s_con_cool_until == 7000U && dbg_console_tx_fault() == 1U,
"TC-CONLOG-076 故障块丢弃(行已出环不可重放)+ 冷却截止记为当时 tick");
"TC-CONLOG-076 the faulted block is dropped (already out of the ring, not replayable) + cooldown deadline is the tick then");
con_uart_txe_ready_left = -1;
con_dwt_step = 0U;
writer_drain(); /* tick 未推进 -> 仍在冷却 */
CHECK(s_con_cool_until == 7000U && s_mx_init_calls == 1U,
"TC-CONLOG-077 冷却期内 drain 直接让位:不重试、不自愈第二次");
"TC-CONLOG-077 during cooldown drain yields at once: no retry, no second self-heal");
}
static void tc_tx_cool(void)
@@ -768,36 +768,36 @@ static void tc_tx_cool(void)
reset_all();
fault_setup(7000U, 1); /* 治好的那次故障 */
CHECK(s_con_cool_until == 7000U && used_now() == 3839U,
"TC-CONLOG-078 故障后剩 3839B 在环里(本块被丢)");
"TC-CONLOG-078 3839B remain in the ring after the fault (this block was dropped)");
con_dwt_step = 0U;
con_uart_txe_stuck = 0;
con_tick_now = 7299U; /* 差 1ms */
writer_drain();
CHECK(s_con_cool_until == 7000U && used_now() == 3839U && s_mx_init_calls == 1U,
"TC-CONLOG-079 差 1ms 仍在冷却:环不动、不重复自愈(冷却即节奏保护)");
"TC-CONLOG-079 1ms short of the deadline: the ring does not move and no repeated self-heal (cooldown is the pacing guard)");
con_tick_now = 7300U; /* 到点 */
writer_drain();
CHECK(s_con_cooling == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
"TC-CONLOG-080 到点即清冷却标志、放行本轮排空,且没有新增故障"
"(cool_until 只存时刻,不再被拿来当哨兵清零)");
"TC-CONLOG-080 at the deadline the flag clears, this round drains, and no new fault appears"
" (cool_until only stores a deadline and is no longer used as a clear-on-zero sentinel)");
/* 修复后的正向断言:冷却与否看独立标志,不再拿 tick 值当哨兵 */
reset_all();
fault_setup(0U, 0); /* 故障恰好落在 HAL_GetTick()==0 那一拍 */
CHECK(s_con_cooling == 1U && dbg_console_tx_fault() == 1U,
"TC-CONLOG-081 tick==0 那拍的故障照样进入冷却(独立标志,不撞 0 哨兵)");
"TC-CONLOG-081 a fault in the tick==0 round still enters cooldown (own flag, no zero sentinel collision)");
writer_drain(); /* tick 仍是 0 */
CHECK(dbg_console_tx_fault() == 1U && s_mx_init_calls == 1U
&& used_now() == (DBG_CON_RING_SIZE - 1U) - DBG_CON_CHUNK_MAX,
"TC-CONLOG-082 同一 tick 上再来一轮被冷却挡住:不再白耗一次块超时");
"TC-CONLOG-082 another round on the same tick is blocked by cooldown: no second wasted block timeout");
con_tick_now = DBG_CON_FAULT_COOL_MS - 1U;
writer_drain();
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-083 冷却 299ms 仍未到点");
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-083 299ms is still inside the cooldown");
con_uart_txe_stuck = 0;
con_tick_now = DBG_CON_FAULT_COOL_MS; /* 到点(且回绕起点正好是 0) */
writer_drain();
CHECK(s_con_cooling == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
"TC-CONLOG-084 到点后清冷却并放行:全程没有第二次故障");
"TC-CONLOG-084 after the deadline the cooldown clears and passes through: no second fault anywhere");
reset_all();
attach_writer();
@@ -816,16 +816,16 @@ static void tc_tx_cool(void)
static void tc_tx_half(void)
{
printf("-- TC-CONLOG-085..088 超时判据的真实后果:半块 / 已上线仍判失败 --\n");
printf("-- TC-CONLOG-085..088 real consequences of the timeout criterion: half block / already sent yet failed --\n");
reset_all();
attach_writer();
(void)dbg_console_write_line("ABCDEFGH", 8U);
con_uart_tc_stuck = 1; /* 字节都进了 DR,只有 TC 不置位 */
con_dwt_step = SystemCoreClock / 4U;
writer_drain();
CHECK(tx_is(0U, "ABCDEFGH"), "TC-CONLOG-085 TC 卡死:8 字节都已上线");
CHECK(tx_is(0U, "ABCDEFGH"), "TC-CONLOG-085 TC stuck: all 8 bytes were handed to the shifter");
CHECK(dbg_console_tx_fault() == 1U && con_uart_block_n == 0U,
"TC-CONLOG-086 已上线仍整块判失败(docs §13-O2:丢弃/故障两本账没分开的现状)");
"TC-CONLOG-086 already on the wire but the whole block still counts as failed (docs section 13-O2: drop and fault books were not separated)");
reset_all();
attach_writer();
@@ -833,50 +833,50 @@ static void tc_tx_half(void)
con_uart_txe_ready_left = 3; /* 只挤得出 3 个字节 */
con_dwt_step = SystemCoreClock / 4U;
writer_drain();
CHECK(tx_is(0U, "ABC"), "TC-CONLOG-087 TXE 中途卡死 -> 只有已写进 DR 的 3 字节上线");
CHECK(tx_is(0U, "ABC"), "TC-CONLOG-087 TXE stuck mid-block -> only the 3 bytes already written to DR reach the wire");
CHECK(dbg_console_tx_fault() == 1U && used_now() == 0U,
"TC-CONLOG-088 半块同样走自愈分支,整块出环(尾部 5 字节随块作废)");
"TC-CONLOG-088 a half block takes the same self-heal branch and leaves the ring (trailing 5 bytes die with the block)");
}
/* ============ TC-CONLOG-090..099 DWT 使能 / 指令计数上界 ============ */
static void tc_dwt(void)
{
printf("-- TC-CONLOG-090..093 DWT 惰性使能与就绪即走 --\n");
printf("-- TC-CONLOG-090..093 lazy DWT enable and skip-when-ready --\n");
reset_all();
(void)dbg_console_write_line("ok\r\n", 4U); /* 走一次直发 -> 顺带使能 DWT */
CHECK(s_con_dwt_up == 1U, "TC-CONLOG-090 con_dwt_ensure 成功使能后置 s_con_dwt_up");
CHECK(s_con_dwt_up == 1U, "TC-CONLOG-090 con_dwt_ensure sets s_con_dwt_up after a successful enable");
CHECK((g_con_core_debug.DEMCR & CoreDebug_DEMCR_TRCENA_Msk) != 0U,
"TC-CONLOG-091 真的写了 DEMCR.TRCENA(位号与 core_cm4.h 一致)");
"TC-CONLOG-091 DEMCR.TRCENA really gets written (bit number matches core_cm4.h)");
CHECK((g_con_dwt.CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0U,
"TC-CONLOG-092 真的写了 DWT.CTRL.CYCCNTENA");
"TC-CONLOG-092 DWT.CTRL.CYCCNTENA really gets written");
CHECK(tx_is(0U, "ok\r\n") && con_dwt_reads == 4U,
"TC-CONLOG-093 通道就绪时发送循环没再读 CYCCNT(3 次 ensure + 1 次取基准 t0)");
"TC-CONLOG-093 with the channel ready the send loop reads no further CYCCNT (3 from ensure + 1 for t0)");
printf("-- TC-CONLOG-094..097 DWT 使能失败 -> con_tx_counted 计数上界 --\n");
printf("-- TC-CONLOG-094..097 DWT enable failure -> con_tx_counted instruction ceiling --\n");
reset_all();
con_dwt_ctrl_sticky = 0; /* CYCCNTENA 写不进去 */
con_uart_txe_stuck = 1;
dbg_console_write_raw("Q", 1U);
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-094 DWT 使能失败 -> 走计数上界支路");
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-094 DWT enable failure -> the counted-guard branch is taken");
CHECK(con_uart_txe_reads == CON_TX_GUARD_MAX,
"TC-CONLOG-095 每字节最多自旋 CON_TX_GUARD_MAX(20000) 次即放弃(有界,非死等)");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-096 计数上界内没发出去就整块放弃");
"TC-CONLOG-095 each byte spins at most CON_TX_GUARD_MAX(20000) times then gives up (bounded, not a dead wait)");
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-096 if the block is not out within the counted ceiling the whole block is abandoned");
CHECK(con_dwt_reads == 3U,
"TC-CONLOG-097 计数支路一次都不读 CYCCNT(不依赖 DWT,也就不依赖时间源)");
"TC-CONLOG-097 the counted branch never reads CYCCNT (independent of DWT, hence of any time source)");
printf("-- TC-CONLOG-098..099 超时判据不依赖 HAL_GetTick(round 8b 的动机) --\n");
printf("-- TC-CONLOG-098..099 the timeout criterion does not use HAL_GetTick (round 8b motivation) --\n");
reset_all();
con_uart_txe_stuck = 1; /* tick 冻结在 0,模拟 TIM7 停摆 */
con_dwt_step = SystemCoreClock / 10U; /* 一次判据正好 = 100ms 上界 */
dbg_console_write_raw("Q", 1U);
CHECK(con_tick_reads == 0U && con_uart_tx_n == 0U,
"TC-CONLOG-098 tick 完全不走也能退出:裸发一次都没读 HAL_GetTick");
"TC-CONLOG-098 it exits even when tick never advances: one raw send reads HAL_GetTick zero times");
reset_all();
con_uart_txe_stuck = 1;
con_dwt_step = SystemCoreClock / 4U;
dbg_console_write_raw("Z", 1U);
CHECK(con_uart_txe_reads == 1U,
"TC-CONLOG-099 DWT 支路第一次自旋就判超时(上界真的起作用,不会拖满 20000 次)");
"TC-CONLOG-099 the DWT branch declares timeout on the first spin (the ceiling really works, never burns 20000)");
}
/* ============ TC-CONLOG-100..106 huart1.Instance == NULL 短路 + 未初始化分诊 ============ */
@@ -887,14 +887,14 @@ static void tc_null_uart(void)
huart1.Instance = 0;
dbg_console_write_raw("boom", 5U);
CHECK(con_uart_tx_n == 0U && con_uart_txe_reads == 0U,
"TC-CONLOG-100 Instance==NULL 时一次寄存器都不碰(不解引用地址 0)");
"TC-CONLOG-100 with Instance==NULL not one register is touched (no dereference of address 0)");
CHECK(con_dwt_reads == 3U,
"TC-CONLOG-101 仍然先惰性使能 DWT 再短路(ensure 在判空之前,顺序照旧)");
"TC-CONLOG-101 DWT is still lazily enabled before the short circuit (ensure stays ahead of the NULL check)");
reset_all();
huart1.Instance = 0;
CHECK(dbg_console_write_line("early", 5U) == 5,
"TC-CONLOG-102 未注册路径(内核前)撞上无串口也不崩、仍返回行长");
"TC-CONLOG-102 the unattached path (pre-kernel) survives a missing UART and still returns the line length");
/* 修复后的正向断言:未初始化 != 僵尸通道,绝不重配外设 */
reset_all();
@@ -920,26 +920,26 @@ static void tc_null_uart(void)
/* ============ TC-CONLOG-110..115 fputc 重定向 ============ */
static void tc_fputc(void)
{
printf("-- TC-CONLOG-110..115 fputc 重定向(printf 遗留路径) --\n");
printf("-- TC-CONLOG-110..115 fputc retarget (legacy printf path) --\n");
reset_all();
CHECK(con_fputc_under_test('Q', stdout) == 'Q', "TC-CONLOG-110 fputc 原样返回字符(C 库约定)");
CHECK(con_fputc_under_test('Q', stdout) == 'Q', "TC-CONLOG-110 fputc returns the character unchanged (C library convention)");
CHECK(tx_is(0U, "Q") && used_now() == 0U,
"TC-CONLOG-111 未注册写者:printf 的字节直发(与旧行为一致)");
"TC-CONLOG-111 writer not attached: printf bytes go out directly (same as the old behaviour)");
reset_all();
attach_writer();
(void)con_fputc_under_test('R', stdout);
CHECK(con_uart_tx_n == 0U && used_now() == 1U && con_ring_store[0] == (uint8_t)'R',
"TC-CONLOG-112 注册后 fputc 也收敛到单写者路径(逐字节入环)");
"TC-CONLOG-112 after attach fputc also funnels into the single-writer path (byte by byte into the ring)");
writer_drain();
CHECK(tx_is(0U, "R"), "TC-CONLOG-113 写者把 printf 字节发出");
CHECK(tx_is(0U, "R"), "TC-CONLOG-113 the writer sends those printf bytes");
reset_all();
attach_writer();
con_ipsr = 12U; /* 假异常号:任意非 0 */
CHECK(dbg_console_write_line("ISR", 3U) == 3, "TC-CONLOG-114 中断上下文写整行返回行长");
CHECK(dbg_console_write_line("ISR", 3U) == 3, "TC-CONLOG-114 writing a whole line from interrupt context returns the line length");
CHECK(tx_is(0U, "ISR") && used_now() == 0U && con_delay_calls == 0U,
"TC-CONLOG-115 中断上下文一律裸发:不入环、不等待(否则在 ISR 里睡死)");
"TC-CONLOG-115 interrupt context always sends raw: no ring entry, no waiting (else it sleeps inside the ISR)");
reset_all();
attach_writer();
@@ -947,59 +947,59 @@ static void tc_fputc(void)
con_ipsr = 5U;
con_dwt_step = SystemCoreClock / 4U;
CHECK(dbg_console_write_line("0123456789", 10U) == 10,
"TC-CONLOG-116 环全满 + 中断上下文:仍直发成功(ISR 绝不进丢弃等待)");
"TC-CONLOG-116 ring completely full + interrupt context: still sends directly (an ISR never enters the drop wait)");
CHECK(dbg_console_dropped() == 0U && con_delay_calls == 0U,
"TC-CONLOG-117 这条路径既不丢行也不记丢弃(K1:内核前/ISR 不依赖任何内核对象)");
"TC-CONLOG-117 this path loses no line and records no drop (K1: pre-kernel and ISR depend on no kernel object)");
}
/* ============ TC-CONLOG-120..122 drain 250ms 预算 ============ */
static void tc_budget(void)
{
printf("-- TC-CONLOG-120..122 单次 drain 预算 250ms(让位喂狗轮询) --\n");
printf("-- TC-CONLOG-120..122 single drain budget 250ms (yields to the watchdog poll) --\n");
reset_all();
attach_writer();
fill_ring(0U); /* 4095B = 16 块 */
con_tick_step = 130U; /* 每次读 tick 走 130ms */
writer_drain();
CHECK(con_uart_block_n == 2U, "TC-CONLOG-120 一轮只发 2 块就因预算让位(不是发完)");
CHECK(con_uart_block_n == 2U, "TC-CONLOG-120 one round sends only 2 blocks then yields on budget (not because it finished)");
CHECK(used_now() == (DBG_CON_RING_SIZE - 1U) - (2U * DBG_CON_CHUNK_MAX),
"TC-CONLOG-121 让位时余下 3583B 留在环里(下轮续排,不丢)");
"TC-CONLOG-121 when it yields, 3583B remain in the ring (next round continues, nothing lost)");
con_tick_step = 0U;
writer_drain(); /* 预算内发完余下 3583B */
CHECK(used_now() == 0U && con_uart_tx_n == (DBG_CON_RING_SIZE - 1U),
"TC-CONLOG-122 下轮把余下的 3583B 也发完:预算让位不丢字节(累计 4095)");
"TC-CONLOG-122 the next round sends the remaining 3583B too: yielding on budget loses no bytes (4095 total)");
}
/* ============ TC-CONLOG-130..135 计数支路的成功路径 / TC 抖动 / 直调 NULL ============ */
static void tc_counted_paths(void)
{
printf("-- TC-CONLOG-130..132 DWT 不可用 -> con_tx_counted 也能发全(不只会超时) --\n");
printf("-- TC-CONLOG-130..132 DWT unavailable -> con_tx_counted can also finish a block (not only time out) --\n");
reset_all();
con_dwt_ctrl_sticky = 0; /* CYCCNTENA 落不住 */
con_tick_now = 9000U; /* tick 冻结:证明与本支路无关 */
dbg_console_write_raw("AB", 2U);
CHECK(s_con_dwt_up == 0U && tx_is(0U, "AB"),
"TC-CONLOG-130 计数支路把整块发完并返回成功(回退路径没退化)");
"TC-CONLOG-130 the counted branch sends the whole block and returns success (fallback path did not regress)");
CHECK(con_uart_tc_reads == 1U && con_dwt_reads == 3U && con_tick_reads == 0U,
"TC-CONLOG-131 计数支路既不等 tick 也不再读 CYCCNT(零外部时间源依赖)");
"TC-CONLOG-131 the counted branch waits no tick and reads no CYCCNT (zero external time source)");
reset_all();
con_dwt_ctrl_sticky = 0;
con_uart_tc_stuck = 1; /* 字节能进 DR,TC 永不置位 */
dbg_console_write_raw("CD", 2U);
CHECK(tx_is(0U, "CD") && con_uart_tc_reads == CON_TX_GUARD_MAX,
"TC-CONLOG-132 TC 卡死时计数支路同样最多自旋 CON_TX_GUARD_MAX 次即放弃");
"TC-CONLOG-132 with TC stuck the counted branch also gives up after at most CON_TX_GUARD_MAX spins");
printf("-- TC-CONLOG-133..135 DWT 支路 TC 抖动 / con_append 直调参数校验 --\n");
printf("-- TC-CONLOG-133..135 DWT branch TC jitter / con_append direct-call argument checks --\n");
reset_all();
con_dwt_step = 1U; /* 时间走得极慢:不会误判超时 */
con_uart_tc_after = 2; /* 前两轮 TC 不就绪 */
dbg_console_write_raw("EF", 2U);
CHECK(tx_is(0U, "EF") && con_uart_tc_reads == 3U,
"TC-CONLOG-133 TC 抖动(未超上界)后仍算整块成功:TC 循环能正常退出");
"TC-CONLOG-133 TC jitter (below the ceiling) still ends as a successful block: the TC loop exits normally");
reset_all();
CHECK(con_append(0, 3U) == -1, "TC-CONLOG-134 con_append(NULL) 自己也要挡住(不只靠上层)");
CHECK(con_append("abc", 0U) == -1, "TC-CONLOG-135 con_append(len=0) 同样拒绝");
CHECK(con_append(0, 3U) == -1, "TC-CONLOG-134 con_append(NULL) must block it itself (not only relying on the caller)");
CHECK(con_append("abc", 0U) == -1, "TC-CONLOG-135 con_append(len=0) is rejected the same way");
}
+29 -29
View File
@@ -58,11 +58,11 @@ static const ftp_auth_creds_t s_creds = { "admin", DEF_HASH, 1u };
static void tc_user_match(void)
{
CHECK(ftp_auth_user_match("admin", "admin") == 1, "TC-FTPAUTH-001a 相等匹配");
CHECK(ftp_auth_user_match("admin", "ADMIN") == 1, "TC-FTPAUTH-001b 大小写不敏感");
CHECK(ftp_auth_user_match("admin", "root") == 0, "TC-FTPAUTH-001c 不等拒绝");
CHECK(ftp_auth_user_match("admin", "admin2") == 0, "TC-FTPAUTH-001d 前缀不算匹配");
CHECK(ftp_auth_user_match("admin", NULL) == 0, "TC-FTPAUTH-001e NULL 拒绝");
CHECK(ftp_auth_user_match("admin", "admin") == 1, "TC-FTPAUTH-001a equal strings match");
CHECK(ftp_auth_user_match("admin", "ADMIN") == 1, "TC-FTPAUTH-001b case-insensitive");
CHECK(ftp_auth_user_match("admin", "root") == 0, "TC-FTPAUTH-001c mismatch rejected");
CHECK(ftp_auth_user_match("admin", "admin2") == 0, "TC-FTPAUTH-001d prefix does not count as match");
CHECK(ftp_auth_user_match("admin", NULL) == 0, "TC-FTPAUTH-001e NULL rejected");
}
/* ===================== TC-FTPAUTH-002..004:基本判定 ===================== */
@@ -73,17 +73,17 @@ static void tc_basic(void)
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 1000u) == FTP_AUTH_OK,
"TC-FTPAUTH-002 对用户名+对口令 → OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-002b 成功后计数为 0");
"TC-FTPAUTH-002 right user + right password -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-002b counter is 0 after success");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-003 错口令 → FAIL");
CHECK(st.fail_count == 1u, "TC-FTPAUTH-003b 失败计数 1");
"TC-FTPAUTH-003 wrong password -> FAIL");
CHECK(st.fail_count == 1u, "TC-FTPAUTH-003b fail count = 1");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 0u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-004 错用户名+对口令 → FAIL(哈希路径已走)");
"TC-FTPAUTH-004 wrong user + right password -> FAIL (hash path taken)");
}
/* ===================== TC-FTPAUTH-005..006:锁定与解锁 ===================== */
@@ -96,22 +96,22 @@ static void tc_lock(void)
(void)memset(&st, 0, sizeof(st));
for (int i = 0; i < 5; i++) {
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-005 第 1..5 次失败 → FAIL");
"TC-FTPAUTH-005 attempts 1..5 -> FAIL");
}
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005b 计数到达 FAIL_MAX");
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005b counter reaches FAIL_MAX");
/* 锁定期内:对口令也 LOCKED,且不再计数 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t + 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-005c 锁定期内对口令 → LOCKED");
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005d LOCKED 不改变计数");
"TC-FTPAUTH-005c right password during lock window -> LOCKED");
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005d LOCKED does not change the counter");
/* 59999 tick:仍在窗内(差值 < 60000) */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st,
st.lock_until_tick - 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-005e 窗内最后 1 tick 仍 LOCKED");
"TC-FTPAUTH-005e last tick inside window still LOCKED");
/* 期满:恰好到达 lock_until → 放行 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st,
st.lock_until_tick) == FTP_AUTH_OK,
"TC-FTPAUTH-006 期满对口令 → OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-006b 解锁成功后计数清零");
"TC-FTPAUTH-006 right password after lock expiry -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-006b counter cleared after unlock then success");
}
/* ===================== TC-FTPAUTH-007:成功清零计数 ===================== */
@@ -126,16 +126,16 @@ static void tc_reset(void)
(void)ftp_auth_check(&s_creds, 1u, "wrong", &st, t);
}
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t) == FTP_AUTH_OK,
"TC-FTPAUTH-007a 3 败后成功 → OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-007b 成功清零");
"TC-FTPAUTH-007a success after 3 failures -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-007b success clears the counter");
for (int i = 0; i < 4; i++) {
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-007c 清零后再 4 败仍 FAIL(未锁)");
"TC-FTPAUTH-007c after clear, 4 more failures still FAIL (not locked)");
}
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-007d 第 5 败 FAIL 并上锁");
"TC-FTPAUTH-007d 5th failure FAILs and arms the lock");
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t + 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-007e 连续 5 败后 LOCKED");
"TC-FTPAUTH-007e LOCKED after 5 consecutive failures");
}
/* ===================== TC-FTPAUTH-008:tick 32 位回绕 ===================== */
@@ -150,10 +150,10 @@ static void tc_wraparound(void)
st.lock_until_tick = 0xFFFFFFF0u;
/* 回绕前 240 tick:仍在窗内 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 0xFFFFFF00u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-008a 回绕前窗内 → LOCKED");
"TC-FTPAUTH-008a inside window before wraparound -> LOCKED");
/* 回绕后 32 tick:now=0x10,(int32_t)(0x10-0xFFFFFFF0)=+32 → 期满 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 0x00000010u) == FTP_AUTH_OK,
"TC-FTPAUTH-008b 回绕后期满 → OK(有符号差抗回绕)");
"TC-FTPAUTH-008b after wraparound window elapsed -> OK (signed diff resists wraparound)");
}
/* ===================== TC-FTPAUTH-009..010:fail-closed 边界 ===================== */
@@ -165,17 +165,17 @@ static void tc_fail_closed(void)
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&bad_creds, 1u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-009 非法 hex 配置 → 恒 FAIL(无人能登录,fail-closed)");
"TC-FTPAUTH-009 invalid hex config -> always FAIL (nobody can log in, fail-closed)");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010a 空口令 → FAIL");
"TC-FTPAUTH-010a empty password -> FAIL");
CHECK(ftp_auth_check(&s_creds, 1u, NULL, &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010b NULL 口令 → FAIL");
"TC-FTPAUTH-010b NULL password -> FAIL");
CHECK(ftp_auth_check(NULL, 1u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010c NULL creds → FAIL");
"TC-FTPAUTH-010c NULL creds -> FAIL");
CHECK(ftp_auth_check(&s_creds, 1u, "admin", NULL, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010d NULL state → FAIL");
"TC-FTPAUTH-010d NULL state -> FAIL");
}
/* ===================== main ===================== */
+16 -16
View File
@@ -148,9 +148,9 @@ static void tc_register(void)
printf("-- TC-FTPCFG-003..005 register --\n");
fake_reset();
ftp_cfg_register();
CHECK(g_reg_n == 1, "TC-FTPCFG-003a 首次注册一次");
CHECK(g_reg_n == 1, "TC-FTPCFG-003a first register adds exactly one entry");
ftp_cfg_register();
CHECK(g_reg_n == 1, "TC-FTPCFG-003 重复 register 不重复登记(幂等)");
CHECK(g_reg_n == 1, "TC-FTPCFG-003 repeated register never duplicates the entry (idempotent)");
CHECK((strcmp(g_reg.name, "ftp") == 0) &&
(g_reg.data_size == sizeof(ftp_cfg_t)) &&
@@ -158,7 +158,7 @@ static void tc_register(void)
(g_reg.p_defaults != NULL) &&
(g_reg.p_data == (const void *)&s_cfg) &&
(g_reg.p_cb == NULL),
"TC-FTPCFG-004 段名/尺寸/键数/默认值/**p_cb 必须为 NULL**");
"TC-FTPCFG-004 section name/size/key count/defaults/**p_cb must be NULL**");
CHECK((strcmp(g_reg.p_keys[0].name, "user") == 0) &&
(g_reg.p_keys[0].type == CFG_VAL_STR) &&
@@ -170,7 +170,7 @@ static void tc_register(void)
(strcmp(g_reg.p_keys[2].name, "write") == 0) &&
(g_reg.p_keys[2].type == CFG_VAL_UINT) &&
(g_reg.p_keys[2].len == 1u),
"TC-FTPCFG-005 键表逐项:user(STR,16)/pwd_hash(STR,65)/write(UINT,1)");
"TC-FTPCFG-005 key table item by item: user(STR,16)/pwd_hash(STR,65)/write(UINT,1)");
}
/* ===================== 出厂默认与只读访问 ===================== */
@@ -182,17 +182,17 @@ static void tc_defaults(void)
printf("-- TC-FTPCFG-001..002,014 defaults --\n");
/* 契约就是"恒非空"(静态常驻),单独一条断言;随后的显式守卫既让
* test-cppcheck 不再报"冗余或空指针二选一",也让真出问题时报在 001a 上 */
CHECK(p != NULL, "TC-FTPCFG-001a ftp_cfg_get 恒非空(静态常驻)");
CHECK(p != NULL, "TC-FTPCFG-001a ftp_cfg_get never NULL (statically resident)");
if (p == NULL) {
return;
}
CHECK(strcmp(p->user, "admin") == 0, "TC-FTPCFG-001 未注册也可 get,user = admin");
CHECK(strcmp(p->user, "admin") == 0, "TC-FTPCFG-001 get works even unregistered, user = admin");
CHECK((strcmp(p->pwd_hash, FTP_CFG_DEF_HASH) == 0) && (p->write_en == 1u),
"TC-FTPCFG-002 初值 = 出厂哈希 + write_en=1(维护工作流依赖)");
"TC-FTPCFG-002 initial value = factory hash + write_en=1 (maintenance workflow relies on it)");
CHECK((FTP_CFG_USER_MAX == 16u) && (FTP_CFG_HASH_MAX == 65u) &&
(sizeof(p->user) == FTP_CFG_USER_MAX) &&
(sizeof(p->pwd_hash) == FTP_CFG_HASH_MAX),
"TC-FTPCFG-014 缓冲尺寸契约:user 16 / hash 65(64 hex + NUL)");
"TC-FTPCFG-014 buffer size contract: user 16 / hash 65 (64 hex + NUL)");
}
/* ===================== 默认口令判定(启动告警的依据) ===================== */
@@ -200,17 +200,17 @@ static void tc_defaults(void)
static void tc_default_pwd(void)
{
printf("-- TC-FTPCFG-006..009 default-password detection --\n");
CHECK(ftp_cfg_is_default_pwd() == 1, "TC-FTPCFG-006 出厂态判为默认口令");
CHECK(ftp_cfg_is_default_pwd() == 1, "TC-FTPCFG-006 factory state judged as default password");
set_hash("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
CHECK(ftp_cfg_is_default_pwd() == 0, "TC-FTPCFG-007 改了口令 → 不再默认");
CHECK(ftp_cfg_is_default_pwd() == 0, "TC-FTPCFG-007 password changed -> no longer default");
set_hash_upper();
CHECK(ftp_cfg_is_default_pwd() == 1,
"TC-FTPCFG-008 大写 hex 仍判默认(比较必须大小写不敏感)");
"TC-FTPCFG-008 uppercase hex still judged default (comparison must be case-insensitive)");
set_hash("");
CHECK(ftp_cfg_is_default_pwd() == 0, "TC-FTPCFG-009 空哈希不是默认口令");
CHECK(ftp_cfg_is_default_pwd() == 0, "TC-FTPCFG-009 empty hash is not the default password");
}
/* ===================== 静态比较函数(NULL / 折叠 / 前缀) ===================== */
@@ -219,12 +219,12 @@ static void tc_ci_eq(void)
{
printf("-- TC-FTPCFG-010..013 case-insensitive compare --\n");
CHECK(s_str_ci_eq("A_b1!Z", "a_b1!z") == 1,
"TC-FTPCFG-010 只折叠 A-Z,数字/下划线/符号原样比较");
CHECK(s_str_ci_eq("abc", "abcd") == 0, "TC-FTPCFG-011 前缀关系不算相等");
"TC-FTPCFG-010 only folds A-Z; digits/underscore/symbols compared as-is");
CHECK(s_str_ci_eq("abc", "abcd") == 0, "TC-FTPCFG-011 prefix relation is not equality");
CHECK((s_str_ci_eq(NULL, "x") == 0) && (s_str_ci_eq("x", NULL) == 0),
"TC-FTPCFG-012 任一为 NULL 判不等");
"TC-FTPCFG-012 either side NULL -> not equal");
CHECK(s_str_ci_eq(NULL, NULL) == 0,
"TC-FTPCFG-013 两个 NULL 也判不等(fail-closed)");
"TC-FTPCFG-013 both NULL also judged not equal (fail-closed)");
}
/* ===================== 入口 ===================== */
+94 -94
View File
@@ -315,17 +315,17 @@ static void tc_cp56(void)
iec104_epoch_ms_to_cp56(e1, b);
rc = iec104_cp56_to_epoch_ms(b, &back);
CHECK((rc == 0) && (back == e1), "TC-001 CP56 往返 2000-01-01");
CHECK((rc == 0) && (back == e1), "TC-001 CP56 round trip on 2000-01-01");
iec104_epoch_ms_to_cp56(e2, b);
back = 0u;
rc = iec104_cp56_to_epoch_ms(b, &back);
CHECK((rc == 0) && (back == e2), "TC-001 CP56 往返闰日 2024-02-29");
CHECK((rc == 0) && (back == e2), "TC-001 CP56 round trip on the leap day 2024-02-29");
b[2] |= 0x80u;
CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-001 IV → -2");
CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-001 IV -> -2");
iec104_epoch_ms_to_cp56(0u, b); /* 早于 2000 → 钳位 */
back = 0u;
(void)iec104_cp56_to_epoch_ms(b, &back);
CHECK(back == e1, "TC-001 早于 2000 钳位");
CHECK(back == e1, "TC-001 a value before 2000 is clamped");
}
static void tc_ioa_float(void)
@@ -334,12 +334,12 @@ static void tc_ioa_float(void)
iec104_put_ioa(b, 0x123456u);
CHECK((iec104_get_ioa(b) == 0x123456u) && (b[0] == 0x56u) && (b[1] == 0x34u)
&& (b[2] == 0x12u), "TC-002 IOA 3B 小端");
&& (b[2] == 0x12u), "TC-002 IOA is 3 bytes little endian");
iec104_put_float(b, 1.0f);
CHECK((b[0] == 0x00u) && (b[1] == 0x00u) && (b[2] == 0x80u)
&& (b[3] == 0x3Fu), "TC-002 float IEEE754 小端");
&& (b[3] == 0x3Fu), "TC-002 float in IEEE754 little endian");
iec104_put_float(b, -12.5f);
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float 往返");
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float round trip");
}
static void tc_apci(void)
@@ -351,9 +351,9 @@ static void tc_apci(void)
static const uint8_t b[6] = { 0x68u, 4u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 3u, now);
CHECK(cap_u_count(0x0Bu) == 0, "TC-003 半帧不触发");
CHECK(cap_u_count(0x0Bu) == 0, "TC-003 a half frame triggers nothing");
iec104_link_on_rx(&g_lk, &b[3], 3u, now);
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 半帧重组 → con");
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 a half frame is reassembled then con");
}
{
static const uint8_t b[12] = {
@@ -364,21 +364,21 @@ static void tc_apci(void)
iec104_link_on_rx(&g_lk, b, 12u, now);
CHECK((cap_u_count(0x83u) == 1)
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED),
"TC-003 粘帧逐处 + STOPDT 生效");
"TC-003 a coalesced frame is handled one by one and STOPDT takes effect");
}
reset_all(now);
{
static const uint8_t b[6] = { 0x69u, 4u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 6u, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏起始符 → IDLE");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 a bad start byte -> IDLE");
}
reset_all(now);
{
static const uint8_t b[6] = { 0x68u, 3u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 6u, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏长度 → IDLE");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 a bad length -> IDLE");
}
}
@@ -387,10 +387,10 @@ static void tc_startdt(void)
uint32_t now = 1000u;
reset_all(now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-004 open → OPEN");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-004 open -> OPEN");
handshake(now);
CHECK((cap_u_count(0x0Bu) == 1)
&& (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-004 STARTDT → RUN");
&& (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-004 STARTDT -> RUN");
}
static void tc_open_ignore(void)
@@ -403,11 +403,11 @@ static void tc_open_ignore(void)
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i_count() == 0) && (iec104_link_state(&g_lk) == IEC104_ST_OPEN),
"TC-005 OPEN 态 I 帧忽略");
"TC-005 an I frame in OPEN state is ignored");
handshake(now);
m_ns = 0u; /* OPEN 忽略不消费序号 → 主站仍从 0 起 */
feed_i(a, n, now); /* ns=0 仍有效(序号未被消费) */
CHECK(cap_i_count() == 1, "TC-005 RUN 后同 ns 正常应答");
CHECK(cap_i_count() == 1, "TC-005 after RUN the same ns is answered normally");
}
static void tc_interrogation(void)
@@ -426,34 +426,34 @@ static void tc_interrogation(void)
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 100u)
&& (c.cot == IEC104_COT_ACTCON), "TC-006 actcon 即发 COT7");
&& (c.cot == IEC104_COT_ACTCON), "TC-006 actcon is sent right away, COT7");
iec104_link_poll(&g_lk, now);
CHECK(cap_i_count() == 5, "TC-006 首 poll 发满预算 4 分片");
CHECK(cap_i_count() == 5, "TC-006 the first poll uses the whole budget of 4 frames");
CHECK((cap_i(1, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 4u)) && (c.ioa == 1u),
"TC-006 YX 双点组 首召 COT=4");
"TC-006 YX double-point group, first call COT=4");
CHECK((cap_i(2, &c) == 0) && (c.type == 1u)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 29u)) && (c.ioa == 5u),
"TC-006 YX 单点组 29 点");
"TC-006 YX single-point group with 29 points");
CHECK((cap_i(3, &c) == 0) && (c.type == 13u)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 8u)) && (c.ioa == 0x4011u)
&& (c.obj_len == 40u), "TC-006 YC 组 8 点 40B");
&& (c.obj_len == 40u), "TC-006 YC group of 8 points, 40 bytes");
if (cap_i(3, &c) == 0) {
CHECK((iec104_get_float(c.obj) == 111.0f) && (c.obj[4] == 0u),
"TC-006 YC 值 111.0 QDS=0");
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 无效通道 QDS=IV");
"TC-006 YC value 111.0 with QDS=0");
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 an invalid channel sets QDS=IV");
}
CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u),
"TC-006 频率单点 SQ=0");
"TC-006 frequency as a single point with SQ=0");
iec104_link_poll(&g_lk, now + 100u);
CHECK((cap_i_count() == 6) && (cap_i(5, &c) == 0) && (c.type == 100u)
&& (c.cot == IEC104_COT_ACTTERM), "TC-006 actterm 收尾");
&& (c.cot == IEC104_COT_ACTTERM), "TC-006 actterm closes the sequence");
/* 二召:init 已清 → COT=2 */
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now + 200u);
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i(7, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_BG_SCAN),
"TC-006 二召 COT=2");
"TC-006 second call COT=2");
/* RBT 位 → COT=4(actcon3 是第 12 个未确认帧 → 先 S 确认窗才开) */
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, IEC104_COT_BIT_RBT,
1u, 0u, body, 1u);
@@ -461,7 +461,7 @@ static void tc_interrogation(void)
feed_s(12u, now + 300u);
iec104_link_poll(&g_lk, now + 300u);
CHECK((cap_i(12, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT),
"TC-006 RBT → COT=4");
"TC-006 RBT -> COT=4");
}
static void tc_cov_yx(void)
@@ -473,33 +473,33 @@ static void tc_cov_yx(void)
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now); /* 建快照 */
CHECK(cap_i_count() == 0, "TC-007 首 poll 无自发");
CHECK(cap_i_count() == 0, "TC-007 the first poll sends nothing spontaneous");
s_f_bits |= (1uLL << 15); /* YX16(idx15)动作 */
iec104_link_poll(&g_lk, now + 100u);
/* B9:单点 YX 自发 = M_SP_TB_1(30)带 CP56Time2a 时标(SOE 上送路径) */
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 30u)
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 16u)
&& (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-007 YX16 变位 COT3 TB");
&& (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-007 a YX16 change of state sends COT3 with TB");
if ((cap_i(0, &c) == 0) && (c.obj_len == 8u)) {
CHECK(iec104_cp56_to_epoch_ms(&c.obj[1], &back) == 0,
"TC-007 CP56 可解");
CHECK(back == s_epoch_val, "TC-007 时标 = epoch_ms 源");
"TC-007 the CP56 time tag decodes");
CHECK(back == s_epoch_val, "TC-007 the time tag equals the epoch_ms source");
}
s_f_bits &= ~(1uLL << 15); /* 复归 */
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i_count() == 2) && (cap_i(1, &c) == 0) && (c.type == 30u)
&& (c.ioa == 16u) && (c.obj[0] == 0u), "TC-007 YX16 复归再变");
&& (c.ioa == 16u) && (c.obj[0] == 0u), "TC-007 YX16 is reset and changes again");
/* B9 IV 路径:时标源无效 → CP56 IV 位如实置位 */
s_epoch_val = UINT64_MAX;
s_f_bits |= (1uLL << 16); /* YX17(idx16)动作 */
iec104_link_poll(&g_lk, now + 300u);
CHECK((cap_i_count() == 3) && (cap_i(2, &c) == 0) && (c.type == 30u)
&& (c.ioa == 17u) && ((c.obj[3] & 0x80u) != 0u),
"TC-007 时钟无效 → CP56 IV 位");
"TC-007 an invalid clock sets the CP56 IV bit");
s_epoch_val = 1760234567000ULL;
s_f_bits |= (1uLL << 0); /* idx0 无效位 → 不触发 */
iec104_link_poll(&g_lk, now + 400u);
CHECK(cap_i_count() == 3, "TC-007 无效位变位不触发");
CHECK(cap_i_count() == 3, "TC-007 a change while the IV bit is set triggers nothing");
}
static void tc_cov_yc(void)
@@ -513,12 +513,12 @@ static void tc_cov_yc(void)
iec104_link_poll(&g_lk, now); /* 快照 = 100 */
s_f_yc[0] = 105.0f; /* 死区 10 内 */
iec104_link_poll(&g_lk, now + 100u);
CHECK(cap_i_count() == 0, "TC-008 死区内不上送");
CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded");
s_f_yc[0] = 130.0f; /* 130-105=25 > 10 */
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 13u)
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 0x4011u)
&& (iec104_get_float(c.obj) == 130.0f), "TC-008 越死区上送");
&& (iec104_get_float(c.obj) == 130.0f), "TC-008 crossing the deadband uploads");
}
static void feed_rd(uint32_t now)
@@ -540,12 +540,12 @@ static void tc_k_window(void)
for (i = 0; i < 12; i++) {
feed_rd(now);
}
CHECK(cap_i_count() == 12, "TC-009 k=12 封顶");
CHECK(cap_i_count() == 12, "TC-009 the k=12 window caps the outstanding frames");
feed_rd(now);
CHECK(cap_i_count() == 12, "TC-009 第 13 个被 k 窗抑制");
CHECK(cap_i_count() == 12, "TC-009 the 13th frame is suppressed by the k window");
feed_s(12u, now); /* 全确认 → 窗开 */
feed_rd(now);
CHECK(cap_i_count() == 13, "TC-009 S 确认后恢复发送");
CHECK(cap_i_count() == 13, "TC-009 sending resumes after the S acknowledgement");
}
static void tc_w_thresh(void)
@@ -559,9 +559,9 @@ static void tc_w_thresh(void)
for (i = 0; i < 7; i++) {
feed_rd(now);
}
CHECK(cap_has_s(&nr) == 0, "TC-010 收 7 未发 S");
CHECK(cap_has_s(&nr) == 0, "TC-010 after 7 received frames no S has been sent yet");
feed_rd(now); /* 第 8 个 → w 阈 */
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 → S nr=8");
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 -> S with nr=8");
}
static void tc_t2(void)
@@ -573,9 +573,9 @@ static void tc_t2(void)
handshake(now);
feed_rd(now); /* rx_unacked=1,t2 起爆 */
iec104_link_poll(&g_lk, now + 9999u);
CHECK(cap_has_s(&nr) == 0, "TC-011 t2 未到期不发 S");
CHECK(cap_has_s(&nr) == 0, "TC-011 no S before t2 expires");
iec104_link_poll(&g_lk, now + 10001u);
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 t2 到期发 S");
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 S is sent once t2 expires");
}
static void tc_t3_t1(void)
@@ -585,19 +585,19 @@ static void tc_t3_t1(void)
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 19999u);
CHECK(cap_u_count(0x43u) == 0, "TC-012 t3 未到期无 TESTFR");
CHECK(cap_u_count(0x43u) == 0, "TC-012 no TESTFR before t3 expires");
iec104_link_poll(&g_lk, now + 20001u);
CHECK(cap_u_count(0x43u) == 1, "TC-012 t3 到期发 TESTFR act");
CHECK(cap_u_count(0x43u) == 1, "TC-012 TESTFR act is sent once t3 expires");
iec104_link_poll(&g_lk, now + 20001u + 15001u);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 TESTFR 未答 t1 → IDLE");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 an unanswered TESTFR hits t1 -> IDLE");
/* con 清挂起 → 不误杀 */
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 20001u);
CHECK(cap_u_count(0x43u) == 1, "TC-012 重连 t3 再触发");
CHECK(cap_u_count(0x43u) == 1, "TC-012 after reconnect t3 fires again");
feed_u(0x83u, now + 20100u); /* TESTFR con */
iec104_link_poll(&g_lk, now + 35000u);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-012 con 清挂起后 t1 不误杀");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-012 once con clears the pending frame t1 does not kill it by mistake");
}
static void tc_dc(void)
@@ -614,13 +614,13 @@ static void tc_dc(void)
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 52u) && (c.cot == IEC104_COT_ACTCON)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-013 选择段 actcon");
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 选择段转发源");
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-013 the select phase answers actcon");
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 the select phase forwards the source");
body[0] = 0x02u; /* 执行 ON */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u)
&& (s_f_yk_sel == 0u), "TC-013 执行段 actcon");
&& (s_f_yk_sel == 0u), "TC-013 the execute phase answers actcon");
/* 失配:选 ON 执 OFF */
body[0] = 0x82u;
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
@@ -629,13 +629,13 @@ static void tc_dc(void)
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-013 执行失配 PN");
"TC-013 a mismatching execute phase returns PN");
/* 拒绝类(YK1):选择段即 PN */
body[0] = 0x82u;
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x6000u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-013 无出口硬件 YK 选择段 PN");
"TC-013 a YK without output hardware returns PN in the select phase");
}
/* TC-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */
@@ -649,21 +649,21 @@ static void tc_dc_yk12(void)
reset_all(now);
handshake(now);
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板默认投入");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 soft plates are engaged by default");
body[0] = 0x81u; /* S=1, DPC=OFF:选择段 */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.cot == IEC104_COT_ACTCON)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-024 YK12 选择段 actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 选择段不动作");
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-024 the YK12 select phase answers actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the select phase performs no action");
body[0] = 0x01u; /* 执行 OFF */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
"TC-024 YK12 执行段 actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 执行段软压板转真退出");
CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed 原子封存 → selfcheck 通过");
CHECK(prot_set_dirty_peek() == 1u, "TC-024 置脏待持久化");
"TC-024 the YK12 execute phase answers actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 the execute phase drives the soft plate to the released state");
CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed seals atomically and selfcheck passes");
CHECK(prot_set_dirty_peek() == 1u, "TC-024 the dirty flag is set for persistence");
body[0] = 0x82u; /* 再投入:选择 ON */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
feed_i(a, n, now);
@@ -671,13 +671,13 @@ static void tc_dc_yk12(void)
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
"TC-024 投入执行 actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板转真投入");
"TC-024 the engage command executes with actcon");
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the soft plate reaches the engaged state");
body[0] = 0x80u; /* DPC=00 非法:选择段即 PN */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-024 非法 DPC 选择段 PN");
"TC-024 an invalid DPC returns PN in the select phase");
}
static void tc_se(void)
@@ -691,27 +691,27 @@ static void tc_se(void)
reset_all(now);
handshake(now);
CHECK(addr == 0x8858u, "TC-014 OC1 电流定值通讯地址");
CHECK(addr == 0x8858u, "TC-014 the OC1 current setting communication address");
iec104_put_float(body, 5.0f);
body[4] = IEC104_QDC_S; /* 选择段 */
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 50u)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-014 定值选择 actcon");
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-014 selecting a setting answers actcon");
body[4] = 0u; /* 执行段 */
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
"TC-014 定值执行 actcon");
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 定值已写入(真实 prot_set)");
CHECK(prot_set_selfcheck() == 0, "TC-014 执行后 seal → selfcheck 通过");
CHECK(prot_set_dirty_peek() == 1u, "TC-014 执行段置脏(B7 待落盘)");
"TC-014 executing a setting answers actcon");
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 the setting really was written (real prot_set)");
CHECK(prot_set_selfcheck() == 0, "TC-014 sealing after execute keeps selfcheck passing");
CHECK(prot_set_dirty_peek() == 1u, "TC-014 the execute phase sets dirty (B7, waiting to be persisted)");
iec104_put_float(body, 100000.0f); /* 越上限 */
body[4] = IEC104_QDC_S;
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
feed_i(a, n, now);
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-014 越界选择段 PN");
"TC-014 an out-of-range select phase returns PN");
}
static void tc_rd(void)
@@ -727,20 +727,20 @@ static void tc_rd(void)
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_REQ)
&& (iec104_get_float(c.obj) == 222.0f), "TC-015 C_RD YC 命中");
&& (iec104_get_float(c.obj) == 222.0f), "TC-015 C_RD hits a YC group");
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x8858u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && (c.type == 13u) && (c.ioa == 0x8858u),
"TC-015 C_RD 定值命中");
"TC-015 C_RD hits a setting");
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x9999u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(2, &c) == 0) && (c.type == 102u)
&& (c.cot == IEC104_COT_UNK_IOA) && ((c.oa & 0x80u) != 0u),
"TC-015 未知 IOA COT14+EVI");
"TC-015 an unknown IOA answers COT14 plus EVI");
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(3, &c) == 0) && (c.cot == IEC104_COT_UNK_IOA),
"TC-015 本地定值项(comm_addr=0)不可寻址(B7)");
"TC-015 a local setting item (comm_addr=0) is not addressable (B7)");
}
static void tc_cs(void)
@@ -759,18 +759,18 @@ static void tc_cs(void)
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 103u)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u) && (s_clock_ms == e),
"TC-016 C_CS 对时成功 epoch 精确");
"TC-016 C_CS syncs the clock with an exact epoch");
cp[2] |= 0x80u; /* IV */
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-016 CP56 IV → PN");
"TC-016 CP56 IV -> PN");
(void)iec104_epoch_ms_to_cp56(e, cp);
s_clock_rc = -1; /* 系统侧拒绝 */
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
feed_i(a, n, now);
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-016 clock_sync 失败 → PN");
"TC-016 a clock_sync failure -> PN");
}
static void tc_err_resp(void)
@@ -785,13 +785,13 @@ static void tc_err_resp(void)
n = mk_asdu(a, 200u, 1u, IEC104_COT_ACT, 0u, 1u, 0u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 200u) && (c.cot == IEC104_COT_UNK_COT)
&& ((c.oa & 0x80u) != 0u), "TC-017 未知类型 COT12+EVI");
&& ((c.oa & 0x80u) != 0u), "TC-017 an unknown type answers COT12 plus EVI");
m = iec104_asdu_put_hdr(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 2u);
a[m] = IEC104_RQT_GROUP1;
m++;
feed_i(a, m, now); /* ca=2 ≠ 1 */
CHECK((cap_i(1, &c) == 0) && (c.cot == IEC104_COT_UNK_CA)
&& ((c.oa & 0x80u) != 0u), "TC-017 错公共地址 COT13+EVI");
&& ((c.oa & 0x80u) != 0u), "TC-017 a wrong common address answers COT13 plus EVI");
}
static void tc_stopdt(void)
@@ -809,9 +809,9 @@ static void tc_stopdt(void)
body[0] = IEC104_RQT_GROUP1;
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK(cap_i_count() == 0, "TC-018 STOPPED 态数据服务关");
CHECK(cap_i_count() == 0, "TC-018 in STOPPED state the data service is off");
feed_u(0x43u, now);
CHECK(cap_u_count(0x83u) == 1, "TC-018 STOPPED 态 TESTFR 仍应答");
CHECK(cap_u_count(0x83u) == 1, "TC-018 in STOPPED state TESTFR is still answered");
}
static void tc_seq_err(void)
@@ -825,7 +825,7 @@ static void tc_seq_err(void)
m_ns = 5u; /* 期望 0,投 5 */
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-019 序号错 → IDLE");
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-019 a sequence error -> IDLE");
}
static void tc_pts(void)
@@ -836,23 +836,23 @@ static void tc_pts(void)
reset_all(1000u);
CHECK((iec104_pts_yc_count() == 9u) && (iec104_pts_yx_count() == 33u)
&& (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-020 表尺寸/IOA");
&& (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-020 table size and IOA layout");
s_f_yc[0] = 7.5f;
CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (v == 7.5f) && (q == 0u),
"TC-020 有效 YC");
"TC-020 a valid YC");
s_f_yc_rc[1] = -1;
(void)iec104_pts_yc_read(1u, &v, &q);
CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-020 无效通道 IV");
CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-020 an invalid channel gives IV");
s_f_yc_rc[2] = -2;
(void)iec104_pts_yc_read(2u, &v, &q);
CHECK(q == IEC104_QDS_NT, "TC-020 未就绪 NT");
CHECK(q == IEC104_QDS_NT, "TC-020 not ready gives NT");
iec104_pts_yx_read(0u, &d);
CHECK(d == IEC104_DIQ_IV, "TC-020 无效遥信 IV");
CHECK(d == IEC104_DIQ_IV, "TC-020 an invalid telemetry point gives IV");
CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u),
"TC-020 is_dp 分界");
"TC-020 the is_dp boundary");
s_f_bits = (1uLL << 15);
iec104_pts_yx_read(15u, &d);
CHECK(d == 1u, "TC-020 有效遥信 ON");
CHECK(d == 1u, "TC-020 a valid telemetry point gives ON");
}
static void tc_ts_rp(void)
@@ -884,10 +884,10 @@ static void tc_periodic(void)
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 60001u);
CHECK(cap_i_count() == 2, "TC-022 周期上送 2 分片");
CHECK(cap_i_count() == 2, "TC-022 the cyclic upload takes 2 frames");
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_PERIODIC)
&& (c.ioa == 0x4011u), "TC-022 周期 YC 组 COT=1");
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 周期频率点");
&& (c.ioa == 0x4011u), "TC-022 the cyclic YC group uses COT=1");
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 the cyclic frequency point");
}
static void tc_cov_kfull(void)
@@ -904,11 +904,11 @@ static void tc_cov_kfull(void)
}
s_f_bits |= (1uLL << 14); /* YX15 变位 */
iec104_link_poll(&g_lk, now + 100u);
CHECK(cap_i_count() == 12, "TC-023 k 满时自发发不出");
CHECK(cap_i_count() == 12, "TC-023 with k full nothing spontaneous can go out");
feed_s(12u, now + 200u); /* 窗开 */
iec104_link_poll(&g_lk, now + 300u);
CHECK((cap_i_count() == 13) && (cap_i(12, &c) == 0) && (c.ioa == 15u)
&& (c.cot == IEC104_COT_SPONT), "TC-023 窗开后补发不丢事件");
&& (c.cot == IEC104_COT_SPONT), "TC-023 once the window opens the backlogged event is sent, none lost");
}
int main(void)
+29 -29
View File
@@ -221,7 +221,7 @@ void osDelay(uint32_t ticks)
(void)ticks;
g_n.delay_calls++;
if (g_n.delay_calls > 500) {
printf(" ABORT 跑飞(>500 轮 osDelay)\n");
printf(" ABORT runaway (>500 osDelay rounds)\n");
fflush(stdout);
exit(2);
}
@@ -278,22 +278,22 @@ static void tc_bind_listen(void)
(g_n.last_bind_family == NET_AF_INET) &&
(g_n.last_bind_port_net == net_htons(IEC104_SERV_PORT)) &&
(g_n.last_bind_addr == NET_INADDR_ANY),
"TC-I104SRV-001 建监听:2404 端口过 htons、绑任意地址、记下监听号");
"TC-I104SRV-001 build the listener: port 2404 through htons, bind to any address, remember the listen handle");
reset_all();
g_n.socket_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.bind_calls == 0) && (g_n.close_calls == 0),
"TC-I104SRV-002 建不出 socket 就返回,不去 bind 也不关不存在的口");
"TC-I104SRV-002 if no socket can be created return at once: no bind and no close of a handle that does not exist");
reset_all();
g_n.bind_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.close_calls == 1) && (g_n.listen_calls == 0),
"TC-I104SRV-003 bind 失败要把刚建的口关掉(否则每次重试漏一个 socket 号)");
"TC-I104SRV-003 a bind failure must close the socket just created (otherwise every retry leaks a handle)");
reset_all();
g_n.listen_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.close_calls == 1) && (s_listen == -1),
"TC-I104SRV-004 listen 失败同样关口且不留下半个监听");
"TC-I104SRV-004 a listen failure also closes it and leaves no half-built listener");
}
/* ===================== 发送与连续短写判死 ===================== */
@@ -307,25 +307,25 @@ static void tc_send(void)
s_client = -1;
s_send_apdu(NULL, apdu, 4u);
CHECK((g_n.send_rc == 0) && (g_n.close_calls == 0) && (g_i4_error_count == 0),
"TC-I104SRV-010 没连接时直接返回(不发 net_send、不报错)");
"TC-I104SRV-010 with no connection it returns directly (no net_send, no error)");
reset_all();
s_client = 7;
g_n.send_rc = 4;
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 0u) && (s_conn_dead == 0u),
"TC-I104SRV-011 完整发出把连败计数清零");
"TC-I104SRV-011 a fully sent frame clears the consecutive-failure counter");
reset_all();
s_client = 7;
g_n.send_rc = 2; /* 短写:只发了 2/4 */
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 1u) && (s_conn_dead == 0u) && (g_i4_error_count == 1),
"TC-I104SRV-012 第一次短写:留痕但不判死(拥塞很正常)");
"TC-I104SRV-012 the first short write is logged but does not declare death (congestion is normal)");
s_send_apdu(NULL, apdu, 4u);
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 3u) && (s_conn_dead == 1u),
"TC-I104SRV-012b 连到 3 次才判死(IEC104_SEND_FAIL_MAX)");
"TC-I104SRV-012b only the 3rd failure in a row declares death (IEC104_SEND_FAIL_MAX)");
reset_all();
s_client = 7;
@@ -338,7 +338,7 @@ static void tc_send(void)
s_send_apdu(NULL, apdu, 4u);
s_send_apdu(NULL, apdu, 4u);
CHECK((s_conn_dead == 0u) && (s_send_fail_run == 2u),
"TC-I104SRV-013 **成功即复位**:偶发拥塞不会被累计成误杀");
"TC-I104SRV-013 success resets the counter: occasional congestion never adds up into a false kill");
}
/* ===================== 时钟桥接边界 ===================== */
@@ -348,22 +348,22 @@ static void tc_clock(void)
printf("-- TC-I104SRV-020..023 clock bridge --\n");
reset_all();
CHECK(s_clock_sync(NULL, 0x100000000ULL * 1000ULL) == -1,
"TC-I104SRV-020 秒数超过 32 位一律拒绝(截断写进 RTC 就是 2106 年后的乱值)");
CHECK(g_clock_set_calls == 0, "TC-I104SRV-020b 拒绝时根本不该碰时钟");
"TC-I104SRV-020 any second count beyond 32 bits is rejected (truncating it into the RTC means garbage past 2106)");
CHECK(g_clock_set_calls == 0, "TC-I104SRV-020b when rejected the clock must not be touched at all");
reset_all();
(void)s_clock_sync(NULL, 1700000123000ULL);
CHECK((g_clock_set_calls == 1) && (g_clock_last_secs == 1700000123u),
"TC-I104SRV-021 毫秒按整除换秒后写时钟");
"TC-I104SRV-021 milliseconds become seconds by integer division before the clock is written");
reset_all();
g_clock_val = UINT32_MAX; /* 未对时的哨兵值 */
CHECK(s_epoch_ms(NULL) == UINT64_MAX,
"TC-I104SRV-022 未对时回 UINT64_MAX 哨兵(绝不能把 1970 当成设备时间)");
"TC-I104SRV-022 without time sync return the UINT64_MAX sentinel (1970 must never pass for device time)");
reset_all();
g_clock_val = 1700000123u;
CHECK(s_epoch_ms(NULL) == 1700000123000ULL, "TC-I104SRV-023 正常时对时给出毫秒");
CHECK(s_epoch_ms(NULL) == 1700000123000ULL, "TC-I104SRV-023 with a synced clock the answer is in milliseconds");
}
/* ===================== 代次重建与 accept 门控 ===================== */
@@ -379,13 +379,13 @@ static void tc_generation(void)
CHECK((g_n.close_calls == 2) && (g_n.last_closed_fd == 5) &&
(s_client == -1) && (s_liveness_ctr == 0u) && (s_conn_dead == 0u) &&
(g_lk_close == 1) && (s_gen == 101u) && (g_n.bind_calls == 1),
"TC-I104SRV-030 代次一变就拆掉旧连接与旧监听并重建(新 IP 上端口必须继续服务)");
"TC-I104SRV-030 a generation change tears down the old connection and listener and rebuilds them (the port must keep serving on the new IP)");
reset_all();
s_listen = -1;
s_serv_poll();
CHECK((g_n.bind_calls == 1) && (s_listen == 5) && (g_n.accept_calls == 0),
"TC-I104SRV-031 监听丢了就补建,这一轮不去 accept");
"TC-I104SRV-031 a lost listener is rebuilt and this round does not accept");
reset_all();
s_listen = 5;
@@ -393,7 +393,7 @@ static void tc_generation(void)
g_n.sock_exists = 1;
s_serv_poll();
CHECK((g_n.accept_calls == 0) && (s_listen == 5),
"TC-I104SRV-032 监听门未开时不 accept(否则对着未就绪的号空转)");
"TC-I104SRV-032 no accept while the listener gate is down (otherwise it spins on a handle that is not ready)");
reset_all();
s_listen = 5;
@@ -401,7 +401,7 @@ static void tc_generation(void)
g_n.sock_exists = 0; /* 表项已不存在 */
s_serv_poll();
CHECK((s_listen == -1) && (g_n.accept_calls == 0),
"TC-I104SRV-032b 表项已失效时把监听号丢掉,下一轮重建");
"TC-I104SRV-032b once the slot is invalid the listen handle is dropped and rebuilt next round");
reset_all();
s_listen = 5;
@@ -413,7 +413,7 @@ static void tc_generation(void)
CHECK((s_client == 9) && (g_lk_open == 1) && (g_lk_last_now == 5000u) &&
(s_send_fail_run == 0u) && (s_conn_dead == 0u) && (s_liveness_ctr == 0u) &&
(g_i4_info_count == 1),
"TC-I104SRV-033 新连接:打开链路、三个失败计数全部复位");
"TC-I104SRV-033 a new connection opens the link and resets all three failure counters");
}
/* ===================== 活性分频、对端 FIN、错误丢弃 ===================== */
@@ -428,7 +428,7 @@ static void tc_liveness(void)
s_liveness_ctr = 0u;
s_serv_poll();
CHECK((g_n.recv_calls == 0) && (g_lk_poll == 1) && (s_liveness_ctr == 1u),
"TC-I104SRV-034 无可读数据时按 32 分频探活,这一轮不收包(省 CPU 预算)");
"TC-I104SRV-034 with nothing readable the liveness probe runs on a divide-by-32 tick and this round receives nothing (saves CPU budget)");
reset_all();
s_listen = 5;
@@ -441,7 +441,7 @@ static void tc_liveness(void)
s_liveness_ctr = 31u; /* 下一轮正好到分频点 */
s_serv_poll();
CHECK((g_n.recv_calls == 1) && (g_lk_poll == 1) && (s_client == 9),
"TC-I104SRV-035 分频到点才去收一次(TESTFR 等活性帧靠它发出去)");
"TC-I104SRV-035 only when the divider fires does it receive once (liveness frames such as TESTFR depend on it going out)");
reset_all();
s_listen = 5;
@@ -451,7 +451,7 @@ static void tc_liveness(void)
s_serv_poll();
CHECK((g_n.last_closed_fd == 9) && (s_client == -1) && (g_lk_close == 1) &&
(g_n.close_calls == 1) && (s_listen == 5) && (g_n.bind_calls == 0),
"TC-I104SRV-036 对端正常断开:只关数据口、**监听必须留着**(单连接 socket 关错就是端口失联)");
"TC-I104SRV-036 a normal peer disconnect closes only the data socket: the listener must stay (on a single-socket chip closing the wrong one kills the port)");
reset_all();
s_listen = 5;
@@ -460,7 +460,7 @@ static void tc_liveness(void)
g_n.recv_rc = 0;
s_serv_poll();
CHECK((g_n.close_calls == 0) && (s_client == -1) && (g_lk_close == 1),
"TC-I104SRV-036b 数据号与监听号同号时**不许关**(否则把监听一起毁掉)");
"TC-I104SRV-036b when the data handle equals the listen handle it must NOT be closed (that would destroy the listener too)");
reset_all();
s_listen = 5;
@@ -470,7 +470,7 @@ static void tc_liveness(void)
g_n.errno_val = NET_ERR_WOULDBLOCK; /* 空转:没数据可读 */
s_serv_poll();
CHECK((s_client == 9) && (g_lk_close == 0) && (g_i4_error_count == 0),
"TC-I104SRV-037 WOULDBLOCK 是正常空转,不能当成错误丢连接");
"TC-I104SRV-037 WOULDBLOCK is normal idling and must not drop the connection as an error");
reset_all();
s_listen = 5;
@@ -481,7 +481,7 @@ static void tc_liveness(void)
s_serv_poll();
CHECK((s_client == -1) && (s_listen == 5) && (g_i4_error_count == 1) &&
(g_lk_close == 1) && (s_liveness_ctr == 0u),
"TC-I104SRV-038 真错误:丢连接并**当场重建监听**(不留空窗)");
"TC-I104SRV-038 a real error drops the connection and rebuilds the listener on the spot (no gap in service)");
reset_all();
s_listen = 5;
@@ -489,7 +489,7 @@ static void tc_liveness(void)
s_conn_dead = 1u;
s_service_link(1000u);
CHECK((s_client == -1) && (g_n.close_calls == 1) && (g_i4_error_count == 1),
"TC-I104SRV-039 判死后由 service_link 收口(发送失败线索单独成一条)");
"TC-I104SRV-039 after death is declared service_link does the teardown (the send-failure path is a separate case)");
reset_all();
s_listen = 5;
@@ -497,7 +497,7 @@ static void tc_liveness(void)
g_lk_state = IEC104_ST_IDLE; /* 链路层自己回到了未建立 */
s_service_link(1000u);
CHECK((s_client == -1) && (g_lk_close >= 1) && (s_listen == 5),
"TC-I104SRV-040 协议状态掉回 IDLE 也要丢并重生(否则服务挂着但永不服务)");
"TC-I104SRV-040 falling back to IDLE also drops and respawns the session (otherwise the service stays up but never serves)");
reset_all();
s_listen = 9;
@@ -508,7 +508,7 @@ static void tc_liveness(void)
g_n.recv_rc = 0;
s_serv_poll();
CHECK((s_client == -1) && (s_listen == -1) && (g_n.close_calls == 0),
"TC-I104SRV-041 表项没了又重建失败:丢掉假监听号,下一轮继续重试(数据号与监听同号时不许关)");
"TC-I104SRV-041 slot gone and the rebuild failed: drop the stale listen handle and retry next round (never close it while it equals the data handle)");
}
int main(void)
+36 -36
View File
@@ -334,7 +334,7 @@ void vTaskDelay(TickType_t t)
(void)t;
g_n.delay_calls++;
if (g_n.delay_calls > 200) {
printf(" ABORT 循环跑飞(>200 轮)\n");
printf(" ABORT loop runaway (>200 rounds)\n");
fflush(stdout);
exit(2);
}
@@ -450,46 +450,46 @@ static void tc_gates(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("220 ") == 1) && (rep_has("530 ") == 3) &&
(g_n.accept_calls == 0),
"TC-LFTPD-001 未登录只放行 USER/PASS/QUIT/FEAT/SYST/NOOP,其它一律 530");
"TC-LFTPD-001 before login only USER/PASS/QUIT/FEAT/SYST/NOOP pass, everything else gets 530");
reset_all();
feed("ABCDEFG", "NOOP", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("500 ") == 1) && (rep_has("200 ") == 1),
"TC-LFTPD-002 命令名 >=5 字符先拒(command_tmp 只有 5 格,防 memcpy 越界)");
"TC-LFTPD-002 a command name of 5 characters or more is rejected first (command_tmp has only 5 slots, this keeps memcpy in bounds)");
reset_all();
g_cli.auth_logged_in = 1; /* 否则会被认证白名单先以 530 拦掉,测不到分派 */
feed("FOO bar", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("502 ") == 1) && (rep_has("530 ") == 0),
"TC-LFTPD-003 认不出的命令回 502 而不是断开");
"TC-LFTPD-003 an unknown command answers 502 instead of dropping the connection");
reset_all();
feed("QUIT", "NOOP", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("221 ") == 1) && (rep_has("200 ") == 0) && (g_n.line_i == 1),
"TC-LFTPD-004 QUIT 之后不再读下一条(会话就此结束)");
"TC-LFTPD-004 after QUIT no further command is read (the session ends here)");
reset_all();
g_n.string_rc = -1; /* 220 都送不出去 */
feed("NOOP", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((g_n.line_i == 0) && (g_lg_info_count >= 1),
"TC-LFTPD-005 欢迎未送达(对端已走)就直接收工,不空转读命令(该留痕按真实分级算 INFO)");
"TC-LFTPD-005 if the welcome never got through (peer already gone) finish straight away instead of spinning on reads; that trace really is INFO");
reset_all();
g_cli.data_socket = 33; /* 被动模式已开但客户端直接跑了 */
feed("QUIT", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((g_n.close_calls == 1) && (g_cli.data_socket == -1),
"TC-LFTPD-006 退出时必须释放还开着的**数据监听**(单连接 socket 不能留残口)");
"TC-LFTPD-006 on exit the still-open data listener must be released (a single-socket chip must not be left with a dangling socket)");
reset_all();
g_cli.auth_logged_in = 1;
feed("noop", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK(rep_has("200 ") == 1, "TC-LFTPD-007 命令名大小写不敏感");
CHECK(rep_has("200 ") == 1, "TC-LFTPD-007 command names are case insensitive");
reset_all();
g_cli.auth_logged_in = 1;
@@ -499,7 +499,7 @@ static void tc_gates(void)
feed("LIST", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("425 ") == 1) && (g_n.accept_calls == 0),
"TC-LFTPD-010 没先开被动模式的数据命令一律 425,且不去 accept");
"TC-LFTPD-010 a data command without passive mode set first gets 425 and never reaches accept");
}
/* ===================== 被动模式与数据通道 ===================== */
@@ -514,7 +514,7 @@ static void tc_passive(void)
(void)cmd_pasv(&g_cli, NULL);
CHECK((strcmp(rep(0), "227 Entering Passive Mode (192,168,1,1,19,136).\r\n") == 0) &&
(g_cli.data_socket == 20) && (g_n.listen_calls == 1),
"TC-LFTPD-011 227 六元组:IP 经 htonl 后按高字节到低字节(点分序)、端口高/低,并记下被动监听号");
"TC-LFTPD-011 the 227 tuple: ip through htonl then high byte to low byte (dotted order), port high/low, and the passive listen handle is recorded");
reset_all();
g_cli.auth_logged_in = 1;
@@ -523,14 +523,14 @@ static void tc_passive(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("425 ") == 1) && (g_n.close_calls == 1) &&
(g_cli.data_socket == -1),
"TC-LFTPD-012 拿不到本机 IP 时要把刚建的监听关掉(别留半个被动模式)");
"TC-LFTPD-012 when the local ip is unavailable the listener just created must be closed (no half-passive mode left behind)");
reset_all();
g_cli.auth_logged_in = 1;
feed("EPSV", NULL, NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK(strcmp(rep(1), "229 Entering Extended Passive Mode (|||5000|).\r\n") == 0,
"TC-LFTPD-013 EPSV 只报端口(IPv6 客户端自己沿用控制连接地址)");
"TC-LFTPD-013 EPSV reports only the port (an IPv6 client reuses the control connection address)");
reset_all();
g_cli.auth_logged_in = 1;
@@ -540,7 +540,7 @@ static void tc_passive(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("150 ") == 1) && (rep_has("425 ") == 1) &&
(g_cli.data_socket == -1) && (g_n.close_calls == 1),
"TC-LFTPD-014 数据 accept 失败:报 425 并**关掉监听复位**,不留给下一条命令");
"TC-LFTPD-014 a failed data accept reports 425 and closes and resets the listener instead of leaving it to the next command");
reset_all();
g_cli.auth_logged_in = 1;
@@ -550,14 +550,14 @@ static void tc_passive(void)
feed("PASV", "LIST", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("226 ") == 1) && (g_n.data_write_rc == 0),
"TC-LFTPD-015 LIST 成功:先 150、数据口写目录、再 226 收尾");
"TC-LFTPD-015 LIST success: 150 first, the directory goes out on the data socket, then 226 closes it");
reset_all();
g_cli.auth_logged_in = 1;
g_io.opendir_rc = 1; /* 目录打不开 */
feed("PASV", "LIST", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK(rep_has("550 ") == 1, "TC-LFTPD-016 目录打不开时 LIST 回 550(不是 226)");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-016 when the directory cannot be opened LIST answers 550 (not 226)");
}
/* ===================== 传输与写开关 ===================== */
@@ -571,7 +571,7 @@ static void tc_transfer(void)
feed("PASV", "RETR a.txt", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("226 ") == 1) && (g_lg_info_count >= 1) && (g_wd_beats >= 1),
"TC-LFTPD-017 RETR 成功:226 收尾、留 DOWNLOAD 记录,且传输循环有喂狗");
"TC-LFTPD-017 RETR success: 226 at the end, a DOWNLOAD record is kept and the transfer loop feeds the watchdog");
reset_all();
g_cli.auth_logged_in = 1;
@@ -579,7 +579,7 @@ static void tc_transfer(void)
feed("PASV", "RETR gone.txt", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((rep_has("450 ") == 1) && (g_lg_error_count >= 1),
"TC-LFTPD-018 文件打不开回 450(传输失败与命令不合法要分开报)");
"TC-LFTPD-018 a file that cannot be opened answers 450 (a transfer failure and an invalid command must be reported apart)");
reset_all();
g_cli.auth_logged_in = 1;
@@ -588,7 +588,7 @@ static void tc_transfer(void)
feed("PASV", "STOR up.bin", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK((g_io.unlink_calls == 1) && (rep_has("450 ") == 1),
"TC-LFTPD-019 STOR 中途失败必须**删掉半成品**(否则 Flash 上永久留个坏文件)");
"TC-LFTPD-019 a STOR failing halfway must delete the partial file (otherwise a broken file stays on flash forever)");
reset_all();
g_cli.auth_logged_in = 1;
@@ -598,27 +598,27 @@ static void tc_transfer(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("550 ") == 1) && (g_n.accept_calls == 0) &&
(g_io.open_write_rc == 0) && (g_io.unlink_calls == 0),
"TC-LFTPD-020 写开关关闭时 STOR 在碰文件之前就拒绝,且不开数据通道");
"TC-LFTPD-020 with writes disabled STOR is refused before the file is touched and no data channel is opened");
reset_all();
g_cli.auth_logged_in = 1;
g_cfg.write_en = 0u;
(void)cmd_dele(&g_cli, "a.txt");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-021 写开关同样挡住 DELE");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-021 the write switch blocks DELE as well");
reset_all();
g_cli.auth_logged_in = 1;
g_io.is_dir = 1;
(void)cmd_dele(&g_cli, "sub");
CHECK((rep_has("550 ") == 1) && (g_io.unlink_calls == 0),
"TC-LFTPD-022 DELE 不许删目录(本实现只支持删文件)");
"TC-LFTPD-022 DELE must not delete directories (this implementation only deletes files)");
reset_all();
g_cli.auth_logged_in = 1;
g_io.is_dir = 0;
(void)cmd_dele(&g_cli, "a.txt");
CHECK((g_io.unlink_calls == 1) && (rep_has("250 ") == 1),
"TC-LFTPD-022b 删普通文件走 unlink 并回 250");
"TC-LFTPD-022b deleting a regular file goes through unlink and answers 250");
}
/* ===================== 目录与元信息 ===================== */
@@ -629,58 +629,58 @@ static void tc_meta(void)
reset_all();
(void)cmd_cwd(&g_cli, "");
CHECK((rep_has("550 ") == 1) && (strcmp(g_cli.directory, "/") == 0),
"TC-LFTPD-023 CWD 空参数拒绝,当前目录不变");
"TC-LFTPD-023 CWD with an empty argument is refused and the current directory stays");
reset_all();
g_io.is_dir = 0;
(void)cmd_cwd(&g_cli, "a.txt");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-023b CWD 到普通文件被拒(is_dir 必须真为目录)");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-023b CWD onto a regular file is refused (is_dir must really be a directory)");
reset_all();
g_io.is_dir = 1;
(void)cmd_cwd(&g_cli, "logs");
CHECK((rep_has("250 ") == 1) && (strcmp(g_cli.directory, "//logs") == 0) &&
(g_free_calls == 1),
"TC-LFTPD-024 CWD 成功后更新当前目录并释放路径缓冲(不泄漏堆)");
"TC-LFTPD-024 a successful CWD updates the current directory and frees the path buffer (no heap leak)");
reset_all();
(void)cmd_cwd(&g_cli, "/");
CHECK((rep_has("250 ") == 1) && (g_io.stat_rc == 0),
"TC-LFTPD-024b 回到根目录免 stat 直接放行(根一定存在)");
"TC-LFTPD-024b going back to the root skips stat and succeeds directly (the root always exists)");
reset_all();
g_io.is_dir = 0;
g_io.size = 12345u;
(void)cmd_size(&g_cli, "up.bin");
CHECK(strcmp(rep(0), "213 12345\r\n") == 0, "TC-LFTPD-025 SIZE 报字节数");
CHECK(strcmp(rep(0), "213 12345\r\n") == 0, "TC-LFTPD-025 SIZE reports the byte count");
reset_all();
g_io.stat_rc = -1;
(void)cmd_size(&g_cli, "nope");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-025b 文件不存在时 SIZE 回 550");
CHECK(rep_has("550 ") == 1, "TC-LFTPD-025b when the file is missing SIZE answers 550");
reset_all();
(void)strcpy(g_cli.directory, "/logs");
(void)cmd_pwd(&g_cli, NULL);
CHECK(strcmp(rep(0), "257 \"/logs\"\r\n") == 0,
"TC-LFTPD-026 PWD 用双引号包住目录(RFC 959 要求)");
"TC-LFTPD-026 PWD wraps the directory in double quotes (required by RFC 959)");
reset_all();
(void)cmd_syst(&g_cli, NULL);
CHECK(strcmp(rep(0), "215 UNIX Type: L8\r\n") == 0,
"TC-LFTPD-027 SYST 固定报 UNIX Type: L8(客户端据此决定模式)");
"TC-LFTPD-027 SYST always reports UNIX Type: L8 (clients pick their mode from it)");
reset_all();
(void)cmd_feat(&g_cli, NULL);
CHECK((strncmp(rep(0), "211-", 4) == 0) && (strcmp(rep(1), "EPSV\r\n") == 0) &&
(strcmp(rep(2), "PASV\r\n") == 0) && (strncmp(rep(5), "211 ", 4) == 0) &&
(g_n.reply_n == 6),
"TC-LFTPD-028 FEAT 多行:首行 211-、四条能力无状态码、末行 211 结束");
"TC-LFTPD-028 FEAT multiline: first line 211-, four capability lines without a status code, last line 211 ends it");
reset_all();
(void)cmd_type(&g_cli, "I");
(void)cmd_noop(&g_cli, NULL);
CHECK(rep_has("200 ") == 2, "TC-LFTPD-030 TYPE 与 NOOP 都回 200");
CHECK(rep_has("200 ") == 2, "TC-LFTPD-030 both TYPE and NOOP answer 200");
}
/* ===================== 登录序列 ===================== */
@@ -693,7 +693,7 @@ static void tc_login(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("331 ") == 1) && (rep_has("230 ") == 1) &&
(g_cli.auth_logged_in == 1u) && (strcmp(g_cli.auth_user, "admin") == 0),
"TC-LFTPD-031 USER 记下用户名与匹配位,PASS 过了就转已登录");
"TC-LFTPD-031 USER records the user name and the match bit; a passing PASS switches to logged in");
reset_all();
g_auth_res = FTP_AUTH_FAIL;
@@ -701,7 +701,7 @@ static void tc_login(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("530 ") == 1) && (g_cli.auth_logged_in == 0u) &&
(g_lg_info_count >= 1),
"TC-LFTPD-032 口令错:530 且**必须重新走 USER**(auth_user_ok 已清零)");
"TC-LFTPD-032 a wrong password gives 530 and USER must be repeated (auth_user_ok was cleared)");
reset_all();
g_auth_res = FTP_AUTH_LOCKED;
@@ -709,7 +709,7 @@ static void tc_login(void)
feed("USER admin", "PASS bad", NULL, NULL);
(void)handle_control_channel(&g_cli);
CHECK(rep_has("530 ") == 1,
"TC-LFTPD-033 连错被锁时同样只回 530(不给攻击者区分提示)");
"TC-LFTPD-033 while locked out after repeated failures it also answers only 530 (no distinguishing hint for an attacker)");
reset_all();
g_cfg_present = 0; /* 配置读不到 */
@@ -717,7 +717,7 @@ static void tc_login(void)
(void)handle_control_channel(&g_cli);
CHECK((rep_has("331 ") == 1) && (rep_has("530 ") == 1) &&
(g_cli.auth_user_ok == 0u),
"TC-LFTPD-034 配置缺失时 USER 无法匹配、PASS 直接 530(fail-closed)");
"TC-LFTPD-034 with missing configuration USER cannot match and PASS answers 530 straight away (fail-closed)");
}
int main(void)
+29 -29
View File
@@ -241,7 +241,7 @@ void vTaskDelay(TickType_t xTicksToDelay)
if (g_net.delay_calls > 100) {
/* 跑飞保险:本模块每条用例都必须在有限轮内退出(accept 序列要么成功、
* 要么撤防、要么代次变化)。真跑飞就退出,绝不把挂死留给 pre-commit。 */
printf(" ABORT accept/等待循环跑飞(>100 轮未退出)\n");
printf(" ABORT accept/wait loop runaway (>100 rounds without exit)\n");
fflush(stdout);
exit(2);
}
@@ -262,32 +262,32 @@ static void tc_listen(void)
net_reset(); logs_clear();
g_net.socket_rc = -1;
CHECK((lftpd_inet_listen(21) == -1) && (g_net.bind_calls == 0) &&
(g_lfi_error_count == 1), "TC-LFTINET-001 建 socket 失败即返回,不再 bind");
(g_lfi_error_count == 1), "TC-LFTINET-001 a socket creation failure returns at once, no bind attempt");
net_reset(); logs_clear();
g_net.bind_rc = -1;
CHECK((lftpd_inet_listen(21) == -1) && (g_net.close_calls == 1) &&
(g_net.listen_calls == 0), "TC-LFTINET-002 bind 失败要把 socket 关掉");
(g_net.listen_calls == 0), "TC-LFTINET-002 a bind failure must close the socket");
net_reset(); logs_clear();
g_net.listen_rc = -1;
CHECK((lftpd_inet_listen(21) == -1) && (g_net.close_calls == 1) &&
(g_lfi_error_count == 1), "TC-LFTINET-003 listen 失败同样关socket 并留痕");
(g_lfi_error_count == 1), "TC-LFTINET-003 a listen failure also closes the socket and logs it");
net_reset(); logs_clear();
CHECK((lftpd_inet_listen(21) == 3) && (g_net.last_domain == NET_AF_INET) &&
(g_net.last_type == NET_SOCK_STREAM) &&
(g_net.last_bind_port_net == net_htons(21u)) && (g_net.listen_calls == 1),
"TC-LFTINET-004 流式监听口,且**端口过了 htons**(sin_port 是网络序)");
"TC-LFTINET-004 a stream listener whose port went through htons (sin_port is network order)");
net_reset(); logs_clear();
g_net.getsock_rc = -1;
CHECK((lftpd_inet_get_socket_port(3) == -1) && (g_lfi_error_count == 1),
"TC-LFTINET-005 问不到名字就报 -1(不拿脏端口当结果)");
"TC-LFTINET-005 a failing getsockname returns -1 (never report a stale port as the result)");
net_reset(); logs_clear();
g_net.getsock_port_net = net_htons(8888u);
CHECK(lftpd_inet_get_socket_port(3) == 8888,
"TC-LFTINET-006 端口回来要 ntohs 还原成宿主序");
"TC-LFTINET-006 the port comes back through ntohs into host order");
}
/* ===================== accept:喂狗、代次、撤防 ===================== */
@@ -298,14 +298,14 @@ static void tc_accept(void)
net_reset(); logs_clear();
g_net.accept_seq[0] = 9; g_net.accept_n = 1;
CHECK((lftpd_inet_accept(3) == 9) && (g_net.delay_calls == 0),
"TC-LFTINET-010 一击即中不睡不等");
"TC-LFTINET-010 an immediate hit neither sleeps nor waits");
net_reset(); logs_clear();
g_net.accept_seq[0] = -1; g_net.accept_seq[1] = -1; g_net.accept_seq[2] = 9;
g_net.accept_n = 3;
CHECK((lftpd_inet_accept(3) == 9) && (g_net.delay_calls == 2) &&
(g_lfi_info_count == 0),
"TC-LFTINET-011 每轮失败都让出一次 CPU(同段代码里也喂狗),直到成功");
"TC-LFTINET-011 every failed round yields the CPU (and feeds the watchdog in the same block) until it succeeds");
net_reset(); logs_clear();
g_net.accept_seq[0] = -1; g_net.accept_seq[1] = -1; g_net.accept_seq[2] = -1;
@@ -313,25 +313,25 @@ static void tc_accept(void)
g_net.gen_flip_after = 3; /* 第 3 轮时网络重建 */
CHECK((lftpd_inet_accept(3) == -1) && (g_lfi_info_count == 1) &&
(g_net.accept_calls == 3),
"TC-LFTINET-012 **代次一变立刻撤掉 accept**(否则新 IP 生效后监听还挂在旧代次)");
"TC-LFTINET-012 a generation change aborts accept immediately (otherwise the listener stays on the old generation after the new IP takes effect)");
net_reset(); logs_clear();
g_net.accept_seq[0] = -1; g_net.accept_seq[1] = 9; g_net.accept_n = 2;
CHECK((lftpd_inet_accept_timeout(3, 5000) == 9) && (g_net.delay_calls == 1),
"TC-LFTINET-020 超时窗内等到数据通道");
"TC-LFTINET-020 the data channel is accepted inside the timeout window");
net_reset(); logs_clear();
g_net.accept_seq[0] = -1; g_net.accept_seq[1] = -1; g_net.accept_n = 2;
g_net.armed = 0; /* 监听门被撤(会话已放弃) */
CHECK((lftpd_inet_accept_timeout(3, 5000) == -1) && (g_net.accept_calls == 1) &&
(g_lfi_error_count == 1),
"TC-LFTINET-021 监听门撤防立即放弃,不对着死监听耗满 5 s");
"TC-LFTINET-021 lowering the listener gate gives up at once instead of burning the full 5 s on a dead listener");
net_reset(); logs_clear();
g_net.accept_seq[0] = -1; g_net.accept_n = 1;
CHECK((lftpd_inet_accept_timeout(3, 300) == -1) && (g_net.delay_calls == 3) &&
(g_lfi_info_count == 1),
"TC-LFTINET-022 对端一直不开数据通道:按 100ms 步进等满后 INFO 收场");
"TC-LFTINET-022 the peer never opens the data channel: step by 100ms to the limit and finish as INFO");
}
/* ===================== read_line:分片、截断、预算 ===================== */
@@ -347,38 +347,38 @@ static void tc_read_line(void)
g_net.recv_seq[0] = 12; g_net.recv_n = 1;
rc = lftpd_inet_read_line(3, b, sizeof(b));
CHECK((rc == 0) && (strcmp(b, "USER admin") == 0),
"TC-LFTINET-030 一次到位:CRLF 就地截成结束符(FTP 命令行的边界)");
"TC-LFTINET-030 one shot: CRLF is cut into the terminator in place (the FTP command line boundary)");
net_reset(); logs_clear();
(void)strcpy(g_net.rx, "USER admin\r\n");
g_net.recv_seq[0] = 4; g_net.recv_seq[1] = 8; g_net.recv_n = 2;
rc = lftpd_inet_read_line(3, b, sizeof(b));
CHECK((rc == 0) && (strcmp(b, "USER admin") == 0),
"TC-LFTINET-031 分两片到达也要拼出整行(跨包命令行)");
"TC-LFTINET-031 two arriving halves are stitched into one line (a command line spanning packets)");
net_reset(); logs_clear();
g_net.recv_seq[0] = 0; g_net.recv_n = 1;
CHECK(lftpd_inet_read_line(3, b, sizeof(b)) == -1,
"TC-LFTINET-032 读到 0(对端关闭)立刻失败,不进死循环");
"TC-LFTINET-032 reading 0 (peer closed) fails at once and must not spin");
net_reset(); logs_clear();
(void)strcpy(g_net.rx, "NOOP\r\n");
g_net.recv_seq[0] = -2; g_net.recv_seq[1] = 6; g_net.recv_n = 2;
rc = lftpd_inet_read_line(3, b, sizeof(b));
CHECK((rc == 0) && (strcmp(b, "NOOP") == 0),
"TC-LFTINET-033 -2 表示这一片超时,继续等而不是当错误");
"TC-LFTINET-033 -2 means this slice timed out: keep waiting instead of treating it as an error");
net_reset(); logs_clear();
g_net.recv_seq[0] = -5; g_net.recv_n = 1;
CHECK(lftpd_inet_read_line(3, b, sizeof(b)) == -5,
"TC-LFTINET-034 其它负值**原样返回**(错误码不许被吞成 -1)");
"TC-LFTINET-034 any other negative value is returned verbatim (an error code must not be swallowed into -1)");
net_reset(); logs_clear();
g_net.recv_seq[0] = -2; g_net.recv_n = 1; /* 一直超时,每轮耗 200ms */
rc = lftpd_inet_read_line(3, b, sizeof(b));
CHECK((rc == -1) && (g_net.recv_calls == 150) &&
(g_net.last_recv_timeout <= 200u),
"TC-LFTINET-035 30 s 总预算按 200ms 分片累加,到点退出(饿不到看门狗)");
"TC-LFTINET-035 the 30 s total budget accumulates in 200ms slices and exits on time (the watchdog never starves)");
net_reset(); logs_clear();
(void)memset(g_net.rx, 'A', 40); /* 没有 CRLF 的长行 */
@@ -386,7 +386,7 @@ static void tc_read_line(void)
(void)memset(b, 0, sizeof(b));
rc = lftpd_inet_read_line(3, b, 8);
CHECK((rc == -1) && (b[7] == '\0'),
"TC-LFTINET-036 缓冲填满仍无行尾:失败且**不越界写**(留最后一格给结束符)");
"TC-LFTINET-036 buffer full with no line end: fail without writing out of bounds (the last byte is reserved for the terminator)");
}
/* ===================== 写与读 ===================== */
@@ -400,45 +400,45 @@ static void tc_write_read(void)
net_reset(); logs_clear();
g_net.send_seq[0] = 12; g_net.send_n = 1;
CHECK((lftpd_inet_write_string(3, msg) == 0) && (g_net.send_calls == 1),
"TC-LFTINET-040 一次写完");
"TC-LFTINET-040 everything written in a single call");
net_reset(); logs_clear();
g_net.send_seq[0] = 5; g_net.send_seq[1] = 7; g_net.send_n = 2;
CHECK((lftpd_inet_write_string(3, msg) == 0) && (g_net.send_calls == 2) &&
(g_net.send_off[1] == 5),
"TC-LFTINET-041 短写后续传从**正确的偏移**接着写(少半条响应就是协议错乱)");
"TC-LFTINET-041 after a short write the continuation resumes at the correct offset (half a response is a protocol break)");
net_reset(); logs_clear();
g_net.send_seq[0] = 0; g_net.send_n = 1;
CHECK((lftpd_inet_write_string(3, msg) == -1) && (g_lfi_error_count == 1),
"TC-LFTINET-042 写 0 字节按失败处理(不是当成成功写了 0 字节)");
"TC-LFTINET-042 writing 0 bytes counts as a failure (not a successful zero-byte write)");
net_reset(); logs_clear();
g_net.send_seq[0] = -1; g_net.send_n = 1;
CHECK(lftpd_inet_write(3, msg, 12) == -1, "TC-LFTINET-043 写失败原样回 -1");
CHECK(lftpd_inet_write(3, msg, 12) == -1, "TC-LFTINET-043 a write failure returns -1 as is");
net_reset(); logs_clear();
g_net.send_seq[0] = 4; g_net.send_seq[1] = 6; g_net.send_n = 2;
CHECK((lftpd_inet_write(3, msg, 10) == 10) && (g_net.send_calls == 2),
"TC-LFTINET-050 二进制写也走短写续传,成功时回**请求长度**");
"TC-LFTINET-050 binary writes also resume after short writes and return the requested length on success");
net_reset(); logs_clear();
(void)strcpy(g_net.rx, "STOR\r\n");
g_net.recv_seq[0] = 4; g_net.recv_n = 1;
CHECK(lftpd_inet_read(3, b, sizeof(b)) == 4, "TC-LFTINET-060 读到数据立刻交还");
CHECK(lftpd_inet_read(3, b, sizeof(b)) == 4, "TC-LFTINET-060 data read is handed back immediately");
net_reset(); logs_clear();
g_net.recv_seq[0] = -1; g_net.recv_n = 1;
CHECK(lftpd_inet_read(3, b, sizeof(b)) == -1, "TC-LFTINET-061 -1 直接失败返回");
CHECK(lftpd_inet_read(3, b, sizeof(b)) == -1, "TC-LFTINET-061 -1 fails and returns directly");
net_reset(); logs_clear();
g_net.recv_seq[0] = -3; g_net.recv_n = 1;
CHECK((lftpd_inet_read(3, b, sizeof(b)) == -1) && (g_net.recv_calls > 1),
"TC-LFTINET-062 其它负值继续等,直到 30 s 预算耗尽");
"TC-LFTINET-062 other negative values keep waiting until the 30 s budget is exhausted");
net_reset(); logs_clear();
CHECK((lftpd_inet_close(3) == 0) && (g_net.close_calls == 1),
"TC-LFTINET-070 close 就是转 net_close");
"TC-LFTINET-070 close only forwards to net_close");
}
int main(void)
+33 -33
View File
@@ -276,11 +276,11 @@ static void tc_fatfs_path(void)
{
printf("-- TC-LFTPIO-001..004 to_fatfs_path --\n");
CHECK((strcmp(to_fatfs_path(NULL), "0:") == 0) &&
(strcmp(to_fatfs_path(""), "0:") == 0), "TC-LFTPIO-001 NULL/空串 -> 0:");
CHECK(strcmp(to_fatfs_path("/"), "0:") == 0, "TC-LFTPIO-002 根 -> 0:");
(strcmp(to_fatfs_path(""), "0:") == 0), "TC-LFTPIO-001 NULL/empty string -> 0:");
CHECK(strcmp(to_fatfs_path("/"), "0:") == 0, "TC-LFTPIO-002 root -> 0:");
CHECK(strcmp(to_fatfs_path("/a/b.txt"), "a/b.txt") == 0,
"TC-LFTPIO-003 去掉前导斜杠");
CHECK(strcmp(to_fatfs_path("a.txt"), "a.txt") == 0, "TC-LFTPIO-004 原样返回");
"TC-LFTPIO-003 the leading slash is stripped");
CHECK(strcmp(to_fatfs_path("a.txt"), "a.txt") == 0, "TC-LFTPIO-004 returned unchanged");
}
/* ===================== 路径规范化 ===================== */
@@ -289,32 +289,32 @@ static void tc_canon(void)
{
printf("-- TC-LFTPIO-010..022 canonicalize --\n");
reset_all();
CHECK(strcmp(canon("/dir", "f.txt"), "/dir/f.txt") == 0, "TC-LFTPIO-010 相对拼接");
CHECK(strcmp(canon("/dir", "/abs.txt"), "/abs.txt") == 0, "TC-LFTPIO-011 绝对忽略 base");
CHECK(strcmp(canon("/a/b", "../c"), "/a/c") == 0, "TC-LFTPIO-012 上跳一级");
CHECK(strcmp(canon("/dir", "f.txt"), "/dir/f.txt") == 0, "TC-LFTPIO-010 relative join");
CHECK(strcmp(canon("/dir", "/abs.txt"), "/abs.txt") == 0, "TC-LFTPIO-011 an absolute name ignores base");
CHECK(strcmp(canon("/a/b", "../c"), "/a/c") == 0, "TC-LFTPIO-012 go up one level");
CHECK(strcmp(canon("/a", "../../x"), "/x") == 0,
"TC-LFTPIO-013 上跳过头仍被钳在根内(安全属性)");
CHECK(strcmp(canon("/a", "."), "/a") == 0, "TC-LFTPIO-014 丢弃 . 段");
CHECK(strcmp(canon("/a", ".."), "/") == 0, "TC-LFTPIO-015 回退到根");
CHECK(strcmp(canon("/a//", "b"), "/a/b") == 0, "TC-LFTPIO-016 段间多余斜杠被折叠");
"TC-LFTPIO-013 over-shooting .. is still clamped inside the root (safety property)");
CHECK(strcmp(canon("/a", "."), "/a") == 0, "TC-LFTPIO-014 a . segment is dropped");
CHECK(strcmp(canon("/a", ".."), "/") == 0, "TC-LFTPIO-015 .. at the top falls back to the root");
CHECK(strcmp(canon("/a//", "b"), "/a/b") == 0, "TC-LFTPIO-016 redundant slashes between segments are collapsed");
CHECK(strcmp(canon("/dir", "//x"), "/x") == 0,
"TC-LFTPIO-016b name 以斜杠开头按**绝对路径**处理(忽略 base,仍钳在根内)");
CHECK(strcmp(canon(NULL, "x"), "/x") == 0, "TC-LFTPIO-017 base NULL 视作空串");
CHECK(strcmp(canon("/a", NULL), "/a") == 0, "TC-LFTPIO-018 name NULL 视作空串");
CHECK(strcmp(canon(NULL, NULL), "/") == 0, "TC-LFTPIO-019 两个 NULL -> /");
CHECK(strcmp(canon("/", ".."), "/") == 0, "TC-LFTPIO-020 根上加 .. 仍是根");
"TC-LFTPIO-016b a name starting with a slash is treated as an absolute path (base ignored, still clamped inside the root)");
CHECK(strcmp(canon(NULL, "x"), "/x") == 0, "TC-LFTPIO-017 a NULL base counts as an empty string");
CHECK(strcmp(canon("/a", NULL), "/a") == 0, "TC-LFTPIO-018 a NULL name counts as an empty string");
CHECK(strcmp(canon(NULL, NULL), "/") == 0, "TC-LFTPIO-019 both NULL -> /");
CHECK(strcmp(canon("/", ".."), "/") == 0, "TC-LFTPIO-020 .. on the root is still the root");
reset_all();
g_fail_at = 1;
CHECK(lftpd_io_canonicalize_path("/a", "b") == NULL,
"TC-LFTPIO-021 首块分配失败返回 NULL");
CHECK(g_alloc_calls == 1, "TC-LFTPIO-021b 失败后不再分配第二块");
"TC-LFTPIO-021 the first allocation failing returns NULL");
CHECK(g_alloc_calls == 1, "TC-LFTPIO-021b no second allocation is attempted after that failure");
reset_all();
g_fail_at = 2;
CHECK(lftpd_io_canonicalize_path("/a", "b") == NULL,
"TC-LFTPIO-022 结果缓冲分配失败返回 NULL");
CHECK(g_free_calls == 1, "TC-LFTPIO-022b 第一块已释放(不泄漏堆)");
"TC-LFTPIO-022 the result buffer allocation failing returns NULL");
CHECK(g_free_calls == 1, "TC-LFTPIO-022b the first block was freed (no heap leak)");
}
/* ===================== 打开文件:守卫 / 卷锁 / 错误通道 ===================== */
@@ -329,24 +329,24 @@ static void tc_open(void)
rc = lftpd_io_open_read("/a/b.txt");
CHECK((rc == 0) && (g_open_mode == FA_READ) &&
(strcmp(g_open_path, "a/b.txt") == 0),
"TC-LFTPIO-030 成功:FA_READ + 映射后的路径");
"TC-LFTPIO-030 success: FA_READ plus the mapped path");
reset_all();
s_file_open = 1;
rc = lftpd_io_open_read("/x");
CHECK((rc == -1) && (g_open_calls == 0) && (g_wait_calls == 0),
"TC-LFTPIO-031 单文件槽守卫:已打开时不碰卷锁与 FatFs");
"TC-LFTPIO-031 single-file slot guard: while a file is open it touches neither the volume lock nor FatFs");
reset_all();
g_open_rc = FR_NO_FILE;
rc = lftpd_io_open_read("/x");
CHECK((rc == -1) && (g_release_calls == 1) && (g_lfio_error_count == 1),
"TC-LFTPIO-032 打开失败:返回 -1 + 留痕 + 释放卷锁");
"TC-LFTPIO-032 open failure: return -1 + log + release the volume lock");
reset_all();
rc = lftpd_io_open_read("/x");
CHECK((rc == 0) && (g_release_calls == 0) && (s_file_open == 1),
"TC-LFTPIO-033 成功后卷锁不释放(传输期独占)");
"TC-LFTPIO-033 the volume lock stays held after success (exclusive for the whole transfer)");
reset_all();
g_wait_seq[0] = 0; /* 第一轮:卷被 defaultTask 占着 */
@@ -354,22 +354,22 @@ static void tc_open(void)
g_wait_seq[2] = 1; /* 第三轮:拿到 */
rc = lftpd_io_open_read("/x");
CHECK((rc == 0) && (g_beat_calls == 2) && (g_wait_calls == 3),
"TC-LFTPIO-034 等锁循环每轮喂看门狗(漏喂 = 整机复位)");
"TC-LFTPIO-034 the lock-wait loop feeds the watchdog every round (missing a feed resets the whole device)");
reset_all();
rc = lftpd_io_open_write("/up.bin");
CHECK((rc == 0) && (g_open_mode == (BYTE)(FA_WRITE | FA_CREATE_ALWAYS)),
"TC-LFTPIO-035 open_write 用覆盖写模式");
"TC-LFTPIO-035 open_write uses overwrite mode");
reset_all();
s_file_open = 1;
rc = lftpd_io_open_write("/up.bin");
CHECK((rc == -1) && (g_open_calls == 0), "TC-LFTPIO-036 open_write 同样有槽守卫");
CHECK((rc == -1) && (g_open_calls == 0), "TC-LFTPIO-036 open_write has the same slot guard");
reset_all();
g_open_rc = FR_DISK_ERR;
rc = lftpd_io_open_write("/up.bin");
CHECK((rc == -1) && (g_release_calls == 1), "TC-LFTPIO-037 open_write 失败也释放卷锁");
CHECK((rc == -1) && (g_release_calls == 1), "TC-LFTPIO-037 an open_write failure also releases the volume lock");
}
/* ===================== trim ===================== */
@@ -386,19 +386,19 @@ static void tc_trim(void)
(void)strcpy(buf, " PASS x");
r = lftpd_string_ltrim(buf);
CHECK((r != buf) && (strcmp(r, "PASS x") == 0) && (buf[0] == ' '),
"TC-LFTSTR-001 行首空白靠返回指针跳过,原缓冲不搬移");
"TC-LFTSTR-001 leading blanks are skipped via the returned pointer, the buffer itself is not moved");
(void)strcpy(buf, "NOOP");
r = lftpd_string_ltrim(buf);
CHECK((r == buf) && (strcmp(buf, "NOOP") == 0), "TC-LFTSTR-002 无前导空白返回同一指针");
CHECK((r == buf) && (strcmp(buf, "NOOP") == 0), "TC-LFTSTR-002 with no leading blanks the same pointer comes back");
(void)strcpy(buf, " USER admin ");
r = lftpd_string_ltrim(buf);
CHECK(strcmp(r, "USER admin ") == 0, "TC-LFTSTR-003 词中间空白保留");
CHECK(strcmp(r, "USER admin ") == 0, "TC-LFTSTR-003 blanks inside the text are kept");
(void)strcpy(buf, " \t\r\n");
r = lftpd_string_ltrim(buf);
CHECK(r[0] == '\0', "TC-LFTSTR-004 全空白 -> 空串");
CHECK(r[0] == '\0', "TC-LFTSTR-004 all blanks -> empty string");
/* 现状桩(不是期望行为):尾部循环从 strlen(p) 起步,那里是 '\0',
* isspace(0)=0 立刻退出 —— **尾部空白/CRLF 实际从未被裁**。今天无害是因为
@@ -411,7 +411,7 @@ static void tc_trim(void)
(void)strcpy(buf, "LIST ");
r = lftpd_string_ltrim(buf);
CHECK(strcmp(r, "LIST ") == 0,
"TC-LFTSTR-005 尾随空格必须保留(ltrim 语义,RFC 959 参数本体)");
"TC-LFTSTR-005 trailing spaces must survive (ltrim semantics, they belong to the RFC 959 argument)");
}
/* ===================== 入口 ===================== */
+42 -42
View File
@@ -266,7 +266,7 @@ static void tc_defaults(void)
CHECK(bytes_eq(p->ip, def_ip, 4u), "TC-NETCFG-001 ip = 192.168.1.100");
CHECK(bytes_eq(p->mask, def_mask, 4u), "TC-NETCFG-002 mask = 255.255.255.0");
CHECK(bytes_eq(p->gw, def_gw, 4u), "TC-NETCFG-003 gw = 192.168.1.1");
CHECK(mac_eq(p->mac, zero), "TC-NETCFG-004 mac = 全零(芯片内置哨兵)");
CHECK(mac_eq(p->mac, zero), "TC-NETCFG-004 mac = all zero (chip built-in sentinel)");
}
/* ===================== 注册与 load 契约 ===================== */
@@ -279,13 +279,13 @@ static void tc_register_load(void)
fake_reset();
rc = net_cfg_load();
CHECK((rc == 0) && (g_reg_n == 1) && (strcmp(g_regs[0].name, "network") == 0),
"TC-NETCFG-005 首次 load 注册 [network] 恰好一次");
"TC-NETCFG-005 first load registers [network] exactly once");
CHECK((g_ftp_reg_calls == 1) && (g_load_calls == 1),
"TC-NETCFG-006 一并注册 [ftp],且注册在 load 之前");
"TC-NETCFG-006 also registers [ftp], registered before load");
rc = net_cfg_load();
CHECK((rc == 0) && (g_reg_n == 1) && (g_ftp_reg_calls == 2),
"TC-NETCFG-007 重复 load 不重复注册 [network]");
"TC-NETCFG-007 repeated load does not re-register [network]");
CHECK((strcmp(g_regs[0].name, "network") == 0) &&
(g_regs[0].data_size == sizeof(net_cfg_t)) &&
@@ -293,18 +293,18 @@ static void tc_register_load(void)
(g_regs[0].p_defaults != NULL) &&
(g_regs[0].p_cb != NULL) &&
(g_regs[0].p_data == (const void *)&s_cfg),
"TC-NETCFG-008 注册内容:尺寸/键数/默认值/回调/数据指针=RAM 副本");
"TC-NETCFG-008 registration content: size/key count/defaults/callback/data pointer = RAM copy");
fake_reset();
rc = net_cfg_load();
CHECK((rc == 0) && (g_netcfg_info_count == 1) && (g_netcfg_error_count == 0),
"TC-NETCFG-009 load 成功:返回 0 + 一条 INFO");
"TC-NETCFG-009 load success: returns 0 + one INFO line");
fake_reset();
g_load_rc = -1;
rc = net_cfg_load();
CHECK(rc == 0, "TC-NETCFG-010 引擎 -1 时仍返回 0(RAM 恒有效契约)");
CHECK(g_netcfg_error_count == 1, "TC-NETCFG-011 load 失败必须留 ERROR");
CHECK(rc == 0, "TC-NETCFG-010 engine -1 still returns 0 (RAM copy is always valid, by contract)");
CHECK(g_netcfg_error_count == 1, "TC-NETCFG-011 load failure must leave an ERROR line");
}
/* ===================== RAM 副本读写 ===================== */
@@ -321,10 +321,10 @@ static void tc_set_get(void)
memcpy(c.mac, mac, 6u);
net_cfg_set(&c);
CHECK(bytes_eq(net_cfg_get()->ip, ip, 4u) &&
mac_eq(net_cfg_get()->mac, mac), "TC-NETCFG-012 set 被 get 读回");
mac_eq(net_cfg_get()->mac, mac), "TC-NETCFG-012 set is read back by get");
net_cfg_set(NULL);
CHECK(bytes_eq(net_cfg_get()->ip, ip, 4u), "TC-NETCFG-013 set(NULL) 不改写");
CHECK(bytes_eq(net_cfg_get()->ip, ip, 4u), "TC-NETCFG-013 set(NULL) rewrites nothing");
}
/* ===================== 直接保存 ===================== */
@@ -336,13 +336,13 @@ static void tc_save_direct(void)
printf("-- TC-NETCFG-014..015 save --\n");
fake_reset();
rc = net_cfg_save();
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-014 写盘成功返回 0");
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-014 write success returns 0");
fake_reset();
g_save_rc = -1;
rc = net_cfg_save();
CHECK((rc == -1) && (g_netcfg_error_count == 1),
"TC-NETCFG-015 写盘失败返回 -1 并留痕");
"TC-NETCFG-015 write failure returns -1 and leaves a trace");
}
/* ===================== 跨任务保存交接状态机 ===================== */
@@ -357,40 +357,40 @@ static void tc_save_handoff(void)
net_cfg_set_save_req_cb(NULL);
s_save_pending = 0u; /* 白盒复位:上一组的 save() 不碰 pending/result,此处 */
s_save_result = 0u; /* 只是防止将来有人调整后漏出脏态 */
CHECK(net_cfg_save_state() == NET_CFG_SAVE_ST_IDLE, "TC-NETCFG-020 初始 IDLE");
CHECK(net_cfg_save_state() == NET_CFG_SAVE_ST_IDLE, "TC-NETCFG-020 initial state IDLE");
CHECK((net_cfg_save_request() == 0) &&
(net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT), "TC-NETCFG-021 投递后 WAIT");
(net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT), "TC-NETCFG-021 WAIT after posting");
net_cfg_set_save_req_cb(req_cb);
g_req_cb_calls = 0;
(void)net_cfg_save_request();
CHECK(g_req_cb_calls == 1, "TC-NETCFG-022 投递经回调唤醒属主任务");
CHECK(g_req_cb_calls == 1, "TC-NETCFG-022 posting wakes the owner task through the callback");
net_cfg_set_save_req_cb(NULL);
(void)net_cfg_save_request();
CHECK((g_req_cb_calls == 1) &&
(net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT),
"TC-NETCFG-023 未注册回调时空操作,请求照样挂起");
"TC-NETCFG-023 no callback registered -> no-op, request still pending");
g_save_rc = 0;
g_save_calls = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-024a 有请求时确实写盘一次");
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-024a with a pending request it really writes once");
g_save_calls = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (g_save_calls == 0),
"TC-NETCFG-024 无请求时空醒不写盘(2s 轮询不刷 Flash)");
"TC-NETCFG-024 no request: empty wake does not write (2 s polling never flushes Flash)");
(void)net_cfg_save_request();
net_cfg_set_apply_cb(apply_cb);
g_apply_calls = 0;
g_save_rc = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (s_save_pending == 0u), "TC-NETCFG-025 成功后 pending 清零");
CHECK((rc == 0) && (s_save_pending == 0u), "TC-NETCFG-025 pending cleared after success");
CHECK((net_cfg_save_state() == NET_CFG_SAVE_ST_OK) && (s_save_busy == 0u),
"TC-NETCFG-026 成功后状态 OK 且写盘标志已清");
CHECK(g_apply_calls == 1, "TC-NETCFG-027 成功后触发一次立即应用");
"TC-NETCFG-026 state OK after success and the write flag is cleared");
CHECK(g_apply_calls == 1, "TC-NETCFG-027 success triggers one immediate apply");
(void)net_cfg_save_request();
g_save_hook = save_hook_probe;
@@ -399,7 +399,7 @@ static void tc_save_handoff(void)
g_save_rc = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (g_state_during_save == (int)NET_CFG_SAVE_ST_WAIT),
"TC-NETCFG-028 写盘进行中状态仍是 WAIT");
"TC-NETCFG-028 state stays WAIT while the write is in progress");
/* 写盘期间再投递一次:本轮不得把它当已处理,下轮必须真的再写 */
g_requeue_during_save = 1;
@@ -407,12 +407,12 @@ static void tc_save_handoff(void)
g_save_calls = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT),
"TC-NETCFG-029a 写盘期间的新请求没丢");
"TC-NETCFG-029a a new request during the write is not lost");
g_requeue_during_save = 0;
g_save_hook = NULL;
g_save_calls = 0;
rc = net_cfg_save_run();
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-029b 下一轮真的再写一次");
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-029b the next round really writes once more");
/* 写盘失败:置 FAIL 且**不得**触发应用(内存值没落盘,网络不能重建) */
s_save_pending = 0u;
@@ -423,20 +423,20 @@ static void tc_save_handoff(void)
rc = net_cfg_save_run();
CHECK((rc == -1) && (net_cfg_save_state() == NET_CFG_SAVE_ST_FAIL) &&
(s_save_busy == 0u),
"TC-NETCFG-030 失败置 FAIL 且写盘标志已清(否则 UI 永远卡 WAIT)");
CHECK(g_apply_calls == 0, "TC-NETCFG-031 写盘失败不得触发立即应用");
"TC-NETCFG-030 failure sets FAIL and clears the write flag (else UI sticks in WAIT forever)");
CHECK(g_apply_calls == 0, "TC-NETCFG-031 write failure must not trigger an immediate apply");
net_cfg_set_apply_cb(NULL);
g_apply_calls = 0;
net_cfg_request_apply();
CHECK(g_apply_calls == 0, "TC-NETCFG-032 未注册应用回调时是空操作");
CHECK(g_apply_calls == 0, "TC-NETCFG-032 apply is a no-op while no callback is registered");
net_cfg_set_apply_cb(apply_cb);
net_cfg_request_apply();
CHECK(g_apply_calls == 1, "TC-NETCFG-033a 注册后可被触发");
CHECK(g_apply_calls == 1, "TC-NETCFG-033a after registering it can be triggered");
net_cfg_set_apply_cb(NULL);
net_cfg_request_apply();
CHECK(g_apply_calls == 1, "TC-NETCFG-033b 注销后不再被触发");
CHECK(g_apply_calls == 1, "TC-NETCFG-033b after unregistering it is no longer triggered");
}
/* ===================== MAC 三态优先级 ===================== */
@@ -457,30 +457,30 @@ static void tc_mac_priority(void)
net_cfg_set_active_mac(shadow);
memset(out, 0xFF, sizeof(out));
net_cfg_get_mac(out);
CHECK(mac_eq(out, stored), "TC-NETCFG-040 存储非零优先于芯片影子");
CHECK(mac_eq(out, stored), "TC-NETCFG-040 stored non-zero takes precedence over the chip shadow");
c = *net_cfg_get();
memcpy(c.mac, zero, 6u);
net_cfg_set(&c);
memset(out, 0xFF, sizeof(out));
net_cfg_get_mac(out);
CHECK(mac_eq(out, shadow), "TC-NETCFG-041 存储全零则用芯片回写影子");
CHECK(mac_eq(out, shadow), "TC-NETCFG-041 stored all zero -> use the chip shadow write-back");
s_active_valid = 0u; /* 白盒:回到"芯片还没回写"的上电初态 */
memset(out, 0xFF, sizeof(out));
net_cfg_get_mac(out);
CHECK(mac_eq(out, zero), "TC-NETCFG-042 两者皆无 → 全零");
CHECK(mac_eq(out, zero), "TC-NETCFG-042 neither available -> all zero");
net_cfg_set_active_mac(NULL);
CHECK(s_active_valid == 0u, "TC-NETCFG-043 NULL 回写不得把影子置有效");
CHECK(s_active_valid == 0u, "TC-NETCFG-043 NULL write-back must not mark the shadow valid");
net_cfg_set_active_mac(shadow);
memset(out, 0xFF, sizeof(out));
net_cfg_get_mac(out);
CHECK(mac_eq(out, shadow), "TC-NETCFG-044 正常回写后影子生效");
CHECK(mac_eq(out, shadow), "TC-NETCFG-044 shadow becomes active after a normal write-back");
net_cfg_get_mac(NULL);
CHECK(1, "TC-NETCFG-045 get_mac(NULL) 安全返回");
CHECK(1, "TC-NETCFG-045 get_mac(NULL) returns safely");
}
/* ===================== IPv4 定长格式化 ===================== */
@@ -500,33 +500,33 @@ static void tc_fmt_ipv4(void)
/* 定长 = 4 段 × 3 位 + 3 个点 = 15(曾有误记为 11 的注释,已同日更正)
* 钉死这个长度是 UI 光标偏移(段 i 起点 4i、点号 3/7/11)的前提 */
CHECK((strcmp(buf, "192.168.001.100") == 0) && (strlen(buf) == 15u),
"TC-NETCFG-050 定长 15 字符且每段补零");
"TC-NETCFG-050 fixed 15 chars with zero padding in every segment");
memset(buf, 0x7A, sizeof(buf));
net_cfg_fmt_ipv4(ip0, buf, sizeof(buf));
CHECK(strcmp(buf, "000.000.000.000") == 0, "TC-NETCFG-051 全零也占满 15 字符");
CHECK(strcmp(buf, "000.000.000.000") == 0, "TC-NETCFG-051 all zero still fills 15 chars");
memset(buf, 0x7A, sizeof(buf));
net_cfg_fmt_ipv4(ipm, buf, sizeof(buf));
CHECK(strcmp(buf, "255.255.255.255") == 0, "TC-NETCFG-052 255 边界");
CHECK(strcmp(buf, "255.255.255.255") == 0, "TC-NETCFG-052 255 boundary");
memset(guard, 0x7A, sizeof(guard));
memcpy(buf, guard, sizeof(buf));
net_cfg_fmt_ipv4(NULL, buf, sizeof(buf));
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-053 p_ip NULL 不写缓冲");
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-053 p_ip NULL does not touch the buffer");
net_cfg_fmt_ipv4(ip1, NULL, 16u);
CHECK(1, "TC-NETCFG-054 p_buf NULL 不崩");
CHECK(1, "TC-NETCFG-054 p_buf NULL does not crash");
memset(guard, 0x7A, sizeof(guard));
memcpy(buf, guard, sizeof(buf));
net_cfg_fmt_ipv4(ip1, buf, 0u);
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-055 len 0 不写缓冲");
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-055 len 0 does not touch the buffer");
memset(buf, 0x7A, sizeof(buf));
net_cfg_fmt_ipv4(ip1, buf, 5u);
CHECK((strlen(buf) == 4u) && (buf[4] == '\0') && (buf[5] == (char)0x7A),
"TC-NETCFG-056 len 不足:截断但必须 NUL 结尾且不越界");
"TC-NETCFG-056 short len: truncate but must stay NUL-terminated and in bounds");
}
/* ===================== 入口 ===================== */
+34 -34
View File
@@ -217,16 +217,16 @@ static void tc_connect(void)
CHECK((p->remote_ip_arr[0] == 192u) && (p->remote_ip_arr[3] == 50u) &&
(p->remote_ip == ((192u) | (168u << 8) | (1u << 16) | (50u << 24))) &&
(p->remote_port == 51234u),
"TC-NETEV-001 对端六元组解析:ip 低字节在前拼 32 位、端口取第 5/6 字节");
"TC-NETEV-001 peer tuple parsing: ip assembled little-endian into 32 bits, port from bytes 5/6");
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->session_dead == 0u) &&
(p->lost_conn_seen == 0u) && (p->send_ready == 1u),
"TC-NETEV-002 监听口自接后转已连接并立刻可写");
"TC-NETEV-002 a listened socket becomes established after self-accept and is writable at once");
CHECK((g_chip.keepalive_calls == 1) && (g_chip.last_keepalive == 1u) &&
(g_chip.int_status_calls == 1),
"TC-NETEV-003 建链即开 keepalive 并清一次挂起位(读即清)");
"TC-NETEV-003 on connect, enable keepalive and clear the pending bit once (read clears)");
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
(g_ev_fd[0] == 0) && (g_ev_arg[0] == (void *)&g_ev_token),
"TC-NETEV-004 事件带 fd 与调用方 arg 回调给应用");
"TC-NETEV-004 the event calls back into the app with fd and the caller arg");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
@@ -234,13 +234,13 @@ static void tc_connect(void)
handle_connect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->send_ready == 0u) &&
(g_chip.keepalive_calls == 1) && (g_ev_n == 1),
"TC-NETEV-005 主动连接分支同样转已连接,但**不动 send_ready**(与自接有别)");
"TC-NETEV-005 the active-connect branch also becomes established but does NOT touch send_ready (differs from self-accept)");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
handle_connect_event(p);
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-006 其它状态收到 CONNECT 中断一律忽略(不许凭空造会话)");
"TC-NETEV-006 a CONNECT interrupt in any other state is ignored (never invent a session)");
}
/* ===================== DISCONNECT ===================== */
@@ -257,7 +257,7 @@ static void tc_disconnect(void)
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
(g_nev_error_count == 1),
"TC-NETEV-010 芯片说还在连接:忽略这个陈旧 DISCONNECT(不许拆活会话)");
"TC-NETEV-010 the chip still says connecting: ignore this stale DISCONNECT (never tear down a live session)");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
@@ -267,7 +267,7 @@ static void tc_disconnect(void)
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->lost_conn_seen == 1u) &&
(g_ev_n == 0) && (g_settle_n == 0),
"TC-NETEV-011 疑似误报(一条数据都没收就断):延后交给对账器,不立即拆");
"TC-NETEV-011 suspected false alarm (dropped before any data): defer to the reconciler instead of tearing down");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
@@ -280,7 +280,7 @@ static void tc_disconnect(void)
(p->last_peer_port == 51234u) &&
(p->state == NET_SOCK_STATE_CLOSED) &&
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-012 正常断开:优雅沉降 + 会话标死 + 记旧端口 + 发事件");
"TC-NETEV-012 normal disconnect: graceful settle + mark session dead + remember old port + post event");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
@@ -289,20 +289,20 @@ static void tc_disconnect(void)
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) &&
(g_nev_info_count == 1) && (g_nev_error_count == 0),
"TC-NETEV-013 监听还在:陈旧监听 DISCONNECT 只留痕,不报 ERROR");
"TC-NETEV-013 listen still alive: a stale listen DISCONNECT is only logged, not reported as ERROR");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
g_fake.status_byte1[0] = CH395F_TCP_CLOSED; /* 芯片说不在 LISTEN */
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) && (g_req_n == 0),
"TC-NETEV-013b 监听丢了也只留痕:重开交给对账器,事件层不越权");
"TC-NETEV-013b even a lost listener is only logged: reopening belongs to the reconciler, the event layer must not overreach");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
handle_disconnect_event(p);
CHECK((g_nev_error_count == 1) && (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-014 非常规状态收到 DISCONNECT:报 ERROR 但仍要通知应用");
"TC-NETEV-014 DISCONNECT in an unusual state: report ERROR but still notify the application");
}
/* ===================== RECV / SEND / TIMEOUT ===================== */
@@ -316,21 +316,21 @@ static void tc_recv_send_timeout(void)
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
handle_recv_event(p);
CHECK((p->readable == 1u) && (g_ev_kind[0] == (int)NET_EVENT_DATA_RECEIVED),
"TC-NETEV-020 RECV 事件置可读并通知应用");
"TC-NETEV-020 a RECV event sets readable and notifies the application");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
p->send_ready = 1u;
handle_send_event(p);
CHECK((g_ev_kind[0] == (int)NET_EVENT_SEND_COMPLETE) && (p->send_ready == 1u),
"TC-NETEV-021 SEND 完成只发事件,字段留给写路径自己管");
"TC-NETEV-021 SEND completion only posts the event; the fields stay owned by the write path");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
handle_timeout_event(p);
CHECK((p->state == NET_SOCK_STATE_CREATED) && (g_nev_error_count == 1) &&
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
"TC-NETEV-030 连接超时:退回 CREATED 并通知应用");
"TC-NETEV-030 connect timeout: fall back to CREATED and notify the application");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
@@ -339,14 +339,14 @@ static void tc_recv_send_timeout(void)
CHECK((g_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE) &&
(p->released_abnormal == 1u) && (p->session_dead == 1u) &&
(p->last_peer_port == 777u) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-031 会话超时:隔离级沉降 + 标异常释放(net_pending 收尾据此报 CONNRESET)");
"TC-NETEV-031 session timeout: quarantine-level settle + mark released abnormally (net_pending reports CONNRESET from it)");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
handle_timeout_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_nev_error_count == 1) &&
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
"TC-NETEV-032 监听超时只留痕:不改状态,但仍要通知等待方");
"TC-NETEV-032 listen timeout is only logged: state unchanged but waiters are still notified");
}
/* ===================== 掉线自愈 ===================== */
@@ -362,7 +362,7 @@ static void tc_lost_probe(void)
g_fake.cached_tcp[0] = CH395F_TCP_CLOSED;
net_lost_connect_probe(p);
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
"TC-NETEV-040 芯片不在已连接态:只清标记,不自作主张建会话");
"TC-NETEV-040 the chip is not established: only clear the flag, do not create a session on our own");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
@@ -372,7 +372,7 @@ static void tc_lost_probe(void)
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 51234u);
net_lost_connect_probe(p);
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
"TC-NETEV-041 对端口与上次相同:认定是自己没看到 CONNECT,不自接第二遍");
"TC-NETEV-041 peer port identical to last time: assume we missed CONNECT, do not accept a second time");
/* 自愈的适用面是软件还写着 LISTENING、芯片其实已经连上——accept 分支只认
* LISTENING/CONNECTING。若软件态已被别处改成 CLOSED,本函数打完 INFO 后什么都
@@ -387,7 +387,7 @@ static void tc_lost_probe(void)
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
(p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 55555u) &&
(p->lost_conn_seen == 0u),
"TC-NETEV-042 丢了 CONNECT 中断时自愈:补一次 accept,会话不至于永久失联");
"TC-NETEV-042 self-heal after a lost CONNECT interrupt: make up one accept so the session is not stranded forever");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
@@ -395,7 +395,7 @@ static void tc_lost_probe(void)
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u);
net_lost_connect_probe(p);
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-042b 现状:软件已 CLOSED 时自愈接不上(只留痕,不凭空造会话)");
"TC-NETEV-042b current state: self-heal cannot accept once the software side is CLOSED (logged only, no invented session)");
}
/* ===================== 周期对账 ===================== */
@@ -419,10 +419,10 @@ static void tc_reconcile(void)
infos = g_nev_info_count;
recon_step(1000u); /* priming:链路计数记基线 */
CHECK(g_nev_info_count == infos,
"TC-NETEV-051a 上电第一趟不因链路计数打日志(0xFFFF 哨兵只记基线)");
"TC-NETEV-051a the first pass after power-up logs nothing for the link counter (the 0xFFFF sentinel only records the baseline)");
c = g_chip.cached_calls;
recon_step(1000u); /* 同一时刻再调一次 */
CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms 周期闸门:同一时刻不重复查芯片");
CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms period gate: do not query the chip twice at the same instant");
reset_all();
p = mk(1, NET_SOCK_STATE_ESTABLISHED, 2404u);
@@ -434,17 +434,17 @@ static void tc_reconcile(void)
recon_step(1300u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
(g_nev_info_count == infos),
"TC-NETEV-052 会话死亡要**连观 4 次**才动手:前三次保持原状");
"TC-NETEV-052 a dead session needs 4 consecutive observations before acting: the first three change nothing");
recon_step(1400u);
CHECK((p->state == NET_SOCK_STATE_CLOSED) && (p->session_dead == 1u) &&
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-052b 第 4 次稳定后走断开收尾");
"TC-NETEV-052b once the 4th observation is stable, run the disconnect teardown");
p->state = NET_SOCK_STATE_ESTABLISHED; /* 假装又活了,验证冷却 */
recon_step(1500u);
recon_step(1600u);
recon_step(1700u);
recon_step(1800u);
CHECK(g_ev_n == 1, "TC-NETEV-052c 动作后 1000ms 冷却内不再重复折腾(哪怕稳定计数够了)");
CHECK(g_ev_n == 1, "TC-NETEV-052c no repeat action inside the 1000ms cooldown after acting (even when the stable count is met)");
reset_all();
p = mk(2, NET_SOCK_STATE_ESTABLISHED, 2404u);
@@ -452,11 +452,11 @@ static void tc_reconcile(void)
g_fake.cached_tcp[2] = CH395F_TCP_LISTEN; /* 芯片自己重开监听了,却没发 DISCONNECT */
recon_step(2900u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_pcan_n == 0),
"TC-NETEV-053 芯片偷偷重开监听:需要 2 次稳定,第一次只累计");
"TC-NETEV-053 the chip reopened the listener behind our back: needs 2 stable passes, the first one only accumulates");
recon_step(3000u);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_pcan_n == 1) &&
(g_scan_n == 1) && (g_pcan_err == NET_ERR_CONNRESET),
"TC-NETEV-053b 第 2 次稳定:重同步成监听并**取消两条挂起**(否则任务永远等不到回)");
"TC-NETEV-053b second stable pass: resync to listen and cancel both pending waits (otherwise a task would wait forever)");
reset_all();
p = mk(3, NET_SOCK_STATE_LISTENING, 2404u);
@@ -466,7 +466,7 @@ static void tc_reconcile(void)
recon_step(4100u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 40000u) &&
(g_ev_kind[0] == (int)NET_EVENT_CONNECTED),
"TC-NETEV-054 连接完成但中断丢了:1 次即补 accept");
"TC-NETEV-054 connect completed but the interrupt was lost: a single pass is enough to make up the accept");
reset_all();
p = mk(4, NET_SOCK_STATE_LISTENING, 2404u);
@@ -476,7 +476,7 @@ static void tc_reconcile(void)
recon_step(5300u);
CHECK((g_req_n == 1) && (g_req_sock == 4) &&
(g_req_reason == (int)NET_LISTENER_REQ_RECON),
"TC-NETEV-055 监听丢了:2 次稳定后请求重开监听(不自已在事件层里开)");
"TC-NETEV-055 listener lost: request a reopen after 2 stable passes (the event layer does not open it itself)");
reset_all();
p = mk(5, NET_SOCK_STATE_CLOSED, 2404u);
@@ -487,7 +487,7 @@ static void tc_reconcile(void)
recon_step(6400u);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (p->last_peer_port == 999u) &&
(p->lost_conn_seen == 0u) && (g_req_n == 0),
"TC-NETEV-056 U11 重同步:软件 CLOSED 而芯片在 LISTEN -> 直接跟芯片,不多余重开");
"TC-NETEV-056 U11 resync: software CLOSED while the chip is LISTEN -> follow the chip, no redundant reopen");
reset_all();
p = mk(6, NET_SOCK_STATE_CLOSED, 2404u);
@@ -495,13 +495,13 @@ static void tc_reconcile(void)
recon_step(7500u);
recon_step(7600u);
CHECK((g_req_n == 1) && (g_req_sock == 6),
"TC-NETEV-057 软件 CLOSED 且芯片也不在监听:请求重开(重建交回监听层)");
"TC-NETEV-057 software CLOSED and the chip is not listening either: request a reopen (the listener layer rebuilds it)");
reset_all();
(void)memset(&g_tbl[7], 0, sizeof(g_tbl[7])); /* 槽位不在用 */
infos = g_nev_info_count;
recon_step(8700u);
CHECK(g_nev_info_count == infos, "TC-NETEV-058 不在用的槽位直接跳过(不产生任何动作)");
CHECK(g_nev_info_count == infos, "TC-NETEV-058 unused slots are skipped outright (no action at all)");
/* 链路日志:s_link_last 初值 0xFFFF 是哨兵——第一次看到计数只记基线、不打日志
* (否则每次上电都会冒一条"链路 up"的假事件);此后计数一变就必须留痕。
@@ -511,7 +511,7 @@ static void tc_reconcile(void)
infos = g_nev_info_count;
recon_step(9800u);
CHECK(g_nev_info_count == (infos + 1),
"TC-NETEV-051b 链路变更计数一变就留痕(基线那趟已由 051a 验证不打)");
"TC-NETEV-051b any change of the link-change counter is logged (the baseline pass is already covered by 051a)");
}
int main(void)
+37 -37
View File
@@ -194,14 +194,14 @@ static void tc_bounds(void)
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (g_chip.close_calls == 0) &&
(g_nlis_error_count == 0),
"TC-NETLIS-001 越界 fd 静默返回,不打扰芯片");
"TC-NETLIS-001 an out-of-range fd returns silently without touching the chip");
reset_all();
mk_closed_listen(0, 2404);
net_listener_request(0, NET_LISTENER_REQ_ACCEPT);
net_listener_request(0, NET_LISTENER_REQ_DROP); /* 应被忽略 */
CHECK(s_relisten_req[0] == (uint8_t)(NET_LISTENER_REQ_ACCEPT + 1U),
"TC-NETLIS-019 请求去重:后来的 reason 不覆盖首个");
"TC-NETLIS-019 request de-duplication: a later reason must not overwrite the first one");
}
/* ===================== 该不该重建 ===================== */
@@ -215,12 +215,12 @@ static void tc_should_or_not(void)
net_listener_request(0, NET_LISTENER_REQ_ACCEPT);
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_req[0] == 0u),
"TC-NETLIS-010 监听仍活着就不重建,且请求已 clear");
"TC-NETLIS-010 while the listener is alive nothing is rebuilt and the request is cleared");
g_fake.cached_open[0] = CH395F_SOCKET_CLOSED; /* 之后才真死了 */
net_listener_request(0, NET_LISTENER_REQ_RECON);
net_listener_tick();
CHECK(g_chip.open_calls == 1,
"TC-NETLIS-010b 上一步确实清掉了请求(否则这里会被去重吞掉)");
"TC-NETLIS-010b the previous step really cleared the request (otherwise de-duplication would swallow it here)");
reset_all();
mk_closed_listen(0, 2404);
@@ -228,23 +228,23 @@ static void tc_should_or_not(void)
net_listener_request(0, NET_LISTENER_REQ_EOF);
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_req[0] != 0u),
"TC-NETLIS-011 挂起 recv 未结束时让路但保留请求");
"TC-NETLIS-011 a pending recv yields the way but keeps the request");
g_pending[0] = 0;
g_send_pending[0] = 1;
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_req[0] != 0u),
"TC-NETLIS-011b 挂起 send 同样挡路");
"TC-NETLIS-011b a pending send blocks the same way");
g_send_pending[0] = 0;
net_listener_tick();
CHECK((g_chip.open_calls == 1) && (s_relisten_req[0] == 0u),
"TC-NETLIS-011c 挂起结束后自动补上重建");
"TC-NETLIS-011c once the pending op ends the rebuild happens automatically");
reset_all();
mk_closed_listen(0, 2404);
net_listener_request(0, NET_LISTENER_REQ_RECON);
net_listener_tick();
net_listener_tick();
CHECK(g_chip.open_calls == 1, "TC-NETLIS-013 重建成功后不重复重建");
CHECK(g_chip.open_calls == 1, "TC-NETLIS-013 a successful rebuild is not repeated");
}
static void tc_giveup_conditions(void)
@@ -254,7 +254,7 @@ static void tc_giveup_conditions(void)
int cleared[5];
int errs[5];
printf("-- TC-NETLIS-012 五种放弃条件 --\n");
printf("-- TC-NETLIS-012 the five give-up conditions --\n");
reset_all();
for (i = 0; i < 5; i++) {
uint8_t before_open = (uint8_t)g_chip.open_calls;
@@ -276,19 +276,19 @@ static void tc_giveup_conditions(void)
}
CHECK((opened[0] == 0) && (opened[1] == 0) && (opened[2] == 0) &&
(opened[3] == 0) && (opened[4] == 0),
"TC-NETLIS-012 五种条件下都不碰芯片");
"TC-NETLIS-012 none of the five conditions touches the chip");
CHECK((cleared[0] == 1) && (cleared[1] == 1) && (cleared[2] == 1) &&
(cleared[3] == 1) && (cleared[4] == 1),
"TC-NETLIS-012b 放弃即清请求(不留在表里反复白试)");
"TC-NETLIS-012b giving up also clears the request (no repeated futile attempts left in the table)");
CHECK((errs[0] == 0) && (errs[2] == 0) && (errs[4] == 0),
"TC-NETLIS-012c 放弃是静默的(不该报 ERROR,那是对账器的活)");
"TC-NETLIS-012c giving up is silent (no ERROR here, that is the reconciler's job)");
reset_all();
g_sock_valid[0] = 0u; /* 连表项都没有 */
net_listener_request(0, NET_LISTENER_REQ_RECON);
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_req[0] == 0u),
"TC-NETLIS-026 表项不存在由 tick 直接放弃");
"TC-NETLIS-026 a missing table entry is given up directly by the tick");
/* 注:net_relisten_locked() 里的 NET_ERR_BADF 分支因此从 tick **不可达**
* (net_listener.c:112 那段只在 p_sock 非空时才可能被调到)——留痕:它是
* 内部兜底,不是活路径;将来若新增调用点,要重新审视这条。 */
@@ -308,13 +308,13 @@ static void tc_backoff_and_budget(void)
CHECK((g_chip.open_calls == 1) && (g_chip.close_calls == 1) &&
(g_errno_last == NET_ERR_BUSY) && (s_relisten_req[0] != 0u) &&
(s_relisten_next[0] == 1030u) && (s_relisten_dead[0] == 3000u),
"TC-NETLIS-014 open 失败:关回去 + BUSY + 30ms 退避 + 首轮建 2000ms 预算");
"TC-NETLIS-014 open failure: close back + BUSY + 30ms backoff + a 2000ms budget armed on the first round");
g_now = 1020u;
net_listener_tick();
CHECK(g_chip.open_calls == 1, "TC-NETLIS-014b 退避未到不重试(不空转打芯片)");
CHECK(g_chip.open_calls == 1, "TC-NETLIS-014b no retry before the backoff expires (no spinning on the chip)");
g_now = 1030u;
net_listener_tick();
CHECK(g_chip.open_calls == 2, "TC-NETLIS-014c 退避到点再试一次");
CHECK(g_chip.open_calls == 2, "TC-NETLIS-014c once the backoff expires it tries exactly once more");
reset_all();
mk_closed_listen(0, 2404);
@@ -323,7 +323,7 @@ static void tc_backoff_and_budget(void)
net_listener_tick();
CHECK((g_chip.listen_calls == 1) && (g_chip.close_calls == 1) &&
(s_relisten_req[0] != 0u),
"TC-NETLIS-015 listen 失败也要关断(不留下开了一半的 socket)");
"TC-NETLIS-015 a listen failure must also close (no half-open socket left behind)");
reset_all();
mk_closed_listen(0, 2404);
@@ -336,7 +336,7 @@ static void tc_backoff_and_budget(void)
net_listener_tick();
CHECK((g_nlis_error_count == 1) && (s_relisten_req[0] == 0u) &&
(g_chip.open_calls == 1),
"TC-NETLIS-018 预算耗尽:报一次 ERROR 交对账器,之后不再自己试");
"TC-NETLIS-018 budget exhausted: report ERROR once and hand over to the reconciler, never retry on its own");
}
/* ===================== 成功重建的簿记 ===================== */
@@ -363,22 +363,22 @@ static void tc_bookkeeping(void)
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (p->send_ready == 0u) &&
(p->recv_bytes == 0u) && (p->remote_port == 0u) &&
(p->remote_ip == 0u),
"TC-NETLIS-016 重建后回到干净监听态");
"TC-NETLIS-016 after the rebuild the state is a clean listener again");
CHECK((p->last_peer_port == 1234u) && (p->lost_conn_seen == 0u) &&
(p->released_abnormal == 0u) && (p->remote_ip_arr[0] == 0u) &&
(p->remote_ip_arr[3] == 0u),
"TC-NETLIS-016b 旧对端端口进 last_peer_port,其余对端痕迹清零");
"TC-NETLIS-016b the old peer port goes into last_peer_port and the other peer traces are zeroed");
CHECK((g_chip.set_port_calls == 1) && (g_chip.last_port == 2404u) &&
(g_chip.set_proto_calls == 1) &&
(g_chip.last_proto == CH395F_PROTO_TYPE_TCP),
"TC-NETLIS-017 源端口与协议取自表项本身(监听端口不能漂)");
"TC-NETLIS-017 the source port and protocol come from the table entry itself (the listening port must not drift)");
}
/* ===================== 沉降窗:隔离 400 / 优雅 100 ===================== */
static void tc_settle_windows(void)
{
printf("-- TC-NETLIS-020/021 沉降窗 --\n");
printf("-- TC-NETLIS-020/021 settle windows --\n");
reset_all();
mk_closed_listen(0, 2404);
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; /* 芯片关不掉 */
@@ -390,11 +390,11 @@ static void tc_settle_windows(void)
CHECK((g_chip.open_calls == 0) && (g_nlis_info_count == 1) &&
(s_relisten_settle_start[0] == 1000u) &&
(s_relisten_settle_dead[0] == 1800u) && (g_errno_last == NET_ERR_BUSY),
"TC-NETLIS-020 关不掉就等:首次记 start 并给 800ms 留痕线,返回 BUSY");
"TC-NETLIS-020 if it will not close, wait: the first pass records start plus an 800ms logging line and returns BUSY");
g_now = 1030u;
net_listener_tick();
CHECK(g_nlis_info_count == 1,
"TC-NETLIS-020b 等待期间**只在第一次**打日志(否则每 30ms 刷一条)");
"TC-NETLIS-020b while waiting the log is written only the first time (otherwise one line every 30ms)");
/* 边界必须在**独立场景**里测:放行判定与 30ms 退避共用 s_relisten_next,
* 上一轮等待会把 next 设成"当次 + 30",于是同一条链里"恰好 400ms"那一次 tick
@@ -411,7 +411,7 @@ static void tc_settle_windows(void)
g_now = 1399u;
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_quarantine[0] != 0u),
"TC-NETLIS-021 隔离级 hold=400ms:第 399ms 仍不放行");
"TC-NETLIS-021 quarantine level hold=400ms: at 399ms it still does not proceed");
reset_all();
mk_closed_listen(0, 2404);
@@ -424,7 +424,7 @@ static void tc_settle_windows(void)
g_now = 1400u;
net_listener_tick();
CHECK((g_chip.open_calls == 1) && (s_relisten_quarantine[0] == 0u),
"TC-NETLIS-021b 第 400ms 放行并清隔离(独立场景,不受退避槽遮挡)");
"TC-NETLIS-021b at 400ms it proceeds and clears the quarantine (standalone scenario, not masked by a backoff slot)");
reset_all();
mk_closed_listen(0, 2404);
@@ -433,10 +433,10 @@ static void tc_settle_windows(void)
g_now = 1000u;
net_listener_tick();
CHECK((g_chip.open_calls == 0) && (s_relisten_settle_start[0] == 1000u),
"TC-NETLIS-021c 优雅级同样先等(0 < 100ms)");
"TC-NETLIS-021c the graceful level also waits first (0 < 100ms)");
g_now = 1099u;
net_listener_tick();
CHECK(g_chip.open_calls == 0, "TC-NETLIS-021c 优雅级 hold=100ms:第 99ms 不放行");
CHECK(g_chip.open_calls == 0, "TC-NETLIS-021c graceful level hold=100ms: at 99ms it does not proceed");
reset_all();
mk_closed_listen(0, 2404);
@@ -446,12 +446,12 @@ static void tc_settle_windows(void)
net_listener_tick();
g_now = 1100u;
net_listener_tick();
CHECK(g_chip.open_calls == 1, "TC-NETLIS-021d 优雅级第 100ms 放行(比隔离级快 4 倍)");
CHECK(g_chip.open_calls == 1, "TC-NETLIS-021d the graceful level proceeds at 100ms (4x faster than the quarantine level)");
}
static void tc_cap_is_trace_only(void)
{
printf("-- TC-NETLIS-022 800ms 上限只留痕 --\n");
printf("-- TC-NETLIS-022 the 800ms ceiling only logs --\n");
reset_all();
mk_closed_listen(0, 2404);
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; /* 永远关不掉 */
@@ -464,15 +464,15 @@ static void tc_cap_is_trace_only(void)
net_listener_tick();
CHECK((s_relisten_capped[0] == 1u) && (g_nlis_info_count == 2) &&
(g_chip.open_calls == 0) && (s_relisten_req[0] != 0u),
"TC-NETLIS-022 到 800ms 只**留痕一次**,既不重建也**不放弃请求**");
"TC-NETLIS-022 reaching 800ms logs exactly once, rebuilds nothing and does not give up the request");
g_now = 1830u;
net_listener_tick();
CHECK(g_nlis_info_count == 2,
"TC-NETLIS-022b 留痕只打一次(之后继续默默等,不刷屏)");
"TC-NETLIS-022b the log line is emitted only once (it keeps waiting quietly afterwards, no flood)");
g_now = 3000u;
net_listener_tick();
CHECK((g_nlis_error_count == 1) && (s_relisten_req[0] == 0u),
"TC-NETLIS-022c 最终由 2000ms 预算接手:报 ERROR 并交出请求");
"TC-NETLIS-022c the 2000ms budget takes over in the end: report ERROR and release the request");
}
/* ===================== 芯片交互:清理、顺序、读即清 ===================== */
@@ -488,7 +488,7 @@ static void tc_chip_interaction(void)
g_now = 1000u;
net_listener_tick();
CHECK((g_chip.read_calls == 16) && (g_chip.last_read_len == 256u),
"TC-NETLIS-023 清接收区:单次钳到 256 字节、最多 16 轮(有守卫不死循环)");
"TC-NETLIS-023 clearing the receive area: clamped to 256 bytes per call and at most 16 rounds (a guard prevents an infinite loop)");
reset_all();
mk_closed_listen(0, 2404);
@@ -499,7 +499,7 @@ static void tc_chip_interaction(void)
g_now = 1000u;
net_listener_tick();
CHECK((g_chip.read_calls == 1) && (g_chip.last_read_len == 5u),
"TC-NETLIS-023b 读到空立刻停手(不硬凑 16 轮)");
"TC-NETLIS-023b an empty read stops it immediately (never padding up to 16 rounds)");
reset_all();
mk_closed_listen(0, 2404);
@@ -510,7 +510,7 @@ static void tc_chip_interaction(void)
net_listener_tick();
CHECK((strcmp(g_chip.log, "Rcis") == 0) &&
(g_chip.int_status_calls == 1) && (g_chip.get_status_calls == 1),
"TC-NETLIS-024 顺序:清接收 → 关断 → 读即清 → 读状态(不可换)");
"TC-NETLIS-024 order: clear recv -> close -> read-to-clear -> read status (fixed, must not be reordered)");
reset_all();
mk_closed_listen(0, 2404);
@@ -521,7 +521,7 @@ static void tc_chip_interaction(void)
g_now = 1000u;
net_listener_tick();
CHECK((g_chip.int_status_calls >= 1) && (g_fake.int_status[0] == 0u),
"TC-NETLIS-025 读即清真的清零(复刻芯片无按位清除的怪癖)");
"TC-NETLIS-025 read-to-clear really clears everything (mirrors the chip having no per-bit clear)");
}
/* ===================== 入口 ===================== */
+43 -43
View File
@@ -168,24 +168,24 @@ static void tc_recv_register(void)
printf("-- TC-NETPEND-001..006 recv register --\n");
reset_all();
rc = net_pending_register(-1, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001 下越界 fd 报 BADF");
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001 an fd below range reports BADF");
rc = net_pending_register(NET_MAX_SOCKETS, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001b 上越界同样拦下");
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001b an fd above range is rejected the same way");
reset_all();
rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj);
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-005 注册成功回 0x70000000 标记");
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-005 a successful registration returns the 0x70000000 marker");
rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY),
"TC-NETPEND-002 同 fd 已有挂起就拒绝(一条 socket 只挂一个 recv)");
"TC-NETPEND-002 a pending op on the same fd is refused (one recv per socket at most)");
reset_all();
rc = net_pending_register(0, g_rbuf, 10, 100u, NULL);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY),
"TC-NETPEND-003 caller 为空拒绝(没有回传目标就别挂起)");
"TC-NETPEND-003 a NULL caller is refused (nothing to report back to, so do not suspend)");
rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_owner_obj);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY),
"TC-NETPEND-004 属主任务自己发起被拒(自己等自己 = 死锁 netTask)");
"TC-NETPEND-004 the owning task starting it is refused (waiting for itself would deadlock netTask)");
reset_all();
/* 必须先把槽位置成"在用且已建立":否则 tick 第一条兜底(in_use == 0 →
@@ -193,11 +193,11 @@ static void tc_recv_register(void)
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
(void)net_pending_register(0, g_rbuf, 10, 0u, (TaskHandle_t)&g_caller_obj);
CHECK((s_net_pending[0].infinite == 1u) && (s_net_pending[0].deadline == 0u),
"TC-NETPEND-006 to_ms=0 记为无限等(不置 deadline)");
"TC-NETPEND-006 to_ms=0 is recorded as an infinite wait (no deadline armed)");
g_now = 999999u;
net_pending_tick(g_socks);
CHECK((s_net_pending[0].active == 1u) && (g_notify_n == 0),
"TC-NETPEND-006b 无限等不被时间掐掉(也不会偷偷发通知)");
"TC-NETPEND-006b an infinite wait is never cut off by time (and no notification is sent behind the scenes)");
}
static void tc_recv_paths(void)
@@ -213,7 +213,7 @@ static void tc_recv_paths(void)
(g_notify_target[0] == (void *)&g_caller_obj) &&
(g_socks[0].recv_bytes == 5u) && (g_socks[0].readable == 0u) &&
(s_net_pending[0].active == 0u),
"TC-NETPEND-007 数据到达:字节数经通知值回传给发起任务");
"TC-NETPEND-007 data arrived: the byte count is returned to the calling task through the notification value");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -222,7 +222,7 @@ static void tc_recv_paths(void)
ch395f_fake_push_recv_len(0, 100u);
net_pending_tick(g_socks);
CHECK((g_notify_value[0] == 10u) && (g_chip.last_read_len == 10u),
"TC-NETPEND-008 读量钳在调用方缓冲内(绝不越界写别人的 buf)");
"TC-NETPEND-008 the read amount is clamped into the caller buffer (never writing past someone else's buf)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -231,7 +231,7 @@ static void tc_recv_paths(void)
net_pending_tick(g_socks);
CHECK((g_notify_n == 0) && (g_socks[0].readable == 0u) &&
(s_net_pending[0].active == 1u),
"TC-NETPEND-009 空 readable:清标志继续等,不误交 0 字节");
"TC-NETPEND-009 an empty readable: clear the flag and keep waiting instead of handing over 0 bytes");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -240,14 +240,14 @@ static void tc_recv_paths(void)
net_pending_tick(g_socks);
CHECK((g_notify_value[0] == (uint32_t)NET_ERR_TIMEDOUT) &&
(g_errno_last == NET_ERR_TIMEDOUT) && (s_net_pending[0].active == 0u),
"TC-NETPEND-010 超时:错误码同时进通知值与 errno(跨上下文拿得到)");
"TC-NETPEND-010 timeout: the error code goes into both the notification value and errno (available across contexts)");
reset_all();
(void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj);
g_socks[0].in_use = 0u;
net_pending_tick(g_socks);
CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET,
"TC-NETPEND-011 槽位消失报 CONNRESET(不能让任务熬到超时才发现)");
"TC-NETPEND-011 a vanished slot reports CONNRESET (the task must not have to survive until the timeout to notice)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -256,7 +256,7 @@ static void tc_recv_paths(void)
net_pending_tick(g_socks);
CHECK((g_notify_value[0] == 12u) && (g_socks[0].recv_bytes == 12u) &&
(g_lis_req_n == 0),
"TC-NETPEND-012 关闭但有残留:先把最后一包读走再收尾(不丢尾包)");
"TC-NETPEND-012 closed with leftovers: drain the last packet before finishing (no tail data lost)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -264,7 +264,7 @@ static void tc_recv_paths(void)
net_pending_tick(g_socks);
CHECK((g_notify_value[0] == 0u) && (g_lis_req_n == 1) && (g_lis_req_sock == 0) &&
(g_lis_req_reason == (int)NET_LISTENER_REQ_PENDING),
"TC-NETPEND-013 干净结束:报 0 并请求重开监听(单连接 socket 必须回 LISTEN)");
"TC-NETPEND-013 a clean end reports 0 and requests a listener reopen (a single-connection socket must return to LISTEN)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -272,7 +272,7 @@ static void tc_recv_paths(void)
(void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj);
net_pending_tick(g_socks);
CHECK((g_notify_value[0] == 0u) && (g_lis_req_n == 0),
"TC-NETPEND-013b 不自动重开的 socket 不会被偷偷要求重建监听");
"TC-NETPEND-013b a socket with auto-relisten off is never asked to rebuild the listener behind its back");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -280,7 +280,7 @@ static void tc_recv_paths(void)
(void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj);
net_pending_tick(g_socks);
CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET,
"TC-NETPEND-014 异常释放报 CONNRESET 而不是 0(调用方要能分辨好坏收尾)");
"TC-NETPEND-014 an abnormal release reports CONNRESET instead of 0 (the caller must tell a good end from a bad one)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -288,7 +288,7 @@ static void tc_recv_paths(void)
(void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj);
net_pending_tick(g_socks);
CHECK((g_notify_n == 1) && (g_lis_req_n == 1),
"TC-NETPEND-015 session_dead 与 CLOSED 同路(状态滞后时仍能收口)");
"TC-NETPEND-015 session_dead takes the same path as CLOSED (a stale state still gets a proper close)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -296,9 +296,9 @@ static void tc_recv_paths(void)
net_pending_cancel(0, NET_ERR_CONNRESET);
CHECK((g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET) &&
(g_errno_last == NET_ERR_CONNRESET) && (s_net_pending[0].active == 0u),
"TC-NETPEND-016 取消:错误码经通知值送达并解除挂起");
"TC-NETPEND-016 cancel: the error code is delivered through the notification value and the suspension is released");
net_pending_cancel(0, NET_ERR_CONNRESET);
CHECK(g_notify_n == 1, "TC-NETPEND-016b 重复取消幂等(不给同一任务发两次通知)");
CHECK(g_notify_n == 1, "TC-NETPEND-016b a repeated cancel is idempotent (the same task is never notified twice)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -308,14 +308,14 @@ static void tc_recv_paths(void)
net_pending_cancel_all(NET_ERR_CONNRESET);
CHECK((g_notify_n == 2) && (s_net_pending[0].active == 0u) &&
(s_net_pending[2].active == 0u),
"TC-NETPEND-017 cancel_all 只通知挂着的那两个(网络整体复位时用)");
"TC-NETPEND-017 cancel_all notifies only the two suspended ops (used on a full network reset)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
(void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj);
net_pending_clear(0);
CHECK((s_net_pending[0].active == 0u) && (g_notify_n == 0),
"TC-NETPEND-018 clear 只解除挂起、不发通知(不给调用方假答复)");
"TC-NETPEND-018 clear only releases the suspension and sends no notification (the caller gets no fake answer)");
}
static void tc_settle_close(void)
@@ -328,7 +328,7 @@ static void tc_settle_close(void)
before = g_chip.read_calls + g_chip.get_status_calls;
net_pending_settle_close(0, &g_socks[0]);
CHECK((g_chip.read_calls + g_chip.get_status_calls) == before,
"TC-NETPEND-019 没挂起时直接返回(不打扰芯片)");
"TC-NETPEND-019 with nothing suspended it returns directly (the chip is not disturbed)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -337,14 +337,14 @@ static void tc_settle_close(void)
net_pending_settle_close(0, &g_socks[0]);
CHECK((g_notify_value[0] == 7u) && (g_socks[0].recv_bytes == 7u) &&
(g_socks[0].readable == 0u),
"TC-NETPEND-020 收尾时把剩余字节读走并交还字节数(收口不丢数据)");
"TC-NETPEND-020 the teardown drains the remaining bytes and returns their count (closing loses no data)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
(void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj);
net_pending_settle_close(0, &g_socks[0]);
CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET,
"TC-NETPEND-021 无残留则报 CONNRESET(关闭收尾不该被当成读到 0 字节)");
"TC-NETPEND-021 with nothing left it reports CONNRESET (a close teardown must not look like a zero-byte read)");
}
static void tc_send_paths(void)
@@ -354,31 +354,31 @@ static void tc_send_paths(void)
printf("-- TC-NETPEND-030..040 send pending --\n");
reset_all();
rc = net_send_pending_register(-1, g_wbuf, 100, 0, (TaskHandle_t)&g_caller_obj);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-030 越界 fd 报 BADF");
CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-030 an out-of-range fd reports BADF");
rc = net_send_pending_register(0, NULL, 100, 0, (TaskHandle_t)&g_caller_obj);
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030b buf 为空报 INVAL");
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030b a NULL buffer reports INVAL");
rc = net_send_pending_register(0, g_wbuf, 0, 0, (TaskHandle_t)&g_caller_obj);
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030c len 非正报 INVAL");
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030c a non-positive length reports INVAL");
rc = net_send_pending_register(0, g_wbuf, 100, -1, (TaskHandle_t)&g_caller_obj);
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030d sent 为负报 INVAL");
CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030d a negative sent count reports INVAL");
rc = net_send_pending_register(0, g_wbuf, 100, 100, (TaskHandle_t)&g_caller_obj);
CHECK((g_errno_last == NET_ERR_INVAL) && (s_net_send[0].active == 0u),
"TC-NETPEND-030e sent>=len 报 INVAL(已发完的不必挂)");
"TC-NETPEND-030e sent >= len reports INVAL (nothing left to send, no need to suspend)");
rc = net_send_pending_register(0, g_wbuf, 100, 50, NULL);
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031 caller 为空报 BUSY");
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031 a NULL caller reports BUSY");
rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_owner_obj);
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031b 属主自己发报 BUSY");
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031b the owning task sending it itself reports BUSY");
rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_caller_obj);
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-032 挂起发送注册成功");
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-032 a suspended send registers successfully");
rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_caller_obj);
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031c 同 fd 只允许一条挂起发送");
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031c only one suspended send per fd is allowed");
reset_all();
(void)net_send_pending_register(0, g_wbuf, 3000, 700, (TaskHandle_t)&g_caller_obj);
g_socks[0].in_use = 0u;
net_send_pending_tick(g_socks);
CHECK((g_notify_n == 1) && (g_notify_value[0] == 700u),
"TC-NETPEND-033 槽位消失回**已发字节数**(不是错误码,调用方据此算进度)");
"TC-NETPEND-033 a vanished slot returns the number of bytes already sent (not an error code; the caller computes progress from it)");
reset_all();
mk_sock(0, NET_SOCK_STATE_CLOSED);
@@ -386,7 +386,7 @@ static void tc_send_paths(void)
net_send_pending_tick(g_socks);
CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_CONNRESET) &&
(g_notify_value[0] == 700u),
"TC-NETPEND-034 会话塌了:留 ERROR + 报 CONNRESET,但仍把已发字节交回去");
"TC-NETPEND-034 a collapsed session logs ERROR + reports CONNRESET yet still hands back the bytes already sent");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -397,7 +397,7 @@ static void tc_send_paths(void)
(g_chip.last_read_len == (uint16_t)NET_SEND_CHUNK_MAX) &&
(g_socks[0].send_bytes == (uint32_t)NET_SEND_CHUNK_MAX) &&
(g_socks[0].send_ready == 0u) && (s_net_send[0].active == 1u),
"TC-NETPEND-035 单次写入钳在 NET_SEND_CHUNK_MAX(芯片发送缓冲上界)");
"TC-NETPEND-035 a single write is clamped to NET_SEND_CHUNK_MAX (the chip send buffer upper bound)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -410,7 +410,7 @@ static void tc_send_paths(void)
net_send_pending_tick(g_socks);
CHECK((g_notify_value[0] == 3000u) && (s_net_send[0].active == 0u) &&
(g_chip.write_calls == 3),
"TC-NETPEND-036 分三块发完 3000 字节后交还总长度");
"TC-NETPEND-036 3000 bytes finish in three chunks and the total length is returned");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -423,7 +423,7 @@ static void tc_send_paths(void)
net_send_pending_tick(g_socks);
CHECK((g_npd_error_count == 0) && (s_net_send[0].active == 1u) &&
(g_chip.write_calls == 2),
"TC-NETPEND-038 每写一轮续期 deadline:还在推进就不该被 1000ms 掐死");
"TC-NETPEND-038 every written round renews the deadline: while it progresses it must not be killed at 1000ms");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -432,7 +432,7 @@ static void tc_send_paths(void)
net_send_pending_tick(g_socks);
CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_TIMEDOUT) &&
(g_notify_value[0] == 0u),
"TC-NETPEND-037 停滞超时报 TIMEDOUT(对端不收时不能永久占着发送通道)");
"TC-NETPEND-037 a stalled wait times out with TIMEDOUT (a peer that never reads must not hold the send path forever)");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
@@ -440,16 +440,16 @@ static void tc_send_paths(void)
net_send_pending_cancel(0, NET_ERR_CONNRESET);
CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_CONNRESET) &&
(g_notify_value[0] == 40u) && (s_net_send[0].active == 0u),
"TC-NETPEND-039 取消挂起发送:留痕 + 交还已发字节");
"TC-NETPEND-039 cancelling a suspended send logs it and hands back the bytes already sent");
net_send_pending_cancel(0, NET_ERR_CONNRESET);
CHECK(g_notify_n == 1, "TC-NETPEND-039b 重复取消幂等");
CHECK(g_notify_n == 1, "TC-NETPEND-039b a repeated cancel is idempotent");
reset_all();
mk_sock(0, NET_SOCK_STATE_ESTABLISHED);
(void)net_send_pending_register(0, g_wbuf, 100, 40, (TaskHandle_t)&g_caller_obj);
net_send_pending_clear(0);
CHECK((s_net_send[0].active == 0u) && (g_notify_n == 0),
"TC-NETPEND-040 clear 不发通知(与 recv 侧同一条纪律)");
"TC-NETPEND-040 clear sends no notification (the same discipline as the recv side)");
}
int main(void)
+22 -22
View File
@@ -106,7 +106,7 @@ void osDelay(uint32_t ticks)
/* 保险:本模块每条用例都必须在有限轮内退出(要么就绪、要么超时、要么由
* g_release_after 造事件)。跑飞就说明用例或实现出了问题——直接退出,
* 绝不能把挂死留给 pre-commit / CI(上一次就是这么卡住 exe 文件名的)。 */
printf(" ABORT 轮询跑飞(>500 轮未退出)\n");
printf(" ABORT poll runaway (>500 rounds without exit)\n");
fflush(stdout);
exit(2);
}
@@ -147,9 +147,9 @@ static void tc_bounds(void)
reset_all();
NET_FD_ZERO(&r);
CHECK(net_select(0, &r, NULL, NULL, NULL) == -1, "TC-NETSEL-001 nfds=0 -> -1");
CHECK(net_select(-1, &r, NULL, NULL, NULL) == -1, "TC-NETSEL-002 nfds 为负 -> -1");
CHECK(net_select(-1, &r, NULL, NULL, NULL) == -1, "TC-NETSEL-002 negative nfds -> -1");
CHECK(net_select(NET_MAX_SOCKETS + 1, &r, NULL, NULL, NULL) == -1,
"TC-NETSEL-003 nfds 越界 -> -1(不许扫出表外)");
"TC-NETSEL-003 nfds out of range -> -1 (must not scan past the table)");
}
/* ===================== 就绪位与集合回写 ===================== */
@@ -169,14 +169,14 @@ static void tc_ready_sets(void)
NET_FD_SET(1, &r);
rc = net_select(4, &r, NULL, NULL, NULL);
CHECK((rc == 1) && NET_FD_ISSET(1, &r) && !NET_FD_ISSET(0, &r),
"TC-NETSEL-004 回写只保留就绪位,未就绪的 fd0 被清掉");
"TC-NETSEL-004 write-back keeps only the ready bits; not-ready fd0 is cleared");
reset_all();
make_sock(2, NET_SOCK_STATE_ESTABLISHED, 1u, 0u);
NET_FD_ZERO(&r);
NET_FD_SET(2, &r);
rc = net_select(3, &r, NULL, NULL, NULL); /* write/except 传 NULL */
CHECK((rc == 1) && (NET_FD_ISSET(2, &r)), "TC-NETSEL-005 其余集合可为 NULL");
CHECK((rc == 1) && (NET_FD_ISSET(2, &r)), "TC-NETSEL-005 the other sets may be NULL");
/* 注意:timeout 传 NULL 时本函数会一直等到有事件(见 008 的现状说明),
* 所以这类用例必须让被请求的 fd 真的就绪,否则就是用例自己写死的死循环。 */
@@ -187,7 +187,7 @@ static void tc_ready_sets(void)
NET_FD_SET(2, &w);
rc = net_select(3, NULL, &w, NULL, NULL);
CHECK((rc == 1) && NET_FD_ISSET(2, &w) && (r.fd_bits == 0u),
"TC-NETSEL-005b 只回写调用方真正请求过的集合");
"TC-NETSEL-005b only the sets the caller actually requested get written back");
}
/* ===================== 超时算术 ===================== */
@@ -205,7 +205,7 @@ static void tc_timeout(void)
NET_FD_SET(0, &r);
rc = net_select(1, &r, NULL, NULL, NULL); /* timeout = NULL:无限等 */
CHECK((rc == 1) && (g_delay_calls == 3) && NET_FD_ISSET(0, &r),
"TC-NETSEL-006 无限等被对端事件唤醒(第 3 轮)");
"TC-NETSEL-006 infinite wait wakes on a peer event (round 3)");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 0u, 0u);
@@ -215,7 +215,7 @@ static void tc_timeout(void)
tv.tv_usec = 1999; /* 应为 1ms,不是 2ms */
rc = net_select(1, &r, NULL, NULL, &tv);
CHECK((rc == 0) && (g_delay_calls == 0),
"TC-NETSEL-007 tv_usec 整除截断:1999us 当 1ms,首轮即退");
"TC-NETSEL-007 tv_usec integer truncation: 1999us counts as 1ms, exits on the first round");
/* 已修(2026-10-23):原来这里写的是 (timeout_ms > 0) && (已用时 >= timeout_ms),
* timeout = {0,0} 换算出 0 后**永不超时**,只能干等事件——而 POSIX 语义是
@@ -229,7 +229,7 @@ static void tc_timeout(void)
tv.tv_usec = 0;
rc = net_select(1, &r, NULL, NULL, &tv);
CHECK((rc == 0) && (g_delay_calls == 0) && !NET_FD_ISSET(0, &r),
"TC-NETSEL-008 timeout={0,0} 只查一轮立即返回(与 poll 的 0 对齐)");
"TC-NETSEL-008 timeout={0,0} checks one round and returns at once (aligned with poll timeout 0)");
}
/* ===================== 状态准入 ===================== */
@@ -250,7 +250,7 @@ static void tc_state_admission(void)
tv.tv_usec = 1000;
rc = net_select(1, &r, NULL, NULL, &tv);
CHECK((rc == 0) && !NET_FD_ISSET(0, &r),
"TC-NETSEL-009 监听态不可读(accept 语义不在 select 这层)");
"TC-NETSEL-009 listen state is not readable (accept semantics are not in the select layer)");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 0u, 1u);
@@ -258,7 +258,7 @@ static void tc_state_admission(void)
NET_FD_SET(0, &w);
rc = net_select(1, NULL, &w, NULL, &tv);
CHECK((rc == 1) && NET_FD_ISSET(0, &w),
"TC-NETSEL-010 已建立 + send_ready 才可写");
"TC-NETSEL-010 established + send_ready is what makes it writable");
reset_all();
make_sock(0, NET_SOCK_STATE_CONNECTING, 0u, 1u);
@@ -266,7 +266,7 @@ static void tc_state_admission(void)
NET_FD_SET(0, &w);
rc = net_select(1, NULL, &w, NULL, &tv);
CHECK((rc == 0) && !NET_FD_ISSET(0, &w),
"TC-NETSEL-011 连接中不可写(send_ready 不作数)");
"TC-NETSEL-011 connecting state is not writable (send_ready does not count)");
/* 已修(2026-10-23):异常判定原先排在 p_sock == NULL → continue 之后,而
* check_socket_error() 只有"表项为空 / fd 越界"才返回 1、越界的 i 又不在本循环
@@ -279,14 +279,14 @@ static void tc_state_admission(void)
NET_FD_SET(0, &r);
rc = net_select(1, NULL, NULL, &r, &tv);
CHECK((rc == 1) && NET_FD_ISSET(0, &r),
"TC-NETSEL-012 表项不存在的 fd 计入异常集合");
"TC-NETSEL-012 an fd with no table entry counts into the exception set");
reset_all();
g_sock_valid[0] = 0u;
NET_FD_ZERO(&r);
NET_FD_SET(0, &r); /* 只请求可读 */
rc = net_select(1, &r, NULL, NULL, &tv);
CHECK((rc == 0) && !NET_FD_ISSET(0, &r) && (g_delay_calls == 0),
"TC-NETSEL-012b 未请求异常集时不因缺失 fd 计数(不误伤只查可读的调用方)");
"TC-NETSEL-012b a missing fd is not counted when the exception set was not requested");
reset_all();
g_sock_valid[0] = 0u;
NET_FD_ZERO(&r);
@@ -295,7 +295,7 @@ static void tc_state_admission(void)
NET_FD_SET(0, &w); /* 请求异常:应该置位 */
rc = net_select(1, &r, NULL, &w, &tv);
CHECK((rc == 1) && NET_FD_ISSET(0, &w) && !NET_FD_ISSET(0, &r),
"TC-NETSEL-012c 异常位只落到被请求的那个集合,不污染可读集合");
"TC-NETSEL-012c the exception bit lands only in the requested set, never polluting the read set");
}
/* ===================== poll 语义 ===================== */
@@ -308,7 +308,7 @@ static void tc_poll(void)
printf("-- TC-NETSEL-013..019 poll --\n");
reset_all();
CHECK((net_poll_events(NULL, 1, 0) == -1) && (net_poll_events(p, 0, 0) == -1),
"TC-NETSEL-013 poll 参数非法 -> -1");
"TC-NETSEL-013 invalid poll arguments -> -1");
reset_all();
(void)memset(p, 0, sizeof(p));
@@ -319,7 +319,7 @@ static void tc_poll(void)
rc = net_poll_events(p, 2, 0);
CHECK((rc == 2) && ((p[0].revents & NET_POLLNVAL) != 0) &&
((p[1].revents & NET_POLLNVAL) != 0),
"TC-NETSEL-014 负 fd 与越界 fd 都置 POLLNVAL 并计入 ready");
"TC-NETSEL-014 negative and out-of-range fd both set POLLNVAL and count as ready");
reset_all();
make_sock(0, NET_SOCK_STATE_CLOSED, 0u, 0u);
@@ -328,7 +328,7 @@ static void tc_poll(void)
p[0].events = NET_POLLIN;
rc = net_poll_events(p, 1, 0);
CHECK((rc == 1) && ((p[0].revents & NET_POLLHUP) != 0),
"TC-NETSEL-015 CLOSED 置 POLLHUP");
"TC-NETSEL-015 CLOSED sets POLLHUP");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 1u, 0u);
@@ -338,7 +338,7 @@ static void tc_poll(void)
rc = net_poll_events(p, 1, 0);
CHECK((rc == 1) && ((p[0].revents & NET_POLLIN) != 0) &&
((p[0].revents & NET_POLLHUP) == 0),
"TC-NETSEL-016 可读置 POLLIN 且不误置 HUP");
"TC-NETSEL-016 readable sets POLLIN without falsely setting HUP");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 1u, 1u);
@@ -347,7 +347,7 @@ static void tc_poll(void)
p[0].events = 0; /* 什么都不请求 */
(void)net_poll_events(p, 1, 0);
CHECK((p[0].revents & NET_POLLERR) == 0,
"TC-NETSEL-017 现状:POLLERR 实际永不为真(表项为空已被 POLLNVAL 抢先)");
"TC-NETSEL-017 current state: POLLERR is never true (an empty entry is caught earlier by POLLNVAL)");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 0u, 0u);
@@ -356,7 +356,7 @@ static void tc_poll(void)
p[0].events = NET_POLLIN;
rc = net_poll_events(p, 1, 0); /* timeout = 0 */
CHECK((rc == 0) && (g_delay_calls == 0),
"TC-NETSEL-018 poll 的 timeout=0 立即返回(与 select 那条形成对照)");
"TC-NETSEL-018 poll timeout=0 returns at once (contrast with the matching select case)");
reset_all();
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 1u, 0u);
@@ -365,7 +365,7 @@ static void tc_poll(void)
p[0].events = NET_POLLIN;
p[0].revents = NET_POLLOUT; /* 脏的上一轮残留 */
(void)net_poll_events(p, 1, 0);
CHECK((p[0].revents & NET_POLLOUT) == 0, "TC-NETSEL-019 每轮先清 revents");
CHECK((p[0].revents & NET_POLLOUT) == 0, "TC-NETSEL-019 revents is cleared at the start of every round");
}
/* ===================== 入口 ===================== */
+107 -107
View File
@@ -222,10 +222,10 @@ static void tc_101_byteorder(void)
{
reset_all();
CHECK(net_htons(0x1234U) == 0x3412U, "TC-NETSOCK-101a htons 换端");
CHECK(net_ntohs(net_htons(80U)) == 80U, "TC-NETSOCK-101b htons/ntohs 互逆");
CHECK(net_htonl(0x01020304U) == 0x04030201U, "TC-NETSOCK-101c htonl 换端");
CHECK(net_ntohl(net_htonl(0xDEADBEEFU)) == 0xDEADBEEFU, "TC-NETSOCK-101d htonl/ntohl 互逆");
CHECK(net_htons(0x1234U) == 0x3412U, "TC-NETSOCK-101a htons byte swap");
CHECK(net_ntohs(net_htons(80U)) == 80U, "TC-NETSOCK-101b htons/ntohs are inverses");
CHECK(net_htonl(0x01020304U) == 0x04030201U, "TC-NETSOCK-101c htonl byte swap");
CHECK(net_ntohl(net_htonl(0xDEADBEEFU)) == 0xDEADBEEFU, "TC-NETSOCK-101d htonl/ntohl are inverses");
}
static void tc_102_parse_ip(void)
@@ -236,12 +236,12 @@ static void tc_102_parse_ip(void)
(void)memset(a, 0xEE, sizeof(a));
parse_ip_string(NULL, a);
CHECK(a[0] == 0xEEU, "TC-NETSOCK-102a parse_ip_string(NULL) 直接返回,不写目标缓冲");
CHECK(a[0] == 0xEEU, "TC-NETSOCK-102a parse_ip_string(NULL) returns at once, target buffer untouched");
parse_ip_string("192.168.1.100", a);
CHECK(a[0] == 192U && a[1] == 168U && a[2] == 1U && a[3] == 100U,
"TC-NETSOCK-102b parse_ip_string 段 0 落在 [0](芯片 set_ip_addr 要的顺位)");
"TC-NETSOCK-102b parse_ip_string puts segment 0 at [0] (the order chip set_ip_addr wants)");
parse_ip_string("10.20.30.40", NULL); /* 只验不崩 */
CHECK(1, "TC-NETSOCK-102c parse_ip_string 目标为 NULL 不崩");
CHECK(1, "TC-NETSOCK-102c parse_ip_string with NULL target does not crash");
}
static void tc_103_alloc_and_getsock(void)
@@ -253,11 +253,11 @@ static void tc_103_alloc_and_getsock(void)
reset_all();
CHECK(net_get_sock(-1) == NULL && net_get_sock(NET_MAX_SOCKETS) == NULL,
"TC-NETSOCK-103a net_get_sock 越界返 NULL(含负数)");
CHECK(net_get_sock(0) == NULL, "TC-NETSOCK-103b 未分配的槽位返 NULL");
"TC-NETSOCK-103a net_get_sock out of range returns NULL (incl. negative)");
CHECK(net_get_sock(0) == NULL, "TC-NETSOCK-103b unallocated slot returns NULL");
CHECK(net_sock_raw(3) == &s_net_socks[3],
"TC-NETSOCK-103c net_sock_raw 不判 in_use(对账必须看到 CLOSED 槽位)");
CHECK(net_sock_state(0) == -1, "TC-NETSOCK-103d 未占用 sock_state 返 -1 且不设 errno");
"TC-NETSOCK-103c net_sock_raw skips the in_use check (reconciler must see CLOSED slots)");
CHECK(net_sock_state(0) == -1, "TC-NETSOCK-103d unused sock_state returns -1 and leaves errno alone");
/* 逐个占满:alloc_socket 线性扫描首个空闲槽 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
@@ -266,9 +266,9 @@ static void tc_103_alloc_and_getsock(void)
break;
}
}
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-103e alloc_socket 按序分配 0..7");
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-103e alloc_socket hands out 0..7 in order");
p = net_get_sock(0);
CHECK(p != NULL && p->in_use == 1U, "TC-NETSOCK-103f 分配的槽位 in_use=1");
CHECK(p != NULL && p->in_use == 1U, "TC-NETSOCK-103f allocated slot has in_use=1");
reset_all();
}
@@ -281,20 +281,20 @@ static void tc_104_socket_no_inherit(void)
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(fd == 0, "TC-NETSOCK-104a net_socket 返回首个空闲 fd");
CHECK(fd == 0, "TC-NETSOCK-104a net_socket returns the first free fd");
p = &s_net_socks[fd];
CHECK(p->ch395_sock == (uint8_t)fd, "TC-NETSOCK-104b 芯片 Socket 号与描述符一一对应");
CHECK(p->ch395_sock == (uint8_t)fd, "TC-NETSOCK-104b chip socket number maps one-to-one to the descriptor");
CHECK(p->state == NET_SOCK_STATE_CREATED && p->type == NET_SOCK_STREAM,
"TC-NETSOCK-104c 初态 CREATED + 类型记下");
"TC-NETSOCK-104c initial state CREATED with type recorded");
CHECK(p->send_ready == 1U && p->readable == 0U,
"TC-NETSOCK-104d 初态:发送缓冲视为空闲、无可读");
"TC-NETSOCK-104d initial state: send buffer seen as free, nothing readable");
/* 三条"新 fd 不得继承"的定案,逐条钉(都是真机上静默出错的形态) */
CHECK(p->blocking == 1U, "TC-NETSOCK-104e blocking 默认必须显式置 1(BSD 语义)");
CHECK(p->released_abnormal == 0U, "TC-NETSOCK-104f 不得继承上一条的释放原因(TRAP-NET-18)");
CHECK(p->session_dead == 0U, "TC-NETSOCK-104g 不得继承上一条的会话结束电平(N-32 P1)");
CHECK(p->blocking == 1U, "TC-NETSOCK-104e blocking must default to explicitly 1 (BSD semantics)");
CHECK(p->released_abnormal == 0U, "TC-NETSOCK-104f must not inherit the previous release reason (TRAP-NET-18)");
CHECK(p->session_dead == 0U, "TC-NETSOCK-104g must not inherit the previous session-dead level (N-32 P1)");
CHECK(g_peers.listener_clear_calls == 1 && g_peers.pending_clear_calls == 1 &&
g_peers.send_clear_calls == 1,
"TC-NETSOCK-104h 新 fd 必须清监听重试 + 两张挂起表(各一次)");
"TC-NETSOCK-104h new fd clears listener retry and both pending tables (once each)");
/* 脏槽位复用:把上一条会话的残留写进去,再看新建是否清干净 */
p->blocking = 0U;
@@ -307,17 +307,17 @@ static void tc_104_socket_no_inherit(void)
* 本场景没绑消息队列,公开入口会先撞上"队列未创建"的 NET_ERR,
* 而那属于分片 3 的断言面(TC-NETSOCK-110h)。close 的清零主路径在 net_close_locked。 */
CHECK(net_close_sock(&s_net_socks[fd]) == 0,
"TC-NETSOCK-104i net_close_sock 成功(close 的 memset 是清零主路径)");
"TC-NETSOCK-104i net_close_sock succeeds (its memset is the main reset path)");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
p = &s_net_socks[fd];
CHECK(fd == 0 && p->blocking == 1U && p->released_abnormal == 0U &&
p->session_dead == 0U && p->readable == 0U && p->send_ready == 1U &&
p->event_cb == NULL,
"TC-NETSOCK-104j 复用同一槽位:残留字段全部不得带走");
"TC-NETSOCK-104j reusing the slot: no leftover field may carry over");
/* 地址族不支持 → INVAL(在 alloc 之前,不占槽) */
CHECK(net_socket(NET_AF_INET6, NET_SOCK_STREAM) == -1 &&
net_get_errno() == NET_ERR_INVAL && s_net_socks[1].in_use == 0U,
"TC-NETSOCK-104k 非 INET 地址族 → INVAL 且不占槽位");
"TC-NETSOCK-104k non-INET address family -> INVAL and no slot taken");
reset_all();
}
@@ -336,22 +336,22 @@ static void tc_105_bind(void)
in.sin_addr.s_addr = net_inet_addr("192.168.1.100");
CHECK(net_bind(-1, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105a 越界 fd → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105a out of range fd -> BADF");
CHECK(net_bind(0, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105b 未创建就 bind → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105b bind before socket -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_bind(fd, NULL, sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-105c addr 为 NULL → INVAL");
"TC-NETSOCK-105c addr NULL -> INVAL");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-105d addrlen 不足 → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-105d addrlen too small -> INVAL");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0,
"TC-NETSOCK-105e TCP bind 成功");
"TC-NETSOCK-105e TCP bind succeeds");
CHECK(s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].state == NET_SOCK_STATE_BOUND,
"TC-NETSOCK-105f TCP 只记端口转主机序 + 进 BOUND,不碰芯片");
"TC-NETSOCK-105f TCP bind only stores host-order port and goes BOUND, no chip touch");
CHECK(g_chip.set_port_calls == 0 && g_chip.open_calls == 0,
"TC-NETSOCK-105g TCP bind 期间不得下发源端口/开芯片 Socket(打开留给 listen)");
"TC-NETSOCK-105g bind must not set source port or open the chip socket (open belongs to listen)");
/* port=0 → 动态端口 49152 起,并推进 */
reset_all();
@@ -360,16 +360,16 @@ static void tc_105_bind(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 49152U,
"TC-NETSOCK-105h port=0 → 分配 49152");
"TC-NETSOCK-105h port=0 allocates 49152");
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105h2 计数器已推进到 49153(分配即推进,不留重复)");
"TC-NETSOCK-105h2 counter already advanced to 49153 (advance on allocate, never reuse)");
(void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 只创建不 bind */
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105i3 创建 socket **不**消耗动态端口(只有 bind 消耗)");
"TC-NETSOCK-105i3 creating a socket does NOT consume a dynamic port (only bind does)");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 49153U && s_dynamic_port == 49154U,
"TC-NETSOCK-105i 第二次 bind 拿下一个端口(不会把同一个端口发两次)");
"TC-NETSOCK-105i second bind gets the next port (same port never issued twice)");
/* 回绕:把计数器压到 65535,分配后应回 49152(不是 0——0 会让芯片按"未绑定"处理) */
s_dynamic_port = 65535U;
@@ -378,7 +378,7 @@ static void tc_105_bind(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 65535U && s_dynamic_port == 49152U,
"TC-NETSOCK-105j 动态端口越过 65535 回绕到 49152(不回 0)");
"TC-NETSOCK-105j dynamic port wraps from 65535 back to 49152 (never to 0)");
}
static void tc_106_bind_udp(void)
@@ -393,23 +393,23 @@ static void tc_106_bind_udp(void)
/* UDP:bind 就直接开芯片侧(无监听态),且必须配 DesIP=255.255.255.255 收任意源 */
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0, "TC-NETSOCK-106a UDP bind 成功");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN, "TC-NETSOCK-106b UDP 直接进 UDP_OPEN");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0, "TC-NETSOCK-106a UDP bind succeeds");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN, "TC-NETSOCK-106b UDP goes straight to UDP_OPEN");
CHECK(g_chip.set_proto_calls == 1 && g_chip.last_proto == CH395F_PROTO_TYPE_UDP,
"TC-NETSOCK-106c 协议下发为 UDP(不是 TCP)");
"TC-NETSOCK-106c protocol set to UDP (not TCP)");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 8888U,
"TC-NETSOCK-106d 源端口随 bind 下发");
"TC-NETSOCK-106d source port delivered with bind");
CHECK(g_fx.des_ip[0][0] == 0xFFU && g_fx.des_ip[0][1] == 0xFFU &&
g_fx.des_ip[0][2] == 0xFFU && g_fx.des_ip[0][3] == 0xFFU &&
g_fx.des_port[0] == 0U,
"TC-NETSOCK-106e UDP Server 模式:DesIP=全 FF + 目的端口 0(否则只收单一对端)");
CHECK(g_chip.open_calls == 1, "TC-NETSOCK-106f UDP bind 开一次芯片 Socket");
"TC-NETSOCK-106e UDP server mode: DesIP all FF and dest port 0 (else only one peer is received)");
CHECK(g_chip.open_calls == 1, "TC-NETSOCK-106f UDP bind opens the chip socket once");
/* 槽位 >=4 必须补设收发缓冲(net_init 已配过,这里是兜底):布局 6 块/槽 */
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(fd == 0, "TC-NETSOCK-106g 先拿 fd0(对照:不补缓冲)");
CHECK(g_fxchip.set_recv_buf_calls == 0, "TC-NETSOCK-106h fd<4 的 UDP bind 不重设缓冲");
CHECK(fd == 0, "TC-NETSOCK-106g take fd0 first (control case: no buffer reconfig)");
CHECK(g_fxchip.set_recv_buf_calls == 0, "TC-NETSOCK-106h UDP bind on fd<4 does not reconfigure buffers");
reset_all();
for (fd = 0; fd < 4; fd++) {
@@ -421,7 +421,7 @@ static void tc_106_bind_udp(void)
g_fx.recv_blk_cnt[4] == (uint8_t)CH395F_RECV_BLOCKS &&
g_fx.send_blk_start[4] == (uint8_t)(4 * 6 + (int)CH395F_RECV_BLOCKS) &&
g_fx.send_blk_cnt[4] == (uint8_t)CH395F_SEND_BLOCKS,
"TC-NETSOCK-106i fd>=4 兜底重设收发缓冲,块布局与 net_init 同构(6 块/槽)");
"TC-NETSOCK-106i fd>=4 falls back to reconfiguring rx/tx buffers, same 6-block layout as net_init");
/* open 失败 → BUSY 且留在原状态(不得谎报 UDP_OPEN) */
reset_all();
@@ -431,7 +431,7 @@ static void tc_106_bind_udp(void)
net_get_errno() == NET_ERR_BUSY &&
s_net_socks[fd].state != NET_SOCK_STATE_UDP_OPEN &&
g_ns_error_count == 1,
"TC-NETSOCK-106j UDP open 失败 → BUSY + 留痕 + 状态不前进");
"TC-NETSOCK-106j UDP open failure -> BUSY plus error log and state does not advance");
}
static void tc_107_peer_and_localname(void)
@@ -445,40 +445,40 @@ static void tc_107_peer_and_localname(void)
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(net_getsockname(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107a getsockname 越界 → BADF");
"TC-NETSOCK-107a getsockname out of range -> BADF");
CHECK(net_getsockname(0, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107b 未用且 CLOSED 的槽 → BADF");
"TC-NETSOCK-107b unused CLOSED slot -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(7003);
CHECK(net_bind(fd, sa, (int)sizeof(in)) == 0, "TC-NETSOCK-107c bind 前置成功");
CHECK(net_bind(fd, sa, (int)sizeof(in)) == 0, "TC-NETSOCK-107c bind precondition ok");
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_getsockname(fd, sa, &addrlen) == 0, "TC-NETSOCK-107d getsockname 成功");
CHECK(net_getsockname(fd, sa, &addrlen) == 0, "TC-NETSOCK-107d getsockname succeeds");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 7003U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-107e 回填族/端口(网络序)/长度");
"TC-NETSOCK-107e fills family, port (network order) and length");
/* 钉**具体顺位**而不是二选一:getsockname 走的是「首段在最低字节」这一族
* (与 net_inet_ntoa、net_events 的 remote_ip 同族),所以 192.168.1.100 =
* 0x6401A8C0。net_inet_addr 返回的是另一族(首段在最高字节,0xC0A80164),
* 两族不互逆——已作为疑似缺陷报出,这里刻意不写成「两个都行」。 */
CHECK(in.sin_addr.s_addr == 0x6401A8C0U,
"TC-NETSOCK-107f 本机 IP 顺位=首段在最低字节(与 ntoa 同族;net_inet_addr 是另一族)");
"TC-NETSOCK-107f local IP byte order = first octet in low byte (ntoa family)");
addrlen = 1;
CHECK(net_getsockname(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107g 长度不足 → INVAL");
"TC-NETSOCK-107g length too small -> INVAL");
CHECK(net_getsockname(fd, sa, NULL) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107h addrlen 为 NULL → INVAL");
"TC-NETSOCK-107h addrlen NULL -> INVAL");
/* getpeername:没有 CONNECT 回读过对端 → NOTCONN(不发 SPI 是它的卖点) */
(void)memset(&in, 0, sizeof(in));
addrlen = (int)sizeof(in);
CHECK(net_getpeername(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_NOTCONN,
"TC-NETSOCK-107i 尚无对端 → NOTCONN(remote_port==0 判据)");
"TC-NETSOCK-107i no peer yet -> NOTCONN (judged by remote_port==0)");
CHECK(g_chip.get_status_calls == 0 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-107j getpeername 纯读缓存,不发任何芯片事务");
"TC-NETSOCK-107j getpeername only reads the cache, issues no chip transactions");
s_net_socks[fd].remote_port = 51999U;
s_net_socks[fd].remote_ip = 0x0B01A8C0U;
@@ -487,14 +487,14 @@ static void tc_107_peer_and_localname(void)
CHECK(net_getpeername(fd, sa, &addrlen) == 0 &&
net_ntohs(in.sin_port) == 51999U && in.sin_addr.s_addr == 0x0B01A8C0U &&
in.sin_family == NET_AF_INET,
"TC-NETSOCK-107k getpeername 回填缓存的对端 IP:端口");
"TC-NETSOCK-107k getpeername fills the cached peer IP and port");
CHECK(net_getpeername(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107l getpeername 越界 → BADF");
"TC-NETSOCK-107l getpeername out of range -> BADF");
CHECK(net_getpeername(5, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107m getpeername 未在用 → BADF");
"TC-NETSOCK-107m getpeername on unused fd -> BADF");
addrlen = 2;
CHECK(net_getpeername(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107n getpeername 长度不足 → INVAL");
"TC-NETSOCK-107n getpeername length too small -> INVAL");
reset_all();
}
@@ -506,31 +506,31 @@ static void tc_108_sockprops(void)
reset_all();
CHECK(net_setblocking(-1, 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108a setblocking 越界 → BADF");
"TC-NETSOCK-108a setblocking out of range -> BADF");
CHECK(net_setblocking(0, 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108b 未占用 → BADF");
"TC-NETSOCK-108b not in use -> BADF");
CHECK(net_set_event_cb(-1, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108c set_event_cb 越界 → BADF");
"TC-NETSOCK-108c set_event_cb out of range -> BADF");
CHECK(net_set_event_cb(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108d set_event_cb 未占用 → BADF");
"TC-NETSOCK-108d set_event_cb not in use -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_setblocking(fd, 0) == 0 && s_net_socks[fd].blocking == 0U,
"TC-NETSOCK-108e setblocking(0) 落属性(模式挂控制块,不是单次参数)");
"TC-NETSOCK-108e setblocking(0) sets the attribute (mode lives in the control block, not per call)");
CHECK(net_setblocking(fd, 5) == 0 && s_net_socks[fd].blocking == 1U,
"TC-NETSOCK-108f 非 0 一律视为阻塞");
"TC-NETSOCK-108f any non-zero value counts as blocking");
CHECK(net_set_event_cb(fd, (net_event_cb_t)0x1234, &arg) == 0 &&
s_net_socks[fd].event_cb == (net_event_cb_t)0x1234 &&
s_net_socks[fd].event_arg == &arg,
"TC-NETSOCK-108g 注册回调与用户参数");
"TC-NETSOCK-108g registers callback and user argument");
CHECK(net_set_event_cb(fd, NULL, NULL) == 0 && s_net_socks[fd].event_cb == NULL,
"TC-NETSOCK-108h cb=NULL 取消注册");
"TC-NETSOCK-108h cb=NULL unregisters");
s_net_socks[fd].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_sock_state(fd) == (int)NET_SOCK_STATE_ESTABLISHED,
"TC-NETSOCK-108i sock_state 返状态快照");
CHECK(net_sock_state(NET_MAX_SOCKETS) == -1, "TC-NETSOCK-108j sock_state 越界 -1");
"TC-NETSOCK-108i sock_state returns the state snapshot");
CHECK(net_sock_state(NET_MAX_SOCKETS) == -1, "TC-NETSOCK-108j sock_state out of range -> -1");
s_net_socks[fd].in_use = 0U;
CHECK(net_sock_state(fd) == -1, "TC-NETSOCK-108k sock_state 未占用 -1");
CHECK(net_sock_state(fd) == -1, "TC-NETSOCK-108k sock_state on unused fd -> -1");
reset_all();
}
@@ -538,10 +538,10 @@ static void tc_108_sockprops(void)
static void tc_109_generation(void)
{
reset_all();
CHECK(net_get_generation() == 0U, "TC-NETSOCK-109a 初值 0");
CHECK(net_get_generation() == 0U, "TC-NETSOCK-109a initial value 0");
net_generation_inc();
net_generation_inc();
CHECK(net_get_generation() == 2U, "TC-NETSOCK-109b 每次网络重建 +1(服务据此重建监听)");
CHECK(net_get_generation() == 2U, "TC-NETSOCK-109b each network rebuild adds 1 (services rebuild listeners from it)");
reset_all();
}
@@ -551,33 +551,33 @@ static void tc_110_queue_adopt(void)
static int q2;
reset_all();
CHECK(net_msg_queue_adopt(NULL) == -1, "TC-NETSOCK-110a 空句柄 → -1");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q1) == 0, "TC-NETSOCK-110b 首次绑定成功");
CHECK(net_msg_queue_adopt(NULL) == -1, "TC-NETSOCK-110a NULL handle -> -1");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q1) == 0, "TC-NETSOCK-110b first binding succeeds");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q2) == -1,
"TC-NETSOCK-110c 重复绑定必须被拒(不静默换句柄)");
"TC-NETSOCK-110c rebinding must be refused (no silent handle swap)");
CHECK(s_net_msg_queue == (osMessageQueueId_t)&q1,
"TC-NETSOCK-110d 重复绑定**保留首次**句柄——换掉会让已入队请求永不处理");
"TC-NETSOCK-110d rebinding keeps the FIRST handle - swapping would strand queued requests forever");
/* 未绑定时所有公开 API 一律 NET_ERR,而不是崩在空句柄上 */
reset_all();
CHECK(net_listen(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110e 未绑定队列:net_listen → NET_ERR");
"TC-NETSOCK-110e without a bound queue: net_listen -> NET_ERR");
CHECK(net_recv(0, NULL, 0, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110f 未绑定队列:net_recv → NET_ERR(越界检查在队检查之前)");
"TC-NETSOCK-110f without a bound queue: net_recv -> NET_ERR (range check precedes queue check)");
CHECK(net_send(0, "x", 1, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110g 未绑定队列:net_send → NET_ERR");
"TC-NETSOCK-110g without a bound queue: net_send -> NET_ERR");
CHECK(net_close(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110h 未绑定队列:net_close → NET_ERR");
"TC-NETSOCK-110h without a bound queue: net_close -> NET_ERR");
CHECK(net_drop_conn(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110i 未绑定队列:net_drop_conn → NET_ERR");
"TC-NETSOCK-110i without a bound queue: net_drop_conn -> NET_ERR");
CHECK(net_recv_to(0, NULL, 0, 10U) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110j 未绑定队列:net_recv_to → NET_ERR");
"TC-NETSOCK-110j without a bound queue: net_recv_to -> NET_ERR");
CHECK(net_accept(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110k 未绑定队列:net_accept → NET_ERR");
"TC-NETSOCK-110k without a bound queue: net_accept -> NET_ERR");
CHECK(net_connect(0, NULL, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110l 未绑定队列:net_connect → NET_ERR");
"TC-NETSOCK-110l without a bound queue: net_connect -> NET_ERR");
/* 越界优先于"未初始化":调用方拿到的码要能区分"我传错了"和"网还没起来" */
CHECK(net_send(NET_MAX_SOCKETS, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-110m 越界检查先于队列检查 → BADF");
"TC-NETSOCK-110m range check before queue check -> BADF");
reset_all();
}
@@ -592,10 +592,10 @@ static void tc_120_errno_slot_per_task(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_set_errno(NET_ERR_BADF);
CHECK(net_get_errno() == NET_ERR_BADF && s_net_errno_slots[0].owner == NULL,
"TC-NETSOCK-120a 内核未运行 → 用 [0] 槽且**不占槽**(不写 owner)");
"TC-NETSOCK-120a kernel not running -> uses slot [0] and takes no slot (owner not written)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-120b 内核未运行时所有任务共享 [0] 槽(退化语义)");
"TC-NETSOCK-120b with the kernel down all tasks share slot [0] (degraded semantics)");
/* 内核在跑:每个任务自己的槽位 */
g_ns_kernel_state = osKernelRunning;
@@ -605,15 +605,15 @@ static void tc_120_errno_slot_per_task(void)
(void)net_set_errno(NET_ERR_TIMEDOUT);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_OK,
"TC-NETSOCK-120c 另一个任务读不到 caller 的码(E-03 的正面表述)");
"TC-NETSOCK-120c another task cannot read the caller code (the positive wording of E-03)");
(void)net_set_errno(NET_ERR_WOULDBLOCK);
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-120d 本任务读自己的码");
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-120d a task reads its own code");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-120e 互不覆写:caller 的码没被 netTask 抢走(旧实现的红线)");
"TC-NETSOCK-120e no cross overwrite: caller code survives the netTask write (old red line)");
/* 临界区:两个任务各占一次槽 → 两次进入、两次退出,绝不泄漏嵌套计数 */
CHECK(g_ns_crit_enter == 2 && g_ns_crit_exit == 2 && ns_os_crit_depth() == 0,
"TC-NETSOCK-120f 每次**首次占槽**进一次临界区且成对退出(无泄漏)");
"TC-NETSOCK-120f each first slot take enters the critical section once and leaves in pairs");
/* 快路径只读不写:已有槽位的任务反复读写都不该再进临界区、也不该再占槽 */
{
@@ -623,18 +623,18 @@ static void tc_120_errno_slot_per_task(void)
int i_net = ns_slot_index_of((TaskHandle_t)&g_ns_task_net);
CHECK(i_caller >= 0 && i_net >= 0 && i_caller != i_net,
"TC-NETSOCK-120g 两个任务落在**不同**槽位(槽位表可查,不是靠约定)");
"TC-NETSOCK-120g two tasks land in DIFFERENT slots (table is inspectable, not convention)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_set_errno(NET_ERR_BUSY);
(void)net_get_errno();
(void)net_set_errno(NET_ERR_CONNRESET);
CHECK(g_ns_crit_enter == e0, "TC-NETSOCK-120h 命中已有槽位:全程零临界区(快路径只读)");
CHECK(g_ns_crit_enter == e0, "TC-NETSOCK-120h existing slot hit: zero critical sections (fast path is read-only)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK &&
ns_slot_owner_count() == owners0,
"TC-NETSOCK-120i 别的任务反复读也不新增槽位、也读不到 caller 的码");
"TC-NETSOCK-120i repeated reads by another task add no slot and never see the caller code");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_CONNRESET, "TC-NETSOCK-120j caller 读到自己最新的码");
CHECK(net_get_errno() == NET_ERR_CONNRESET, "TC-NETSOCK-120j caller reads its own latest code");
}
/* 槽位用尽 → 回退 [0](刻意不重试,避免把竞态带回来) */
@@ -648,14 +648,14 @@ static void tc_120_errno_slot_per_task(void)
(void)net_set_errno(NET_ERR_ISCONN);
}
g_ns_cur_task = (TaskHandle_t)&filler[0];
CHECK(net_get_errno() == NET_ERR_ISCONN, "TC-NETSOCK-120k 8 个槽各归各主");
CHECK(net_get_errno() == NET_ERR_ISCONN, "TC-NETSOCK-120k 8 slots, one owner each");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; /* 第 9 个任务 */
(void)net_set_errno(NET_ERR_NOMEM);
CHECK(net_get_errno() == NET_ERR_NOMEM,
"TC-NETSOCK-120l 槽位用尽:新任务落 [0] 槽仍读得到自己的码");
"TC-NETSOCK-120l slots exhausted: a new task still reads its own code from slot [0]");
g_ns_cur_task = (TaskHandle_t)&filler[0];
CHECK(net_get_errno() == NET_ERR_NOMEM,
"TC-NETSOCK-120m 用尽后退化为共享 [0] 槽(只影响排障可读性,不新增共享槽位)");
"TC-NETSOCK-120m after exhaustion it degrades to the shared slot [0] (only hurts debuggability)");
}
reset_all();
}
@@ -667,7 +667,7 @@ static void tc_121_errno_slot_isr(void)
g_ns_cur_task = NULL; /* 中断上下文/无句柄 */
(void)net_set_errno(NET_ERR_CONNRESET);
CHECK(net_get_errno() == NET_ERR_CONNRESET && s_net_errno_slots[0].owner == NULL,
"TC-NETSOCK-121 句柄为 NULL(中断上下文)→ 退 [0] 槽且不改槽位表");
"TC-NETSOCK-121 NULL handle (ISR context) -> falls back to slot [0] and leaves the table alone");
reset_all();
}
@@ -675,9 +675,9 @@ static void tc_121_errno_slot_isr(void)
int main(void)
{
printf("=== NET Socket Tests (net_socket.c 主机单测) ===\n");
printf("=== NET Socket Tests (net_socket.c host unit tests) ===\n");
printf("[shard1] 纯函数面与 fd 表\n");
printf("[shard1] pure functions and fd table\n");
tc_100_inet_helpers();
tc_101_byteorder();
tc_102_parse_ip();
@@ -690,20 +690,20 @@ int main(void)
tc_109_generation();
tc_110_queue_adopt();
printf("[shard2] 每任务错误码槽 + 队列绑定\n");
printf("[shard2] per-task errno slots and queue binding\n");
tc_120_errno_slot_per_task();
tc_121_errno_slot_isr();
printf("[shard3] 跨上下文消息与通知值回传\n");
printf("[shard3] cross-context messages and notify-value return\n");
ns_run_s3();
printf("[shard4] 状态机与闸门\n");
printf("[shard4] state machine and gates\n");
ns_run_s4();
printf("[shard5] 建流与读写分支\n");
printf("[shard5] stream setup and read/write branches\n");
ns_run_s5();
printf("[shard6] 边界残支与其余出口\n");
printf("[shard6] edge branches and remaining exits\n");
ns_run_s6();
printf("=== NET Socket Tests: %d/%d PASSED (failed=%d) ===\n",
+7 -7
View File
@@ -118,7 +118,7 @@ void ns_rtos_post_notify(TaskHandle_t to, uint32_t v)
return;
}
}
printf("FATAL: ns_rtos_post_notify 收到未知任务句柄\n");
printf("FATAL: ns_rtos_post_notify got an unknown task handle\n");
exit(2);
}
@@ -185,7 +185,7 @@ osStatus_t osDelay(uint32_t ticks)
g_ns_tick += ticks;
if (++guard > 20000) {
printf("FATAL: osDelay 跑飞(%d 次)—— 等待条件永不满足\n", guard);
printf("FATAL: osDelay runaway (%d calls) - the wait condition is never met\n", guard);
exit(2);
}
if (guard > 0) {
@@ -212,7 +212,7 @@ BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue,
g_ns_notify_last_val = ulValue;
g_ns_notify_last_to = xTaskToNotify;
if (p == NULL) {
printf("FATAL: xTaskNotify 收到未知任务句柄\n");
printf("FATAL: xTaskNotify got an unknown task handle\n");
exit(2);
}
p->val = ulValue;
@@ -228,7 +228,7 @@ BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
ns_notify_slot_t *p = slot_of(g_ns_cur_task);
if (p == NULL) {
printf("FATAL: xTaskNotifyWait 当前任务句柄未知\n");
printf("FATAL: xTaskNotifyWait current task handle unknown\n");
exit(2);
}
if (pulNotificationValue != NULL) {
@@ -249,7 +249,7 @@ BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
/* 阻塞分支:这就是"睡到 netTask 回执"的调度点 → 驱动一轮 netTask 再拾取 */
g_ns_block_waits++;
if (g_ns_pump == NULL) {
printf("FATAL: 走到阻塞等待但测试没装 pump 钩子\n");
printf("FATAL: reached the blocking wait but the test installed no pump hook\n");
exit(2);
}
{
@@ -267,7 +267,7 @@ BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
}
}
if (p->pending == 0) {
printf("FATAL: 阻塞等待 %d 轮仍无回执(该通知的没通知 = 真机必挂死)\n", rounds);
printf("FATAL: blocking wait still got no receipt after %d rounds (a missing notify would hang real hardware)\n", rounds);
exit(2);
}
}
@@ -326,7 +326,7 @@ GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
g_ns_on_poll(); /* 并发事件注入点(在时间推进之后,超时判定才量得到) */
}
if (g_ns_poll_calls > 400000U) {
printf("FATAL: net_poll 轮询 %lu 次仍未收敛(等待条件永不满足)\n",
printf("FATAL: net_poll still not converged after %lu rounds (wait condition never met)\n",
(unsigned long)g_ns_poll_calls);
exit(2);
}
+83 -83
View File
@@ -49,15 +49,15 @@ static void tc_200_unknown_type(void)
msg.sockfd = 0;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
msg.result = 12345; /* 脏值:分派必须覆写它 */
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-200a 原始消息入队");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b 入队本身不产生通知");
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-200a raw message enqueued");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b enqueue itself produces no notification");
net_process_messages(); /* 以 netTask 身份跑一轮 */
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c 未知 type 仍必发回执(否则调用方永久悬挂)");
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c unknown type must still send a receipt (else the caller hangs forever)");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR,
"TC-NETSOCK-200d 未知 type 的通知值 = 本条的精确错误码 NET_ERR(不是上一条残留)");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e 本轮队列被排空(非阻塞 while 语义)");
"TC-NETSOCK-200d notify value for unknown type = this message exact code NET_ERR, not a leftover");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e queue drained within this round (non-blocking while semantics)");
reset_all();
}
@@ -70,9 +70,9 @@ static void tc_201_ticks_run_before_dispatch(void)
CHECK(g_peers.listener_tick_calls == 1 && g_peers.pending_tick_calls == 1 &&
g_peers.send_tick_calls == 1 && g_peers.reconcile_calls == 1,
"TC-NETSOCK-201a 空队列也要跑满四个自愈 tick(D10/pending×2/对账)");
"TC-NETSOCK-201a even with an empty queue all four self-heal ticks run (D10/pending x2/reconcile)");
CHECK(g_ns_notify_calls == 0 && g_ns_get_calls == 1,
"TC-NETSOCK-201b 空队列:取一次即退,不发通知");
"TC-NETSOCK-201b empty queue: one peek then exit, no notification");
reset_all();
}
@@ -85,7 +85,7 @@ static void tc_202_no_queue_bound(void)
net_process_messages();
CHECK(g_peers.listener_tick_calls == 0 && g_ns_get_calls == 0,
"TC-NETSOCK-202 未绑定队列就调 net_process_messages → 立即返回(不碰芯片也不跑 tick)");
"TC-NETSOCK-202 net_process_messages without a bound queue returns at once (no chip, no ticks)");
reset_all();
}
@@ -103,16 +103,16 @@ static void tc_210_send_end_to_end(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == (int)sizeof(buf), "TC-NETSOCK-210a net_send 返回实际写入字节数");
CHECK(rc == (int)sizeof(buf), "TC-NETSOCK-210a net_send returns the number of bytes actually written");
CHECK(g_chip.write_calls == 1 && g_chip.last_read_len == (uint16_t)sizeof(buf),
"TC-NETSOCK-210b 一次写满(健康链路不拆块、不进挂起)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c 发送字节统计累计");
"TC-NETSOCK-210b one full write (a healthy path neither chunks nor goes pending)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c sent byte statistic accumulates");
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == (uint32_t)sizeof(buf),
"TC-NETSOCK-210d 结果经通知值回传(32 位整体即结果)");
"TC-NETSOCK-210d result travels back on the notify value (the whole 32 bits are the result)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-210e 通知发给**登记的那个 caller**,不是广播");
"TC-NETSOCK-210e notify goes to the REGISTERED caller only, not a broadcast");
CHECK(g_ns_block_waits == 1,
"TC-NETSOCK-210f 零等待没拾到 → 走阻塞分支(两段式拾取的第二段)");
"TC-NETSOCK-210f zero-wait found nothing -> blocking branch (second stage of the two-step peek)");
reset_all();
}
@@ -135,11 +135,11 @@ static void tc_211_send_chunking(void)
rc = net_send(fd, buf, 2500, 0);
CHECK(rc == 2500, "TC-NETSOCK-211a 2500 字节全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b 按 1024 上界切成 3 块(1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c 末块只写剩余量");
CHECK(rc == 2500, "TC-NETSOCK-211a all 2500 bytes delivered");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b split into 3 chunks at the 1024 bound (1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c last chunk writes only the remainder");
CHECK(NET_SEND_CHUNK_MAX == 1024,
"TC-NETSOCK-211d 分块粒度取芯片发送缓冲(不是假件自定的数)");
"TC-NETSOCK-211d chunk size comes from the chip send buffer, not a fake-defined number");
reset_all();
}
@@ -160,24 +160,24 @@ static void tc_212_send_pending_defers_notify(void)
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-212a SEND 消息入队");
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-212a SEND message enqueued");
net_process_messages();
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-212b 超自旋预算后登记挂起 send(而不是继续占着 netTask 忙等)");
"TC-NETSOCK-212b past the spin budget it registers a pending send instead of busy-waiting in netTask");
CHECK(g_peers.last_snd_fd == fd && g_peers.last_snd_len == (int)sizeof(buf) &&
g_peers.last_snd_sent == 0,
"TC-NETSOCK-212c 登记的进度=已写芯片字节数(此处 0),总长与 caller 一并带走");
"TC-NETSOCK-212c registered progress = bytes already written to the chip (0 here), length and caller travel too");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-212d 登记成功**绝不发即时回执**(否则「登记成功」被当成本次结果)");
"TC-NETSOCK-212d a successful registration NEVER sends an immediate receipt (else it reads as the result)");
CHECK(g_ns_notify_last_to == NULL,
"TC-NETSOCK-212e 一次通知都没发生(0x70000000 哨兵值也没可能漏进通知值)");
"TC-NETSOCK-212e not a single notification (the 0x70000000 sentinel can not leak into a notify value)");
/* 完成时刻:由挂起 send 的 tick 结束(本套由桩脚本模拟"缓冲腾出来了") */
ns_peers_arm_send_pending_done(fd, 64);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 64U,
"TC-NETSOCK-212f 挂起完成后才发回执,通知值=实际写出字节数");
"TC-NETSOCK-212f receipt only after the pending completes, notify value = bytes actually written");
reset_all();
}
@@ -201,7 +201,7 @@ static void tc_213_send_error_code_roundtrip(void)
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_NOTCONN,
"TC-NETSOCK-213a 未连接的 SEND:通知值=NET_ERR_NOTCONN(精确码回传,供调用方分支)");
"TC-NETSOCK-213a SEND on a not-connected socket: notify value = NET_ERR_NOTCONN (exact code for caller branches)");
/* 端到端:公开 net_send 的 BADF 形态(fd 越界由**分派侧**再判一次) */
reset_all();
@@ -215,16 +215,16 @@ static void tc_213_send_error_code_roundtrip(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-213b 范围内但未分配的 fd:net_get_sock 判空 → BADF 搭通知值回来");
"TC-NETSOCK-213b in range but unallocated fd: net_get_sock null -> BADF travels on the notify value");
/* 调用方上下文恢复 errno:拿到的必须是"本任务的"精确码 */
reset_all();
bring_up();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */
CHECK(rc == -1, "TC-NETSOCK-213c 对外保持 BSD 语义:失败一律 -1");
CHECK(rc == -1, "TC-NETSOCK-213c BSD semantics kept for callers: any failure is -1");
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-213d 调用方在自己的错误码槽里拿到精确码(E-03 + 通知值回传合力)");
"TC-NETSOCK-213d caller reads the exact code from its own errno slot (E-03 plus notify value together)");
reset_all();
}
@@ -243,13 +243,13 @@ static void tc_220_recv_fast_path(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, 10, NET_MSG_DONTWAIT);
CHECK(rc == 10, "TC-NETSOCK-220a 缓冲 1400B、只给 10B 缓冲 → 读满 10(部分读合法)");
CHECK(rc == 10, "TC-NETSOCK-220a chip has 1400B but the caller buffer is 10B -> reads 10 (a partial read is legal)");
CHECK(g_chip.read_calls == 1 && g_chip.last_read_len == 10U,
"TC-NETSOCK-220b 只读调用方给的量(不得按芯片可用量越界写)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c 接收字节统计累计");
"TC-NETSOCK-220b reads only what the caller offered (never the chip available length)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c received byte statistic accumulates");
CHECK(s_net_socks[fd].readable == 0U,
"TC-NETSOCK-220d 读过即清 readable(下一次 RECV_OK 中断再置)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e 数据已在手,不该登记挂起");
"TC-NETSOCK-220d reading clears readable (the next RECV_OK interrupt sets it again)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e data is already in hand, no pending registration");
reset_all();
}
@@ -266,18 +266,18 @@ static void tc_221_recv_wouldblock(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221a DONTWAIT 无数据 → -1 + WOULDBLOCK");
"TC-NETSOCK-221a DONTWAIT with no data -> -1 plus WOULDBLOCK");
CHECK(g_peers.pending_reg_calls == 0,
"TC-NETSOCK-221b 非阻塞绝不登记挂起(否则调用方以为自己在等,实际已被挂住)");
"TC-NETSOCK-221b non-blocking never registers pending (else the caller thinks it waits while it is parked)");
/* 模式属性(net_setblocking(0))与单次标志同判据 */
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c 切非阻塞模式成功");
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c switching to non-blocking mode succeeds");
rc = net_recv(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221d blocking 属性=0 与 NET_MSG_DONTWAIT 等价生效");
"TC-NETSOCK-221d blocking attribute 0 behaves exactly like NET_MSG_DONTWAIT");
CHECK(net_recv_nb(fd, buf, (int)sizeof(buf)) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221e net_recv_nb 具名入口同路径");
"TC-NETSOCK-221e net_recv_nb named entry takes the same path");
reset_all();
}
@@ -296,12 +296,12 @@ static void tc_222_recv_eof_relisten(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0, "TC-NETSOCK-222a CLOSED+无数据 → EOF(0),不是错误");
CHECK(rc == 0, "TC-NETSOCK-222a CLOSED with no data -> EOF(0), not an error");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_EOF,
"TC-NETSOCK-222b EOF 登记 REQ_EOF(重建动作交监听层唯一执行者,本层不动芯片)");
"TC-NETSOCK-222b EOF registers REQ_EOF (the rebuild belongs to the listener layer, this layer never touches the chip)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0 && g_chip.close_calls == 0,
"TC-NETSOCK-222c 登记路径上**一个芯片事务都不发**(单一所有者不变量 II)");
"TC-NETSOCK-222c the registration path issues NOT ONE chip transaction (single-owner invariant II)");
/* 抢先 relisten 后 state 已回 LISTENING,但会话电平仍是"已结束" */
reset_all();
@@ -313,7 +313,7 @@ static void tc_222_recv_eof_relisten(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0,
"TC-NETSOCK-222d 已 relisten 回 LISTENING 仍判 EOF(N-32 P1 电平化:否则 EOF 永久失踪)");
"TC-NETSOCK-222d still EOF after relisten moved it back to LISTENING (N-32 P1 level, else EOF vanishes forever)");
/* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */
reset_all();
@@ -325,7 +325,7 @@ static void tc_222_recv_eof_relisten(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-222e 非监听 fd 的 EOF 不许替调用方擅自重建");
"TC-NETSOCK-222e EOF on a non-listener fd must not rebuild behind the caller back");
reset_all();
}
@@ -351,19 +351,19 @@ static void tc_223_recv_pending_roundtrip(void)
net_process_messages();
CHECK(g_peers.pending_reg_calls == 1 && g_peers.last_pend_fd == fd,
"TC-NETSOCK-223a 阻塞无数据 → 登记挂起 recv(netTask 零占用返回)");
"TC-NETSOCK-223a blocking with no data -> registers a pending recv (netTask returns with zero occupancy)");
CHECK(g_peers.last_pend_to == 250U,
"TC-NETSOCK-223b 调用方的显式预算随登记带走(0=无限,这里必须不是 0)");
"TC-NETSOCK-223b the caller explicit budget travels with the registration (0 means infinite, here it must not be 0)");
CHECK(g_peers.last_pend_caller == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-223c 完成时要知道通知谁");
"TC-NETSOCK-223c completion must know whom to notify");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-223d 登记即发回执 = 把「登记成功」当结果,本条钉它没发生");
"TC-NETSOCK-223d registering then immediately notifying would pass off registration as the result - pinned absent");
/* 数据到达:由挂起 recv 的 tick 结束(脚本模拟) */
ns_peers_arm_pending_done(fd, 7);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 7U,
"TC-NETSOCK-223e 数据到达后才回执,通知值=字节数");
"TC-NETSOCK-223e receipt only once data arrives, notify value = byte count");
reset_all();
}
@@ -381,7 +381,7 @@ static void tc_224_recv_to_timeout_code(void)
/* 先造一条"已登记"的挂起,再让 tick 判到点 */
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
rc = net_recv_to(fd, buf, (int)sizeof(buf), 30U);
CHECK(rc == -2, "TC-NETSOCK-224a 本片超时 → 独立返回码 -2(与真错误 -1 区分)");
CHECK(rc == -2, "TC-NETSOCK-224a this slice timed out -> dedicated return code -2 (distinct from a real error -1)");
reset_all();
}
@@ -396,21 +396,21 @@ static void tc_225_recv_bad_args(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(0, buf, 4, 0U) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225a net_recv_to(timeout=0) 直接拒——0 在 netTask 侧是「无限等待」,"
"与本接口「有界占用」的存在理由相反");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b 拒绝发生在入队之前");
"TC-NETSOCK-225a net_recv_to(timeout=0) is refused up front - 0 means infinite wait on the netTask side,"
"which contradicts this API whole reason for having a bounded occupancy");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b the refusal happens before enqueue");
CHECK(net_recv(fd, NULL, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225c buf 为 NULL → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225c buf NULL -> INVAL");
CHECK(net_recv(fd, buf, 0, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225d len<=0 → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225d len <= 0 -> INVAL");
CHECK(net_send(fd, NULL, 4, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225e send 的 buf 为 NULL → INVAL");
"TC-NETSOCK-225e send with buf NULL -> INVAL");
CHECK(net_send(fd, buf, -1, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225f send 的 len<0 → INVAL");
"TC-NETSOCK-225f send with len < 0 -> INVAL");
CHECK(g_chip.write_calls == 0 && g_chip.read_calls == 0 &&
g_peers.pending_reg_calls == 0 && g_peers.send_reg_calls == 0,
"TC-NETSOCK-225g 参数非法路径上零芯片事务、零挂起登记");
"TC-NETSOCK-225g on the invalid-argument path: zero chip transactions and zero pending registrations");
reset_all();
}
@@ -433,7 +433,7 @@ static void tc_230_close_order_and_peer(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages(); /* 走登记路径(不发回执) */
CHECK(g_peers.pending_reg_calls == 1 && g_ns_notify_calls == 0,
"TC-NETSOCK-230a 前置:已登记挂起且未回执");
"TC-NETSOCK-230a precondition: pending registered and no receipt yet");
/* 芯片侧此刻仍是 OPEN:close 才需要真去拆(已 CLOSED 的形态见 TC-NETSOCK-232) */
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
@@ -450,19 +450,19 @@ static void tc_230_close_order_and_peer(void)
net_process_messages();
CHECK(g_peers.pending_settle_calls == s_close_base_settle + 1,
"TC-NETSOCK-230b close 必先结算本 fd 的挂起 recv(否则等待方永久悬挂)");
"TC-NETSOCK-230b close must first settle this fd pending recv (else the waiter hangs forever)");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-230c 结算必通知(取消也要通知,不是静默清槽)");
"TC-NETSOCK-230c settling always notifies (a cancel notifies too, not a silent slot clear)");
CHECK(g_chip.close_calls >= 1 && g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-230d 芯片仍 OPEN → 走 sock_reset 确定性排渣把它拆掉");
"TC-NETSOCK-230d chip still OPEN -> sock_reset deterministically drains and tears it down");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-230e close 全程不得读全局中断寄存器(B3:唯一读者=net_poll)");
"TC-NETSOCK-230e close must never read the global interrupt register (B3: sole reader is net_poll)");
CHECK(s_net_socks[fd].in_use == 0U && s_net_socks[fd].last_peer_port == 51999U,
"TC-NETSOCK-230f 控制块清零但 last_peer_port 保留(幽灵甄别基准属芯片 Socket 级)");
"TC-NETSOCK-230f control block cleared but last_peer_port kept (ghost discrimination is chip-socket level)");
CHECK(g_peers.listener_clear_calls == s_close_base_clear + 1,
"TC-NETSOCK-230g close 幂等清理监听重试(关闭即取消待重试,D10)");
"TC-NETSOCK-230g close idempotently clears the listener retry (closing cancels pending retries, D10)");
CHECK(g_peers.pend_active_now[fd] == 0,
"TC-NETSOCK-230h close 后本 fd 不再有挂起 recv(两张表都归零)");
"TC-NETSOCK-230h after close this fd has no pending recv (both tables zeroed)");
reset_all();
}
@@ -479,8 +479,8 @@ static void tc_231_close_badfd(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-231a close 未分配的 fd → BADF 经通知值回传");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF 不碰芯片");
"TC-NETSOCK-231a close on an unallocated fd -> BADF travels back on the notify value");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF does not touch the chip");
bring_up();
(void)memset(&msg, 0, sizeof(msg));
@@ -490,7 +490,7 @@ static void tc_231_close_badfd(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-231c accept 未在用 → INVAL(控制块级校验,不是 fd 越界)");
"TC-NETSOCK-231c accept on an unused fd -> INVAL (control-block check, not fd range)");
reset_all();
}
@@ -508,7 +508,7 @@ static void tc_240_drop_conn_branches(void)
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-240a 未分配 → BADF");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-240a unallocated -> BADF");
/* UDP → INVAL */
bring_up();
@@ -520,7 +520,7 @@ static void tc_240_drop_conn_branches(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-240b 非 TCP 流 → INVAL(本 API 只拆 TCP 会话)");
"TC-NETSOCK-240b not a TCP stream -> INVAL (this API only tears down TCP sessions)");
/* 监听态但无活动会话 → 幂等 0,不动芯片 */
bring_up();
@@ -533,9 +533,9 @@ static void tc_240_drop_conn_branches(void)
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == 0U, "TC-NETSOCK-240c 无活动会话 → 幂等成功 0");
CHECK(g_ns_notify_last_val == 0U, "TC-NETSOCK-240c no active session -> idempotent success 0");
CHECK(g_chip.close_calls == 0 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240d 幂等分支不得拆芯片(会把在听的监听毁掉)");
"TC-NETSOCK-240d the idempotent branch must not tear down the chip (it would destroy the live listener)");
/* ESTABLISHED + 监听型 → QUARANTINE 拆除 + 登记重建 */
bring_up();
@@ -549,18 +549,18 @@ static void tc_240_drop_conn_branches(void)
net_process_messages();
CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1,
"TC-NETSOCK-240e 拆会话前先结束两张表的挂起(必通知)");
"TC-NETSOCK-240e both pending tables are ended before dropping the session (with notification)");
CHECK(g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-240f 拆除走 sock_reset 确定性排渣(不是 close+盲等)");
"TC-NETSOCK-240f teardown uses sock_reset deterministic draining, not close plus blind waiting");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g 监听型 fd 拆完登记 REQ_DROP(重建交监听层,本层不自己 open)");
"TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CLOSED &&
s_net_socks[fd].in_use == 1U && s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].auto_relisten == 1U,
"TC-NETSOCK-240h 只收口会话簿记:fd 保留、监听属性与端口都留下");
"TC-NETSOCK-240h only session bookkeeping is closed out: fd, listener attribute and port all survive");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-240i 拆除路径也只碰本 Socket(F-NET-7:不读全局)");
"TC-NETSOCK-240i teardown only touches its own socket (F-NET-7: never reads the global register)");
reset_all();
}
@@ -577,17 +577,17 @@ static void tc_250_put_fail_busy(void)
rc = net_send(fd, "abc", 3, 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250a 队列满 → net_send 报 BUSY(不是静默丢弃)");
"TC-NETSOCK-250a queue full -> net_send reports BUSY, it does not silently drop");
CHECK(g_chip.write_calls == 0 && g_ns_notify_calls == 0,
"TC-NETSOCK-250b 入队失败时零芯片事务、零通知(调用方不会误等回执)");
"TC-NETSOCK-250b on enqueue failure: zero chip transactions and zero notifications (caller will not wait in vain)");
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250c 同一形态对 net_listen 成立");
"TC-NETSOCK-250c the same shape holds for net_listen");
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250d 同一形态对 net_close 成立");
"TC-NETSOCK-250d the same shape holds for net_close");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250e 同一形态对 net_drop_conn 成立");
"TC-NETSOCK-250e the same shape holds for net_drop_conn");
CHECK(net_recv_to(fd, (void *)"x", 1, 5U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250f 同一形态对 net_recv_to 成立");
"TC-NETSOCK-250f the same shape holds for net_recv_to");
reset_all();
}
+57 -57
View File
@@ -31,24 +31,24 @@ static void tc_300_poll_gates(void)
/* 1) 未初始化:直接 0,连引脚都不该读 */
reset_all();
g_ns_pump = net_process_messages;
CHECK(net_poll() == 0, "TC-NETSOCK-300a 未初始化 → 0 事件");
CHECK(net_poll() == 0, "TC-NETSOCK-300a uninitialized -> 0 events");
CHECK(g_ns_poll_calls == 0U && g_fxchip.glob_calls == 0,
"TC-NETSOCK-300b 未初始化不得读 INT# 引脚、不得读全局中断(省 SPI)");
"TC-NETSOCK-300b uninitialized must not read the INT# pin nor the global interrupt register (saves SPI)");
/* 2) 初始化了但 INT# 为高(无中断待处理):不读全局寄存器 */
bring_up();
g_ns_int_pin_set = 1;
CHECK(net_poll() == 0, "TC-NETSOCK-300c INT# 高 → 0 事件");
CHECK(net_poll() == 0, "TC-NETSOCK-300c INT# high -> 0 events");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-300d INT# 高时不得发全局中断读取(引脚轮询就是为省这一步)");
"TC-NETSOCK-300d with INT# high no global interrupt read may be issued (pin polling exists to save that step)");
/* 3) INT# 低但全局状态为 0:读了全局一次,且不进 Socket 循环 */
bring_up();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob(0U);
CHECK(net_poll() == 0, "TC-NETSOCK-300e 全局位全 0 → 0 事件");
CHECK(net_poll() == 0, "TC-NETSOCK-300e all global bits clear -> 0 events");
CHECK(g_fxchip.glob_calls == 1 && g_chip.int_status_calls == 0,
"TC-NETSOCK-300f 全局只读一次,读到 0 立即收手(不逐个 Socket 扫)");
"TC-NETSOCK-300f global register read exactly once and it stops on 0 (no per-socket sweep)");
reset_all();
}
@@ -63,7 +63,7 @@ static void tc_301_poll_only_gated_sockets(void)
a = mk_established(NET_SOCK_STREAM);
b = mk_established(NET_SOCK_STREAM);
(void)mk_established(NET_SOCK_STREAM);
CHECK(a == 0 && b == 1, "TC-NETSOCK-301a 前置:三个在用 fd");
CHECK(a == 0 && b == 1, "TC-NETSOCK-301a precondition: three fds in use");
prime_sint_masks();
g_ns_int_pin_set = 0;
@@ -73,15 +73,15 @@ static void tc_301_poll_only_gated_sockets(void)
CHECK(g_fake.int_status[0] == (uint8_t)(0x01U << 0) &&
g_fake.int_status[2] == (uint8_t)(0x01U << 2),
"TC-NETSOCK-301b 未点亮的 Socket 0/2 挂起位图没被清(读即清=读过,没读=还在)");
CHECK(g_fake.int_status[1] == 0U, "TC-NETSOCK-301c 点亮的 Socket 1 被读走(位图清零)");
CHECK(g_chip.int_status_calls == 1, "TC-NETSOCK-301d 整轮只发了一次 SINT 读取");
"TC-NETSOCK-301b pending bitmaps of unlit sockets 0/2 stay uncleared (read-clear means read, still there means not read)");
CHECK(g_fake.int_status[1] == 0U, "TC-NETSOCK-301c the lit socket 1 got read (bitmap cleared)");
CHECK(g_chip.int_status_calls == 1, "TC-NETSOCK-301d the whole round issued exactly one SINT read");
/* 缓存刷新环是另一回事:它对所有在用 fd 都要刷(单一事实源 I1) */
CHECK(g_fxchip.rst_poll_calls == 3,
"TC-NETSOCK-301e 每轮对全部 3 个在用 fd 步进 sock_reset(IDLE 槽零 SPI)");
"TC-NETSOCK-301e every round steps sock_reset for all 3 in-use fds (IDLE slots cost zero SPI)");
(void)net_poll();
CHECK(g_fxchip.rst_poll_calls == 6,
"TC-NETSOCK-301f 再一轮再加 3 次:刷新环不受全局位门控(事件面与状态面分离)");
"TC-NETSOCK-301f another round adds 3 more: the refresh loop is not gated by global bits (events vs state split)");
reset_all();
/* 高位字节:Socket 4~7 的全局位在 bit8~bit11 */
@@ -94,7 +94,7 @@ static void tc_301_poll_only_gated_sockets(void)
ch395f_fake_push_glob((uint16_t)(1U << (4 + 4)));
(void)net_poll();
CHECK(g_fake.int_status[4] == 0U && g_fake.int_status[3] == (uint8_t)(0x01U << 3),
"TC-NETSOCK-301g Socket4~7 用高位字节 bit(n+4)=bit8..11 定位(算错就永远收不到 4~7 的事件)");
"TC-NETSOCK-301g sockets 4-7 are located in the high byte bit(n+4)=bit8..11 (a wrong shift never sees their events)");
reset_all();
}
@@ -121,33 +121,33 @@ static void tc_302_poll_event_dispatch(void)
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
n = net_poll();
CHECK(n == expect, "TC-NETSOCK-302a 事件计数按位型区分(SENBUF_FREE 是状态位不是事件)");
CHECK(n == expect, "TC-NETSOCK-302a event counting distinguishes bit kinds (SENBUF_FREE is a state bit, not an event)");
switch (bits[k]) {
case CH395F_SINT_STAT_CONNECT:
CHECK(g_peers.evt_connect_calls == 1, "TC-NETSOCK-302b CONNECT → handle_connect_event");
CHECK(g_peers.evt_connect_calls == 1, "TC-NETSOCK-302b CONNECT -> handle_connect_event");
break;
case CH395F_SINT_STAT_DISCONNECT:
CHECK(g_peers.evt_disconnect_calls == 1, "TC-NETSOCK-302c DISCONNECT → handle_disconnect_event");
CHECK(g_peers.evt_disconnect_calls == 1, "TC-NETSOCK-302c DISCONNECT -> handle_disconnect_event");
break;
case CH395F_SINT_STAT_RECV_OK:
CHECK(g_peers.evt_recv_calls == 1 && s_net_socks[fd].readable == 1U,
"TC-NETSOCK-302d RECV_OK → handle_recv_event 置 readable 电平");
"TC-NETSOCK-302d RECV_OK -> handle_recv_event sets the readable level");
break;
case CH395F_SINT_STAT_SEND_OK:
CHECK(g_peers.evt_send_calls == 1, "TC-NETSOCK-302e SEND_OK → handle_send_event");
CHECK(g_peers.evt_send_calls == 1, "TC-NETSOCK-302e SEND_OK -> handle_send_event");
break;
case CH395F_SINT_STAT_SOCK_TIMEOUT:
CHECK(g_peers.evt_timeout_calls == 1, "TC-NETSOCK-302f TIMEOUT → handle_timeout_event");
CHECK(g_peers.evt_timeout_calls == 1, "TC-NETSOCK-302f TIMEOUT -> handle_timeout_event");
break;
default:
CHECK(s_net_socks[fd].send_ready == 1U && g_peers.evt_send_calls == 0,
"TC-NETSOCK-302g SENBUF_FREE 只置 send_ready(不发事件、不误当 SEND_OK)");
"TC-NETSOCK-302g SENBUF_FREE only sets send_ready (no event, never mistaken for SEND_OK)");
break;
}
CHECK(g_peers.evt_connect_calls + g_peers.evt_disconnect_calls +
g_peers.evt_recv_calls + g_peers.evt_send_calls +
g_peers.evt_timeout_calls <= 1,
"TC-NETSOCK-302h 一位进来只敲一个 handler(不串味)");
"TC-NETSOCK-302h one incoming bit drives exactly one handler (no cross-talk)");
}
/* 同一次 SINT 里多个位 → 多个 handler 都被叫到,事件数叠加 */
@@ -157,9 +157,9 @@ static void tc_302_poll_event_dispatch(void)
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = (uint8_t)(CH395F_SINT_STAT_RECV_OK | CH395F_SINT_STAT_DISCONNECT);
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
CHECK(net_poll() == 2, "TC-NETSOCK-302i 同批多事件位:各计一次(不吞后到的那位)");
CHECK(net_poll() == 2, "TC-NETSOCK-302i multiple event bits in one batch: each counted (the later bit is not swallowed)");
CHECK(g_peers.evt_recv_calls == 1 && g_peers.evt_disconnect_calls == 1,
"TC-NETSOCK-302j 两位各归各的 handler");
"TC-NETSOCK-302j two bits each reach their own handler");
reset_all();
}
@@ -172,7 +172,7 @@ static void tc_303_phy_feed(void)
ch395f_fake_push_glob(CH395F_GINT_STAT_PHY_CHANGE);
(void)net_poll();
CHECK(g_fake.link_up == 0U && g_fake.link_change == 1U,
"TC-NETSOCK-303 PHY_CHANGE → 驱动重读 PHY 并计一次链路变更(拔网线不再静默)");
"TC-NETSOCK-303 PHY_CHANGE -> driver re-reads PHY and counts one link change (cable pull is no longer silent)");
reset_all();
}
@@ -183,34 +183,34 @@ static void tc_310_listener_armed(void)
reset_all();
CHECK(net_listener_armed(-1) == 0 && net_listener_armed(NET_MAX_SOCKETS) == 0,
"TC-NETSOCK-310a 越界 fd → 0(纯 RAM 读,不发 RPC)");
CHECK(net_listener_armed(0) == 0, "TC-NETSOCK-310b 未分配的 fd → 0");
"TC-NETSOCK-310a out of range fd -> 0 (pure RAM read, no RPC)");
CHECK(net_listener_armed(0) == 0, "TC-NETSOCK-310b unallocated fd -> 0");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310c ESTABLISHED(待 accept 电平)必须放行:i104 台架实证不放行则会话整场饿死");
"TC-NETSOCK-310c ESTABLISHED (pending-accept level) must pass: i104 bench proved starvation without it");
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310d LISTENING 放行:补 accept 探针在 net_accept_locked 内部,挡住永远到不了");
"TC-NETSOCK-310d LISTENING must pass: the missed-accept probe lives inside net_accept_locked, blocking it never runs");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 7003U;
s_net_socks[fd].readable = 1U;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310e CLOSED+auto_relisten+有残留数据 → 放行(保住 closed-with-readable 接受)");
"TC-NETSOCK-310e CLOSED plus auto_relisten plus leftover data -> pass (keeps closed-with-readable accept alive)");
s_net_socks[fd].readable = 0U;
CHECK(net_listener_armed(fd) == 0,
"TC-NETSOCK-310f CLOSED 但无残留 → 必须挡下(否则整个 CLOSED 窗每轮发 RPC → 喂不上狗)");
"TC-NETSOCK-310f CLOSED with no leftover must be blocked (else every round in that window sends an RPC and the watchdog starves)");
s_net_socks[fd].readable = 1U;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310g 非监听型 fd 的 CLOSED 不放行");
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310g CLOSED on a non-listener fd does not pass");
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310h 还没绑端口的半成品监听不放行");
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310h a half-built listener without a bound port does not pass");
s_net_socks[fd].state = NET_SOCK_STATE_CONNECTING;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310i CONNECTING 不是监听态 → 0");
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310i CONNECTING is not a listening state -> 0");
reset_all();
}
@@ -221,17 +221,17 @@ static void tc_311_can_read(void)
reset_all();
CHECK(net_can_read(0) == 1,
"TC-NETSOCK-311a 描述符失效 → 1:让 recv 去报 BADF,别把连接不存在藏住");
"TC-NETSOCK-311a dead descriptor -> 1: let recv report BADF instead of hiding a missing connection");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 0, "TC-NETSOCK-311b ESTABLISHED 且无可读 → 0");
CHECK(net_can_read(fd) == 0, "TC-NETSOCK-311b ESTABLISHED with nothing readable -> 0");
s_net_socks[fd].readable = 1U;
CHECK(net_can_read(fd) == 1, "TC-NETSOCK-311c readable 置位 → 1");
CHECK(net_can_read(fd) == 1, "TC-NETSOCK-311c readable set -> 1");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 1,
"TC-NETSOCK-311d 连接已非 ESTABLISHED → 必须放行一次 recv 取回 EOF/错误");
"TC-NETSOCK-311d connection no longer ESTABLISHED -> recv must pass once to fetch EOF or the error");
reset_all();
}
@@ -244,23 +244,23 @@ static void tc_320_close_two_paths(void)
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_CLOSED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320a close 成功(快路径)");
CHECK(net_close(fd) == 0, "TC-NETSOCK-320a close succeeds (fast path)");
CHECK(g_fxchip.cmd_done_calls >= 1 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-320b 已 CLOSED → 跳过复位(省掉 100~600ms 的无占用)");
"TC-NETSOCK-320b already CLOSED -> reset skipped (saves a pointless 100-600ms occupation)");
CHECK(g_fxchip.sock_events_calls >= 1,
"TC-NETSOCK-320c 快路径仍要清本 Socket 残留事件(防下一连接误判刚建立就断开)");
CHECK(g_fxchip.glob_calls == 0, "TC-NETSOCK-320d 全程零全局读取(F-NET-7)");
"TC-NETSOCK-320c the fast path still clears this socket leftover events (else the next connection sees a false immediate drop)");
CHECK(g_fxchip.glob_calls == 0, "TC-NETSOCK-320d zero global reads throughout (F-NET-7)");
reset_all();
/* 慢路径:芯片还 OPEN → 走 sock_reset,且必须是 GRACEFUL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320e close 成功(慢路径)");
CHECK(net_close(fd) == 0, "TC-NETSOCK-320e close succeeds (slow path)");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 0U,
"TC-NETSOCK-320f 慢路径用 GRACEFUL(正常释放 100ms 底),不是 QUARANTINE");
"TC-NETSOCK-320f slow path uses GRACEFUL (100ms floor for a normal release), not QUARANTINE");
CHECK(g_fx.rst_state[fd] == (uint8_t)CH395F_FAKE_RST_IDLE,
"TC-NETSOCK-320g 复位机收口回 IDLE(槽位可复用,不搁浅)");
"TC-NETSOCK-320g the reset machine ends back at IDLE (slot reusable, never stranded)");
reset_all();
/* 复位启动被拒(槽位已在运行)→ close 仍放行,只留痕 */
@@ -268,9 +268,9 @@ static void tc_320_close_two_paths(void)
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_state[fd] = (uint8_t)CH395F_FAKE_RST_DRAINED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320h 复位启动失败也放行 close(不卡调用方)");
CHECK(net_close(fd) == 0, "TC-NETSOCK-320h close still succeeds when starting the reset fails (do not block the caller)");
CHECK(g_fxchip.rst_begin_reject == 1 && g_ns_error_count >= 1,
"TC-NETSOCK-320i 复位没成必须 [ERR] 留痕,交 reconciler/pump 对账接管");
"TC-NETSOCK-320i a failed reset must leave an [ERR] line for the reconciler/pump to take over");
reset_all();
}
@@ -286,19 +286,19 @@ static void tc_321_drop_conn_settle(void)
s_net_socks[fd].lost_conn_seen = 3U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn 成功");
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn succeeds");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U,
"TC-NETSOCK-321b 拆会话用 QUARANTINE 400ms 底(TRAP-NET-18 异常释放,与 close 分级)");
"TC-NETSOCK-321b session drop uses QUARANTINE with a 400ms floor (TRAP-NET-18 abnormal release, graded against close)");
CHECK(s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-321c 新会话起点:事件未收到去抖计数清零(N-30)");
"TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)");
CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U &&
s_net_socks[fd].in_use == 1U,
"TC-NETSOCK-321d 端口/监听属性/fd 全部保留:这就是拆连接不拆监听");
"TC-NETSOCK-321d port, listener attribute and fd all survive: this is drop-the-connection-not-the-listener");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-321e 登记 REQ_DROP,重建动作在 net_listener_tick 一处");
"TC-NETSOCK-321e registers REQ_DROP; the rebuild action lives only in net_listener_tick");
CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-321f 收口会话簿记:清 readable + 转 CLOSED");
"TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED");
reset_all();
}
@@ -317,18 +317,18 @@ static void tc_322_close_before_chip(void)
g_ns_pump = NULL; /* 本场景不靠泵,直接看登记结果 */
CHECK(net_recv_sock(&s_net_socks[fd], rxbuf, (int)sizeof(rxbuf), 0)
== NET_PENDING_REGISTERED,
"TC-NETSOCK-322p 前置:阻塞 recv 登记了挂起(返回哨兵)");
"TC-NETSOCK-322p precondition: blocking recv registered a pending request (returns the sentinel)");
ch395f_fake_clear_log();
settle_at = g_peers.pending_settle_calls;
close_at = g_chip.close_calls;
CHECK(net_close_sock(&s_net_socks[fd]) == 0, "TC-NETSOCK-322a netTask 直调 close 成功");
CHECK(net_close_sock(&s_net_socks[fd]) == 0, "TC-NETSOCK-322a netTask-direct close succeeds");
CHECK(g_peers.pending_settle_calls == settle_at + 1,
"TC-NETSOCK-322b close 必先结算本 fd 的挂起 recv");
"TC-NETSOCK-322b close must first settle this fd pending recv");
CHECK(g_chip.close_calls > close_at || g_fxchip.rst_close_calls > 0,
"TC-NETSOCK-322c 结算之后芯片侧才收到 CLOSE");
"TC-NETSOCK-322c the chip only sees CLOSE after the settlement");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-322d 结算必通知(取消也要通知,不是静默清槽 → 否则等待方永久悬挂)");
"TC-NETSOCK-322d settlement always notifies (cancel notifies too, else the waiter hangs forever)");
reset_all();
}
+113 -113
View File
@@ -34,80 +34,80 @@ static void tc_400_net_init_downlink(void)
reset_all();
g_fx.exist_rc = (int)CH395F_STATUS_NOT_DETECTED;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400a 芯片不在位 → -1");
"TC-NETSOCK-400a chip not present -> -1");
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
"TC-NETSOCK-400b 不在位就不再复位/初始化(别把错误拖到协议栈里)");
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c 芯片不在位必留 [ERR](否则只剩端口不通的现象)");
"TC-NETSOCK-400b when absent it must not reset or initialise (do not drag the error into the stack)");
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c an absent chip must leave an [ERR] line (else only a dead port is left to observe)");
/* 2) 协议栈初始化失败 */
reset_all();
g_fx.init_rc = (int)CH395F_STATUS_ERROR;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400d ch395f_init 失败 → -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e 初始化失败后不得继续使能 PING(半配置状态)");
"TC-NETSOCK-400d ch395f_init failure -> -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e after an init failure PING must not be enabled (half-configured state)");
/* 3) 版本门控:低于 0x46 必须**拒置** bit3(SOCKET_CLOSE),退回芯片自动关断 */
reset_all();
g_fx.version = 0x45U;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400f 版本不足不是致命错:继续起来");
"TC-NETSOCK-400f a too-old version is not fatal: bring-up continues");
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
"TC-NETSOCK-400g ver<0x46 → FUN_PARA 实发 0(bit3 拒置)+ [ERR] 留痕,不静默带病运行");
"TC-NETSOCK-400g ver<0x46 -> FUN_PARA actually sends 0 (bit3 refused) plus [ERR], never silently degraded");
/* 4) 版本够新:FUN_PARA=0x08;bit1(TCP_SERVER 多连接) 与 bit0(保留) 都必须为 0 */
reset_all();
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400h 正常初始化成功");
"TC-NETSOCK-400h normal initialisation succeeds");
/* bit3=SOCKET_CLOSE 要置;bit1=TCP_SERVER 多连接(本项目单连接模型)与
* bit0=保留位都必须为 0——旧实现把 0x09 当「多 Socket 模式」下发,2026-10-11
* 定案为 0x08(bit0 是保留位,证据链 TRAP-NET-15)。所以掩码要考 bit0|bit1,
* 拿 0x09 考会把我们正想要的 bit3 一起算进去(第一版就这么写错了)。 */
CHECK(g_fx.fun_para == CH395F_FUN_PARA_SOCKET_CLOSE &&
(g_fx.fun_para & 0x03UL) == 0UL,
"TC-NETSOCK-400i FUN_PARA 只置 bit3,bit0/bit1 为 0(0x09 是历史误值,TRAP-NET-15)");
"TC-NETSOCK-400i FUN_PARA sets bit3 only, bit0/bit1 are 0 (0x09 was a historical mistake, TRAP-NET-15)");
CHECK(g_fx.mss == (uint16_t)CH395F_TCP_MSS && g_fx.retrans_cnt == 8U &&
g_fx.retrans_period == 200U,
"TC-NETSOCK-400j MSS/重传参数取 net_config.h 真值(8 次 / 200ms,P0-a)");
"TC-NETSOCK-400j MSS and retransmit parameters come from net_config.h truth (8 tries / 200ms, P0-a)");
CHECK(g_fx.ka_idle == 60000U && g_fx.ka_intvl == 5000U && g_fx.ka_cnt == 3U,
"TC-NETSOCK-400k KeepAlive 60s/5s/3(且必须在 ch395f_init 之前下发)");
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 参数确有下发");
"TC-NETSOCK-400k KeepAlive 60s/5s/3 and it must be issued before ch395f_init");
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 parameters really were issued");
CHECK(g_chip.log_n == 0 ||
ch395f_fake_log_find('n') > ch395f_fake_log_find('Q'),
"TC-NETSOCK-400l MSS/KeepAlive 等 SET 类命令排在 INIT_CH395 之前(手册硬约束)");
"TC-NETSOCK-400l SET-class commands (MSS/KeepAlive) come before INIT_CH395 (manual hard requirement)");
/* 5) IP/掩码/网关三样都按点分顺位下发,并缓存本机 IP 供 getsockname 用 */
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 1U &&
g_fx.ip[3] == 100U && g_fx.gw[3] == 1U && g_fx.mask[0] == 255U,
"TC-NETSOCK-400m IP/网关/掩码按 parse_ip_string 顺位下发(段 0 在 [0])");
"TC-NETSOCK-400m IP, gateway and mask follow the parse_ip_string order (segment 0 lands at [0])");
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
"TC-NETSOCK-400n 指定了静态 IP 就绝不使能 DHCP");
"TC-NETSOCK-400n with a static IP given, DHCP is never enabled");
/* 6) ip=NULL → 走 DHCP,不下发静态三样 */
reset_all();
(void)net_init(NULL, NULL, NULL, NULL);
CHECK(g_fxchip.set_dhcp_calls == 1 && g_fx.dhcp_enable == 1U,
"TC-NETSOCK-400o ip=NULL → 使能 DHCP");
"TC-NETSOCK-400o ip=NULL -> enables DHCP");
CHECK(g_fxchip.set_ip_calls == 0 && g_fxchip.set_gw_calls == 0 &&
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p DHCP 形态不下发静态地址");
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p the DHCP form issues no static address at all");
/* 7) MAC:全零=用芯片内置,不下发;非零必须下发(写 EEPROM,须在 INIT 之前) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_zero);
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q MAC 全零 → 保持芯片内置,不下发");
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q all-zero MAC -> keep the chip built-in one, do not issue it");
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_real);
CHECK(g_fxchip.set_mac_calls == 1 && g_fx.mac[0] == 0x02U && g_fx.mac[5] == 0x55U,
"TC-NETSOCK-400r 非零 MAC 下发(设备在线身份与 UI 显示一致)");
"TC-NETSOCK-400r a non-zero MAC is issued (device online identity stays consistent with the UI)");
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
ch395f_fake_log_find('M') < 0,
"TC-NETSOCK-400s SET_MAC 排在 INIT_CH395 **之前**(TRAP-NET-19:顺序反了本次上电不生效)");
"TC-NETSOCK-400s SET_MAC comes BEFORE INIT_CH395 (TRAP-NET-19: reversed order does not take effect this power cycle)");
/* 8) 收发缓冲块布局:6 块/Socket,先配数据 Socket 再配监听 Socket 0(TRAP-NET-01) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_fxchip.set_recv_buf_calls == NET_MAX_SOCKETS &&
g_fxchip.set_send_buf_calls == NET_MAX_SOCKETS,
"TC-NETSOCK-400t 8 个 Socket 全部显式重分配(上电默认只给 0~3)");
"TC-NETSOCK-400t all 8 sockets are explicitly reallocated (power-on defaults only cover 0-3)");
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (g_fx.recv_blk_start[i] != (uint8_t)(i * 6) ||
g_fx.recv_blk_cnt[i] != (uint8_t)CH395F_RECV_BLOCKS ||
@@ -117,20 +117,20 @@ static void tc_400_net_init_downlink(void)
}
}
CHECK(i == NET_MAX_SOCKETS,
"TC-NETSOCK-400u 块布局 recv=i*6 / send=recv+4,Socket7 恰好用尽 0~47(手册 §5.45)");
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v 使能 PING 响应");
"TC-NETSOCK-400u layout recv=i*6 / send=recv+4, socket 7 uses exactly blocks 0..47 (manual 5.45)");
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v PING response enabled");
/* 9) net_init 的第一件事是取消挂起并通知调用方(顺序错了 = 调用方永久挂死) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_peers.pending_cancel_all_calls == 1 &&
g_peers.send_cancel_all_calls == 1,
"TC-NETSOCK-400w 重建前两张挂起表都要取消(recv/send 各一张,漏一张就有人永远等)");
"TC-NETSOCK-400w both pending tables are cancelled before a rebuild (recv and send each one, missing one strands a task)");
CHECK(g_peers.last_pend_cancel_err == NET_ERR_CONNRESET &&
g_peers.last_snd_cancel_err == NET_ERR_CONNRESET,
"TC-NETSOCK-400x 取消原因 = CONNRESET");
"TC-NETSOCK-400x cancellation reason = CONNRESET");
CHECK(g_peers.pending_capture_calls == 1,
"TC-NETSOCK-400y 登记执行任务(挂起登记的自等死锁守卫据此判「谁在执行」)");
"TC-NETSOCK-400y registers the executing task (the pending self-wait guard judges who is executing from it)");
reset_all();
}
@@ -144,32 +144,32 @@ static void tc_410_listen_locked(void)
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410a UDP 不能 listen");
"TC-NETSOCK-410a UDP cannot listen");
/* 状态不对(还没 bind 也没到 CREATED)→ INVAL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410b 已建立的 fd 再 listen → INVAL(不许把数据通道打回监听)");
"TC-NETSOCK-410b listening again on an established fd -> INVAL (never demote a data channel to a listener)");
/* 成功路径:BOUND → LISTENING,并启用 auto_relisten */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(2404);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-410c bind 前置");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-410c bind precondition");
ch395f_fake_clear_log();
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen 成功");
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen succeeds");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING &&
s_net_socks[fd].auto_relisten == 1U && s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-410e 进 LISTENING + auto_relisten=1(BSD 监听常驻)+ 去抖计数清零");
"TC-NETSOCK-410e enters LISTENING with auto_relisten=1 (BSD listener stays resident) and debounce cleared");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 2404U,
"TC-NETSOCK-410f 源端口取控制块的 local_port(端口不能漂)");
"TC-NETSOCK-410f source port comes from the control block local_port (the port must not drift)");
CHECK(ch395f_fake_log_find('i') >= 0 &&
ch395f_fake_log_find('i') < ch395f_fake_log_find('l'),
"TC-NETSOCK-410g 清本 Socket 事件排在 TCP_LISTEN **之前**(E14:反了会吞掉新 CONNECT)");
"TC-NETSOCK-410g clearing this socket events comes BEFORE TCP_LISTEN (E14: reversed swallows the new CONNECT)");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-410h listen 全程不得读全局中断寄存器(B3 审计掉的正是这个)");
"TC-NETSOCK-410h listen must never read the global interrupt register (exactly what the B3 audit removed)");
/* open 一直失败:重试 3 次,每次都走确定性排渣,最后 BUSY */
reset_all();
@@ -177,10 +177,10 @@ static void tc_410_listen_locked(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410i open 三次仍失败 → BUSY");
"TC-NETSOCK-410i open still failing after 3 tries -> BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.rst_begin_calls == 3,
"TC-NETSOCK-410j 三次 open + 三次 sock_reset(TRAP-NET-09 的重试语义保留,不是盲等 500ms)");
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k open 没成绝不发 LISTEN 命令");
"TC-NETSOCK-410j three opens plus three sock_reset (TRAP-NET-09 retry semantics kept, not a blind 500ms wait)");
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k with no successful open, never issue the LISTEN command");
/* LISTEN 命令失败:open 已成的 Socket 必须关回去(U12 对称,否则卡 OPEN 无监听) */
reset_all();
@@ -188,11 +188,11 @@ static void tc_410_listen_locked(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.listen_rc[fd] = CH395F_ERR_VAL;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410l TCP_LISTEN 失败 → BUSY");
"TC-NETSOCK-410l TCP_LISTEN failure -> BUSY");
CHECK(g_chip.close_calls == 1 && g_chip.open_calls == 1,
"TC-NETSOCK-410m LISTEN 失败必须 close 已开的 Socket(否则下次 open 恒返 USE)");
"TC-NETSOCK-410m a failed LISTEN must close the opened socket (else every later open returns USE)");
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-410n 失败不得谎报 LISTENING");
"TC-NETSOCK-410n failure must not falsely report LISTENING");
reset_all();
}
@@ -203,24 +203,24 @@ static void tc_411_listen_public(void)
int fd;
bring_up();
CHECK(net_listen(-1) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-411a 越界 → BADF");
CHECK(net_listen(-1) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-411a out of range -> BADF");
CHECK(net_listen(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-411b 上界越界 → BADF");
"TC-NETSOCK-411b upper bound out of range -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(7003);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-411c bind");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_listen(fd) == 0, "TC-NETSOCK-411d 端到端 listen 成功(入队→分派→通知值 0)");
CHECK(net_listen(fd) == 0, "TC-NETSOCK-411d end-to-end listen succeeds (enqueue, dispatch, notify value 0)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-411e 公开入口确实驱动了芯片侧监听");
"TC-NETSOCK-411e the public entry really drove the chip-side listener");
CHECK(g_ns_notify_last_val == 0U,
"TC-NETSOCK-411f 成功的 listen 通知值=0(哨兵 0x70000000 不许漏进来)");
"TC-NETSOCK-411f a successful listen notify value is 0 (the 0x70000000 sentinel must not leak)");
/* 已监听的 fd 再 listen:netTask 里 INVAL,经通知值回传 */
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-411g 重复 listen → -1 且 errno=INVAL(精确码,不是笼统 -1)");
"TC-NETSOCK-411g listening twice -> -1 with errno INVAL (exact code, not a generic -1)");
reset_all();
}
@@ -238,12 +238,12 @@ static void tc_420_accept_shapes(void)
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_accept(fd, sa, &addrlen) == fd, "TC-NETSOCK-420a 单连接:accept 返回监听 fd 自身");
CHECK(net_accept(fd, sa, &addrlen) == fd, "TC-NETSOCK-420a single connection: accept returns the listening fd itself");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 51999U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-420b 回填对端地址与长度(lftpd 打印「谁连进来」就靠这个)");
"TC-NETSOCK-420b fills the peer address and length (lftpd prints who connected from this)");
CHECK(g_peers.lost_probe_calls == 0,
"TC-NETSOCK-420c 非 LISTENING 不触发补 accept 探针(探针是监听态专用自救)");
"TC-NETSOCK-420c non-LISTENING never calls the missed-accept probe (the probe is listener-only self-rescue)");
/* 2) LISTENING 且探针命中 → 接受(CONNECT 事件未收到的自愈路径) */
reset_all();
@@ -255,9 +255,9 @@ static void tc_420_accept_shapes(void)
g_peers.lost_probe_hit = 1;
addrlen = 0;
CHECK(net_accept(fd, NULL, NULL) == fd,
"TC-NETSOCK-420d 探针把监听转 ESTABLISHED → accept 立即成功(N-30 主根因修复)");
"TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (N-30 root fix)");
CHECK(g_peers.lost_probe_calls == 1,
"TC-NETSOCK-420e LISTENING 才调探针(每轮 accept 一次,带去抖计数)");
"TC-NETSOCK-420e only LISTENING calls the probe (once per accept round, with debounce)");
/* 3) LISTENING 且无连接 → WOULDBLOCK,且不动监听重建 */
reset_all();
@@ -266,9 +266,9 @@ static void tc_420_accept_shapes(void)
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420f 无连接 → WOULDBLOCK(不阻塞 netTask)");
"TC-NETSOCK-420f no connection -> WOULDBLOCK (never blocks netTask)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420g 监听还活着时绝不登记重建(那是 U12 事故的形态)");
"TC-NETSOCK-420g while the listener is alive, never register a rebuild (that is the U12 accident shape)");
/* 4) CLOSED + auto_relisten + RX 有残留 → 接受"已关但可读",先排空再 EOF */
reset_all();
@@ -281,11 +281,11 @@ static void tc_420_accept_shapes(void)
ch395f_fake_push_recv_len((uint8_t)fd, 1027U);
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == fd,
"TC-NETSOCK-420h closed-with-readable:接受这条连接去排空(N-30 定案,反了就是 425)");
"TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (N-30, reversed yields 425)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420i 有残留数据时**不**登记重建(先 relisten 会把已到达的数据销毁)");
"TC-NETSOCK-420i with leftover data present, do NOT register a rebuild (relisting first destroys arrived data)");
CHECK(g_chip.get_status_calls == 0,
"TC-NETSOCK-420j 排空判据只读 GET_RECV_LEN,不发状态查询");
"TC-NETSOCK-420j the drain decision only reads GET_RECV_LEN, no status query");
/* 5) CLOSED + auto_relisten + RX 空 → 登记 REQ_ACCEPT 并 WOULDBLOCK */
reset_all();
@@ -295,13 +295,13 @@ static void tc_420_accept_shapes(void)
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 21U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420k CLOSED 且无残留 → WOULDBLOCK");
"TC-NETSOCK-420k CLOSED with no leftover -> WOULDBLOCK");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_ACCEPT &&
g_peers.last_req_fd == fd,
"TC-NETSOCK-420l 消费者撞空才登记即时重建(N-32 P2:删掉它会拉长 CLOSED 窗)");
"TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (N-32 P2: removing it widens the CLOSED window)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
"TC-NETSOCK-420m 登记路径零芯片事务(重建动作只在 net_listener_tick 一处)");
"TC-NETSOCK-420m the registration path costs zero chip transactions (rebuild lives only in net_listener_tick)");
/* 6) 非监听型 CLOSED(客户端)→ 不擅自登记 */
reset_all();
@@ -310,7 +310,7 @@ static void tc_420_accept_shapes(void)
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-420n 客户端 fd 的 CLOSED 不登记监听重建");
"TC-NETSOCK-420n CLOSED on a client fd registers no listener rebuild");
reset_all();
}
@@ -324,14 +324,14 @@ static void tc_421_accept_public(void)
bring_up();
CHECK(net_accept(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-421a 越界 → BADF");
"TC-NETSOCK-421a out of range -> BADF");
fd = mk_established(NET_SOCK_STREAM);
addrlen = 0;
CHECK(net_accept(fd, sa, NULL) == fd,
"TC-NETSOCK-421b addrlen 为 NULL 仍可 accept(addr 回填被跳过,不崩)");
"TC-NETSOCK-421b accept still works with addrlen NULL (address fill skipped, no crash)");
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, NULL, &addrlen) == fd,
"TC-NETSOCK-421c addr 为 NULL:只要 fd,不回填地址");
"TC-NETSOCK-421c addr NULL: returns just the fd and fills no address");
reset_all();
}
@@ -343,13 +343,13 @@ static void tc_430_connect(void)
bring_up();
CHECK(net_connect(-1, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-430a 越界 → BADF");
"TC-NETSOCK-430a out of range -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect(fd, NULL, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430b addr=NULL → INVAL(入队之前就把参数挡下)");
"TC-NETSOCK-430b addr=NULL -> INVAL (arguments blocked before enqueue)");
CHECK(net_connect(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen 不足 → INVAL");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d 参数非法不产生消息(不占 netTask)");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen too small -> INVAL");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d invalid arguments create no message (netTask is not occupied)");
/* 成功:地址簿记 + 大端顺位 + 状态 CONNECTING */
reset_all();
@@ -361,21 +361,21 @@ static void tc_430_connect(void)
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430e connect 命令下发成功(异步,成败由中断裁定)");
"TC-NETSOCK-430e connect command issued (asynchronous, the interrupt decides success)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING,
"TC-NETSOCK-430f 本地状态转 CONNECTING");
"TC-NETSOCK-430f local state moves to CONNECTING");
CHECK(s_net_socks[fd].remote_port == 8888U,
"TC-NETSOCK-430g 对端端口转主机序存簿记");
"TC-NETSOCK-430g peer port stored in bookkeeping as host order");
CHECK(g_fx.des_ip[fd][0] == 192U && g_fx.des_ip[fd][1] == 168U &&
g_fx.des_ip[fd][2] == 1U && g_fx.des_ip[fd][3] == 200U,
"TC-NETSOCK-430h 目的 IP 按大端数组下发(set_des_ip 要的顺位,反了就连不上)");
"TC-NETSOCK-430h destination IP issued as a big-endian array (the order set_des_ip needs, reversed means no link)");
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-430i 源端口下发一次(SET_SOUR_PORT),目的端口另走 SET_DES_PORT=8888");
"TC-NETSOCK-430i source port issued once (SET_SOUR_PORT), the peer port goes via SET_DES_PORT=8888");
CHECK(g_fxchip.sock_events_calls == 1 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-430j 清残留中断只清本 Socket(F-NET-7:原「再读清全局」会焊死别人)");
"TC-NETSOCK-430j clearing stale interrupts only clears this socket (F-NET-7: the old extra global read welded others shut)");
CHECK(ch395f_fake_log_find('v') < ch395f_fake_log_find('T') ||
ch395f_fake_log_find('v') < 0,
"TC-NETSOCK-430k 清事件排在 TCP_CONNECT 之前(TRAP-NET-12/-18 的两级预清)");
"TC-NETSOCK-430k clearing events precedes TCP_CONNECT (the two-stage pre-clear of TRAP-NET-12/-18)");
/* 命令级被拒(非 BUSY):必须回滚,不能停在 CONNECTING 干等 */
reset_all();
@@ -383,13 +383,13 @@ static void tc_430_connect(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_VAL;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430l 芯片拒命令 → -1");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430l chip refused the command -> -1");
CHECK(g_chip.close_calls == 1,
"TC-NETSOCK-430m 被拒要按手册「再次连接需从 OPEN 重开」关掉 Socket");
"TC-NETSOCK-430m after a refusal the manual requires reopening from OPEN, so the socket is closed");
CHECK(s_net_socks[fd].session_dead == 1U &&
s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-430n 命令级失败同样是本会话结束:置 session_dead+CLOSED(B2:否则挂起 recv 干等)");
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o 被拒必留 [ERR]");
"TC-NETSOCK-430n a command-level failure also ends the session: sets session_dead and CLOSED (B2, else pending recv waits forever)");
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o a refusal must leave an [ERR] line");
/* BUSY 只是命令仍在执行,不得判失败 */
reset_all();
@@ -397,9 +397,9 @@ static void tc_430_connect(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_BUSY;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430p BUSY=芯片还在执行 → 维持原行为交给中断裁定(慢芯片不该被误杀)");
"TC-NETSOCK-430p BUSY means the chip is still executing -> keep the old behaviour and let the interrupt decide (do not kill a slow chip)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING && g_chip.close_calls == 0,
"TC-NETSOCK-430q BUSY 不回滚、不关 Socket");
"TC-NETSOCK-430q BUSY neither rolls back nor closes the socket");
/* open 三次失败 → BUSY,且不发 CONNECT */
reset_all();
@@ -407,21 +407,21 @@ static void tc_430_connect(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-430r open 三次失败 → BUSY");
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-430r open failing 3 times -> BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.connect_calls == 0,
"TC-NETSOCK-430s open 没成不发 TCP_CONNECT");
"TC-NETSOCK-430s with no successful open, never issue TCP_CONNECT");
/* 非 TCP 流 / 未在用 → INVAL(netTask 侧再判一次) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430t UDP 槽位不走 TCP connect");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430t a UDP slot never takes the TCP connect path");
CHECK(net_connect_locked(7, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430u 范围内但未在用 → INVAL(in_use 与控制块一起考;BADF 只考越界)");
"TC-NETSOCK-430u in range but not in use -> INVAL (in_use is checked with the control block; BADF is only for range)");
CHECK(net_connect_locked(NET_MAX_SOCKETS, (const struct net_sockaddr *)&in,
(int)sizeof(in)) == -1, "TC-NETSOCK-430v 越界 → BADF");
(int)sizeof(in)) == -1, "TC-NETSOCK-430v out of range -> BADF");
reset_all();
}
@@ -437,17 +437,17 @@ static void tc_440_udp_datagram(void)
bring_up_netctx();
CHECK(net_sendto(-1, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440a sendto 越界 → BADF");
"TC-NETSOCK-440a sendto out of range -> BADF");
CHECK(net_sendto(0, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440b 未在用 → BADF");
"TC-NETSOCK-440b not in use -> BADF");
CHECK(net_recvfrom(0, buf, sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom 未在用 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom on an unused fd -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_sendto(fd, NULL, 5, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-440d sendto 参数非法 → INVAL");
"TC-NETSOCK-440d sendto invalid arguments -> INVAL");
CHECK(net_recvfrom(fd, NULL, 5, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom buf 为空 → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom with a NULL buf -> INVAL");
/* 带目的地址:先设 DesIP/DesPort 再写数据(不带上限的自旋实现,健康链路一次写完) */
reset_all();
@@ -457,11 +457,11 @@ static void tc_440_udp_datagram(void)
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.9");
CHECK(net_sendto(fd, "hello", 5, 0, sa, (int)sizeof(in)) == 5,
"TC-NETSOCK-440f sendto 成功返回字节数");
"TC-NETSOCK-440f sendto returns the number of bytes sent");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-440g 目的地址随本次发送下发(UDP 无连接,地址是每包带的)");
"TC-NETSOCK-440g the destination address travels with this send (UDP is connectionless, address is per packet)");
CHECK(g_chip.write_calls == 1 && s_net_socks[fd].send_bytes == 5U,
"TC-NETSOCK-440h 小包一次写完 + 统计");
"TC-NETSOCK-440h small packet written in one go plus statistics");
/* 大报分块(仍受 NET_SEND_CHUNK_MAX 上界约束) */
reset_all();
@@ -470,8 +470,8 @@ static void tc_440_udp_datagram(void)
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
CHECK(net_sendto(fd, big, 2100, 0, NULL, 0) == 2100, "TC-NETSOCK-440i 2100B 全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP 也按 1024 上界分块(写穿 FIFO 的约束同 TCP)");
CHECK(net_sendto(fd, big, 2100, 0, NULL, 0) == 2100, "TC-NETSOCK-440i all 2100 bytes delivered");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP also chunks at the 1024 bound (the FIFO overrun constraint matches TCP)");
/* recvfrom:前 8 字节是芯片 UDP 头(长度/srcPort/srcIP),要剥掉再交出去 */
reset_all();
@@ -481,26 +481,26 @@ static void tc_440_udp_datagram(void)
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == 20,
"TC-NETSOCK-440k 返回 20:报文头 8 字节被剥掉,不把头部当数据交给应用");
"TC-NETSOCK-440k returns 20: the 8-byte report header was stripped, the header is not handed over as data");
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
"TC-NETSOCK-440l 回填源地址结构与长度");
"TC-NETSOCK-440l fills the source address structure and length");
/* 无数据 + 非阻塞 → WOULDBLOCK(阻塞形态是自旋无预算,本套刻意不测,见交付说明) */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440m 非阻塞无数据 → WOULDBLOCK");
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440m non-blocking with no data -> WOULDBLOCK");
s_net_socks[fd].blocking = 0U;
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440n 非阻塞属性同判据生效");
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440n the non-blocking attribute follows the same decision");
/* 短于 8 字节的残包:不剥头,整包交出去(钉当前实现的边界,不猜"应该怎么算") */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, 5U);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == 5,
"TC-NETSOCK-440o recv_len<8 时不剥头,整包返回(当前实现)");
"TC-NETSOCK-440o with recv_len<8 no header is stripped, the whole packet is returned (current implementation)");
reset_all();
}
@@ -509,11 +509,11 @@ static void tc_450_direct_entries(void)
{
bring_up();
CHECK(net_send_sock(NULL, "x", 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450a net_send_sock(NULL) → BADF");
"TC-NETSOCK-450a net_send_sock(NULL) -> BADF");
CHECK(net_recv_sock(NULL, (void *)"x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450b net_recv_sock(NULL) → BADF");
"TC-NETSOCK-450b net_recv_sock(NULL) -> BADF");
CHECK(net_close_sock(NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450c net_close_sock(NULL) → BADF");
"TC-NETSOCK-450c net_close_sock(NULL) -> BADF");
/* netTask 内直调 send:挂起登记会被"执行者自等待"守卫拒 → 返回 -1(BUSY) */
reset_all();
@@ -525,9 +525,9 @@ static void tc_450_direct_entries(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
g_peers.send_register_rc = -1; /* 复刻守卫:netTask 自己发的必被拒 */
CHECK(net_send_sock(&s_net_socks[fd], "hello", 5) == -1,
"TC-NETSOCK-450d netTask 内等不到缓冲时不登记挂起(没人能替它完成)→ -1");
"TC-NETSOCK-450d inside netTask a stalled buffer never registers pending (nobody can complete it) -> -1");
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-450e 确实走到了登记调用(被拒的是登记侧,不是本层没试)");
"TC-NETSOCK-450e the registration call really happened (the refusal came from the register side, not from us not trying)");
}
reset_all();
}
@@ -545,13 +545,13 @@ static void tc_460_reset_sync_timeout_bound(void)
t_before = g_ns_tick;
CHECK(net_close(fd) == 0,
"TC-NETSOCK-460a 芯片关不掉也照样放行 close(fd 侧簿记必须继续收口)");
"TC-NETSOCK-460a even when the chip cannot be closed, close proceeds (fd-side bookkeeping must still close out)");
CHECK((g_ns_tick - t_before) <= 700U,
"TC-NETSOCK-460b 复位等待有 600ms 硬上限(无上限=netTask 被一颗坏芯片拖死)");
"TC-NETSOCK-460b the reset wait has a hard 600ms ceiling (without one a bad chip drags netTask down)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-460c 复位超时必 [ERR]:这条 [ERR] 是唯一能发现它的信号");
"TC-NETSOCK-460c a reset timeout must leave [ERR]: that line is the only way to ever notice it");
CHECK(s_net_socks[fd].in_use == 0U,
"TC-NETSOCK-460d 超时也照常释放槽位(不因芯片状态卡住本地 fd 表)");
"TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)");
reset_all();
/* drop_conn 同一条超时线 */
@@ -563,7 +563,7 @@ static void tc_460_reset_sync_timeout_bound(void)
t_before = g_ns_tick;
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U &&
g_ns_error_count >= 1,
"TC-NETSOCK-460e drop_conn 也走同一条有界收口并留痕");
"TC-NETSOCK-460e drop_conn takes the same bounded close-out and logs it too");
reset_all();
}
@@ -586,11 +586,11 @@ static void tc_470_send_disconnect_midway(void)
rc = net_send(fd, big, 2100, 0);
CHECK(rc >= 0 && rc < 2100,
"TC-NETSOCK-470a 断线只截断剩余量:按已发数返回(短写),不是 -1 也不是谎报 2100");
"TC-NETSOCK-470a a mid-send disconnect only truncates: returns bytes already sent (a short write), not -1 and not a false 2100");
CHECK((int)s_net_socks[fd].send_bytes == rc,
"TC-NETSOCK-470b 字节统计与实际交付一致(对端已收到的不能回收)");
"TC-NETSOCK-470b byte statistic matches what was actually delivered (what the peer got cannot be clawed back)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-470c 短写必 [ERR](静默截断曾让 UI 推流被对端误判为全程无字节)");
"TC-NETSOCK-470c a short write must log [ERR] (a silent truncation once made the peer see zero bytes from a UI push)");
reset_all();
}
+51 -51
View File
@@ -23,25 +23,25 @@ static void tc_500_public_bounds(void)
addrlen = (int)sizeof(in);
CHECK(net_send(NET_MAX_SOCKETS, buf, 4, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500a net_send 越界 → BADF");
"TC-NETSOCK-500a net_send out of range -> BADF");
CHECK(net_recv(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500b net_recv 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500b net_recv out of range -> BADF");
CHECK(net_recv_to(NET_MAX_SOCKETS, buf, 4, 10U) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500c net_recv_to 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500c net_recv_to out of range -> BADF");
CHECK(net_close(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500d net_close 越界 → BADF");
"TC-NETSOCK-500d net_close out of range -> BADF");
CHECK(net_drop_conn(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500e net_drop_conn 越界 → BADF");
"TC-NETSOCK-500e net_drop_conn out of range -> BADF");
CHECK(net_accept(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500f net_accept 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500f net_accept out of range -> BADF");
CHECK(net_connect(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500g net_connect 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500g net_connect out of range -> BADF");
CHECK(net_sendto(NET_MAX_SOCKETS, buf, 4, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500h net_sendto 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500h net_sendto out of range -> BADF");
CHECK(net_recvfrom(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500i net_recvfrom 越界 → BADF");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500i net_recvfrom out of range -> BADF");
CHECK(g_ns_put_calls == 0,
"TC-NETSOCK-500j 全部越界形态零入队(入队之后才挡 = 让 netTask 处理一个没主的请求)");
"TC-NETSOCK-500j every out-of-range shape enqueues nothing (blocking after the enqueue hands netTask an ownerless request)");
reset_all();
}
@@ -57,21 +57,21 @@ static void tc_501_all_entries_busy_on_full_queue(void)
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_accept(fd, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501a net_accept 队满 → BUSY");
"TC-NETSOCK-501a net_accept on a full queue -> BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
(void)memset(&in, 0, sizeof(in));
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501b net_connect 队满 → BUSY");
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501b net_connect on a full queue -> BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv_to(fd, (void *)"x", 1, 20U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501c net_recv_to 队满 → BUSY");
"TC-NETSOCK-501c net_recv_to on a full queue -> BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv(fd, (void *)"x", 1, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501d net_recv 队满 → BUSY");
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501d net_recv on a full queue -> BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_send(fd, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501e net_send 队满 → BUSY");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-501f 队满路径零通知(调用方直接拿返回值走人)");
"TC-NETSOCK-501e net_send on a full queue -> BUSY");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-501f the full-queue path sends no notification (the caller leaves with the return value)");
reset_all();
}
@@ -88,12 +88,12 @@ static void tc_502_socket_table_full(void)
break;
}
}
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-502a 8 个槽位全部可分配");
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-502a all 8 slots are allocatable");
CHECK(net_socket(NET_AF_INET, NET_SOCK_STREAM) == -1 &&
net_get_errno() == NET_ERR_NOMEM, "TC-NETSOCK-502b 第 9 个 → NOMEM(不越界写表)");
net_get_errno() == NET_ERR_NOMEM, "TC-NETSOCK-502b the ninth -> NOMEM (no out-of-range table write)");
s_net_socks[3].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_get_sock(3) != NULL && net_listener_armed(3) == 1,
"TC-NETSOCK-502c 表满不影响已在用会话(alloc 失败只回 NOMEM,不动别人的槽位)");
"TC-NETSOCK-502c a full table does not disturb live sessions (alloc failure only returns NOMEM)");
reset_all();
}
@@ -113,10 +113,10 @@ static void tc_503_zero_wait_pickup(void)
rc = net_send(fd, "abcd", 4, 0);
CHECK(rc == 4, "TC-NETSOCK-503a 零等待就拾到通知值 → 直接返回结果");
CHECK(g_ns_block_waits == 0, "TC-NETSOCK-503b 已有通知时绝不进阻塞分支");
CHECK(rc == 4, "TC-NETSOCK-503a the zero-wait peek takes the notify value -> returns the result immediately");
CHECK(g_ns_block_waits == 0, "TC-NETSOCK-503b with a notification already pending it never enters the blocking branch");
CHECK(g_peers.send_reg_calls == 0 && g_chip.write_calls == 0,
"TC-NETSOCK-503c 本次调用没有真的写芯片(拾到的是上一条消息的回执——这就是 实测踩过的形态)");
"TC-NETSOCK-503c this call never touched the chip (the value peeked was the previous receipt, a shape we already hit in practice)");
reset_all();
}
@@ -133,16 +133,16 @@ static void tc_504_locked_entry_branches(void)
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect_locked(fd, NULL, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504a connect_locked addr=NULL → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504a connect_locked with addr=NULL -> INVAL");
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504b connect_locked addrlen 不足 → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504b connect_locked with a short addrlen -> INVAL");
s_net_socks[fd].in_use = 1U;
CHECK(net_send_locked(&s_net_socks[fd], "x", 1, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN, "TC-NETSOCK-504c 未建立的直调 send → NOTCONN");
net_get_errno() == NET_ERR_NOTCONN, "TC-NETSOCK-504c direct send on a not-established block -> NOTCONN");
CHECK(net_recv_locked(&s_net_socks[fd], (void *)"x", 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-504d 直调 recv 无数据非阻塞 → WOULDBLOCK");
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-504d direct recv, no data, non-blocking -> WOULDBLOCK");
CHECK(net_recv_locked(&s_net_socks[fd], NULL, 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504e 直调 recv buf 空 → INVAL");
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504e direct recv with a NULL buf -> INVAL");
reset_all();
}
@@ -162,9 +162,9 @@ static void tc_505_inner_wait_and_last_chunk(void)
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == NET_PENDING_REGISTERED,
"TC-NETSOCK-505a 内层等待(写完一块后等发完)超预算也转挂起,不留 in-place 忙等");
"TC-NETSOCK-505a the inner wait (after writing one chunk) also converts to pending past budget, no in-place busy wait");
CHECK(g_chip.write_calls == 2 && g_peers.last_snd_sent == 2048,
"TC-NETSOCK-505b 登记的进度=已写进芯片的字节数(1024+1024=2048),已发的绝不重发");
"TC-NETSOCK-505b registered progress = bytes written into the chip (1024+1024=2048), delivered bytes are never resent");
reset_all();
/* 末块:全部写完但芯片不回执 → 按完成返回,**不许**登记挂起
@@ -173,9 +173,9 @@ static void tc_505_inner_wait_and_last_chunk(void)
fd = mk_established(NET_SOCK_STREAM);
rc = net_send_locked(&s_net_socks[fd], big, 1024,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-505c 单块恰好写完 → 返回 1024(完成)");
CHECK(rc == 1024, "TC-NETSOCK-505c a single chunk finishing exactly -> returns 1024 (complete)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-505d 末块等待不得登记挂起(sent>=len 的守卫必须挡住)");
"TC-NETSOCK-505d the last-chunk wait must not register pending (the sent>=len guard has to hold)");
reset_all();
}
@@ -195,9 +195,9 @@ static void tc_506_sendto_stall_and_short(void)
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc < 2100 && rc >= 0,
"TC-NETSOCK-506a sendto 停滞只拿到部分:按已发数返回(短写语义与 TCP 一致)");
"TC-NETSOCK-506a sendto stalled returns only what went out (short-write semantics match TCP)");
CHECK(g_ns_error_count >= 2,
"TC-NETSOCK-506b 停滞 [ERR] + 短写 [ERR] 都要留(缺一就退化成静默截断)");
"TC-NETSOCK-506b both the stall [ERR] and the short-write [ERR] must be there (missing one degrades into a silent truncation)");
reset_all();
/* 写完一块后等发完的超时出口 */
@@ -209,7 +209,7 @@ static void tc_506_sendto_stall_and_short(void)
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc >= 1024 && rc < 2100,
"TC-NETSOCK-506c 第一块已交付、第二块等发完超时 → 返回 1024..2099 之间的实际值");
"TC-NETSOCK-506c first chunk delivered and the second chunk wait timed out -> a value between 1024 and 2099");
reset_all();
}
@@ -231,11 +231,11 @@ static void tc_530_stall_timeout_before_spin(void)
rc = net_send_locked(&s_net_socks[fd], buf, (int)sizeof(buf),
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 0,
"TC-NETSOCK-530a 停滞超时按已发数 0 返回(短写形态,不是 -1)");
"TC-NETSOCK-530a the stall timeout returns the bytes sent so far, 0 (a short write, not -1)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-530b 停滞超时优先:不进挂起登记(判定顺序被换掉的话这里会登记)");
"TC-NETSOCK-530b the stall timeout wins over the spin budget: no pending registration (a swapped order would register)");
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-530c errno = TIMEDOUT(供调用方区分停滞与断线)");
"TC-NETSOCK-530c errno = TIMEDOUT (lets the caller tell a stall from a disconnect)");
reset_all();
}
@@ -255,10 +255,10 @@ static void tc_535_inner_timeout_after_chunk(void)
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-535a 已交付的第一块照样交出(1024),剩余按短写处理");
CHECK(rc == 1024, "TC-NETSOCK-535a the already delivered first chunk still counts (1024), the rest is a short write");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-535b 停滞超时收口在登记挂起**之前**(换序就会登记一条永远完不成的挂起)");
CHECK(g_chip.write_calls == 1, "TC-NETSOCK-535c 只写了一次芯片:不得把已交付的块重发");
"TC-NETSOCK-535b the stall timeout closes out BEFORE any pending registration (swapping the order parks a pending that never completes)");
CHECK(g_chip.write_calls == 1, "TC-NETSOCK-535c the chip was written exactly once: a delivered chunk must never be resent");
reset_all();
}
@@ -275,7 +275,7 @@ static void tc_536_recv_to_three_exits(void)
ch395f_fake_push_recv_len((uint8_t)fd, 12U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 100U) == 12,
"TC-NETSOCK-536a 有数据 → 返回实际字节数(走 >=0 支)");
"TC-NETSOCK-536a data available -> returns the actual byte count (the >=0 branch)");
/* <0 且非 TIMEDOUT:分派侧 BADF → 恢复 errno 后返回 -1 */
reset_all();
@@ -284,7 +284,7 @@ static void tc_536_recv_to_three_exits(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(1, buf, (int)sizeof(buf), 100U) == -1 &&
net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-536b 真错误 → -1 且 errno 从通知值恢复(与 -2 严格区分)");
"TC-NETSOCK-536b a real error -> -1 with errno restored from the notify value (strictly distinct from -2)");
/* ==TIMEDOUT:已在 TC-NETSOCK-224 钉过 -2;这里补一条「-2 不改写 errno」 */
reset_all();
@@ -295,7 +295,7 @@ static void tc_536_recv_to_three_exits(void)
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 30U) == -2 &&
net_get_errno() == NET_OK,
"TC-NETSOCK-536c 超时只凭返回码判定,**不改写**错误码槽(本分支刻意不设 errno)");
"TC-NETSOCK-536c timeout is judged by the return code only and does NOT rewrite the errno slot (this branch sets none)");
reset_all();
}
@@ -313,7 +313,7 @@ static void tc_537_event_callback_path(void)
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SEND_OK;
CHECK(net_poll() == 1, "TC-NETSOCK-537 未注册回调也照常计事件(不因空指针跳过分发)");
CHECK(net_poll() == 1, "TC-NETSOCK-537 events are counted even with no callback registered (a null pointer never skips dispatch)");
reset_all();
}
@@ -330,9 +330,9 @@ static void tc_540_negative_results(void)
fd = 3; /* 范围内、未分配 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a close 未分配的 fd → -1 且 errno 从通知值恢复成 BADF(不是笼统 NET_ERR)");
"TC-NETSOCK-540a close on an unallocated fd -> -1 with errno restored to BADF from the notify value, not a generic NET_ERR");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a2 drop_conn 同一条负值恢复支");
"TC-NETSOCK-540a2 drop_conn takes the same negative-value restoration branch");
reset_all();
/* drop_conn 同理:用未分配 fd 走分派侧 BADF 后再由调用方恢复 */
@@ -344,7 +344,7 @@ static void tc_540_negative_results(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540b drop_conn 分派侧 BADF 经通知值回传");
"TC-NETSOCK-540b drop_conn BADF from the dispatch side travels back on the notify value");
/* RECV 的 BADF 支(分派侧 net_get_sock 判空) */
reset_all();
@@ -358,7 +358,7 @@ static void tc_540_negative_results(void)
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540c recv 分派侧 BADF 同样经通知值回传");
"TC-NETSOCK-540c recv BADF from the dispatch side likewise travels back on the notify value");
reset_all();
}
@@ -370,10 +370,10 @@ static void tc_545_local_entries_without_queue(void)
reset_all();
CHECK(net_getsockname(0, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-545a 未初始化也能安全报 BADF(不崩)");
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-545a even uninitialised it safely reports BADF (no crash)");
CHECK(net_getpeername(0, (struct net_sockaddr *)&in, &addrlen) == -1,
"TC-NETSOCK-545b getpeername 同上");
CHECK(net_inet_addr("1.2.3.4") != 0U, "TC-NETSOCK-545c 纯函数不依赖初始化");
"TC-NETSOCK-545b getpeername behaves the same");
CHECK(net_inet_addr("1.2.3.4") != 0U, "TC-NETSOCK-545c pure helpers do not depend on initialisation");
reset_all();
}
+16 -16
View File
@@ -217,14 +217,14 @@ static void tc_load_triage(void)
backend_reset();
g_read_rc = -1;
rc = prot_set_store_load();
CHECK(rc == 1, "TC-PROTSTOR-001 读失败归 1(按无档处理)");
CHECK(rc == 1, "TC-PROTSTOR-001 read failure maps to 1 (treat as no file)");
backend_reset();
len = make_blob(blob, sizeof(blob));
memcpy(g_img, blob, len);
stage_blob(len);
rc = prot_set_store_load();
CHECK(rc == 0, "TC-PROTSTOR-002 有效 blob 装载成功");
CHECK(rc == 0, "TC-PROTSTOR-002 valid blob loads successfully");
/* ver 换代:magic 完好、ver 与当前固件不同 → 预期升级路径,归 2 */
backend_reset();
@@ -233,9 +233,9 @@ static void tc_load_triage(void)
g_img[4] = (uint8_t)(PROT_SET_BLOB_VER + 1u);
stage_blob(len);
rc = prot_set_store_load();
CHECK(rc == 2, "TC-PROTSTOR-003 ver 换代归 2(不是 -1)");
CHECK(rc == 2, "TC-PROTSTOR-003 ver mismatch maps to 2 (not -1)");
CHECK((g_pstore_info_count == 1) && (g_pstore_error_count == 0),
"TC-PROTSTOR-004 换代只打 INFO,不误报 ERROR");
"TC-PROTSTOR-004 version mismatch logs INFO only, no false ERROR");
/* cnt 换代:B8 定值表 32→40 项就是这个形态 */
backend_reset();
@@ -244,7 +244,7 @@ static void tc_load_triage(void)
g_img[6] = (uint8_t)((uint32_t)PS_ID_COUNT + 1u);
stage_blob(len);
rc = prot_set_store_load();
CHECK(rc == 2, "TC-PROTSTOR-005 cnt 换代归 2");
CHECK(rc == 2, "TC-PROTSTOR-005 cnt mismatch maps to 2");
/* magic 坏 = 真损坏 */
backend_reset();
@@ -253,7 +253,7 @@ static void tc_load_triage(void)
g_img[0] = (uint8_t)(g_img[0] ^ 0xFFu);
stage_blob(len);
rc = prot_set_store_load();
CHECK((rc == -1) && (g_pstore_error_count == 0), "TC-PROTSTOR-006 magic 坏归 -1");
CHECK((rc == -1) && (g_pstore_error_count == 0), "TC-PROTSTOR-006 broken magic maps to -1");
/* magic/ver/cnt 全好,但数据被改 → 校验不过 = 损坏 */
backend_reset();
@@ -262,7 +262,7 @@ static void tc_load_triage(void)
g_img[PROT_SET_BLOB_HDR_SZ] = (uint8_t)(g_img[PROT_SET_BLOB_HDR_SZ] ^ 0x5Au);
stage_blob(len);
rc = prot_set_store_load();
CHECK(rc == -1, "TC-PROTSTOR-007 数据被改(校验失败)归 -1");
CHECK(rc == -1, "TC-PROTSTOR-007 data tampered (checksum fail) maps to -1");
/* 截断到不足头部:不得误判为换代 */
backend_reset();
@@ -270,7 +270,7 @@ static void tc_load_triage(void)
memcpy(g_img, blob, len);
stage_blob(PROT_SET_BLOB_HDR_SZ - 1u);
rc = prot_set_store_load();
CHECK(rc == -1, "TC-PROTSTOR-008 长度不足头部归 -1 且不误判换代");
CHECK(rc == -1, "TC-PROTSTOR-008 length below header maps to -1 and is not misjudged as version mismatch");
backend_reset();
len = make_blob(blob, sizeof(blob));
@@ -278,7 +278,7 @@ static void tc_load_triage(void)
stage_blob(len);
rc = prot_set_store_load();
CHECK((rc == 0) && (g_pstore_info_count == 0),
"TC-PROTSTOR-009 成功路径不打 INFO(version change 措辞不许乱出现)");
"TC-PROTSTOR-009 success path logs no INFO (version change wording must not appear)");
}
/* ===================== 后端调用契约 ===================== */
@@ -295,9 +295,9 @@ static void tc_backend_contract(void)
stage_blob(len);
(void)prot_set_store_load();
CHECK(strcmp(g_read_path, "/prot_set.bin") == 0,
"TC-PROTSTOR-010 路径 = /prot_set.bin(8.3 合规)");
"TC-PROTSTOR-010 path = /prot_set.bin (8.3 compliant)");
CHECK(g_read_cap == (size_t)PROT_SET_BLOB_SZ,
"TC-PROTSTOR-011 读取容量 = PROT_SET_BLOB_SZ(静态缓冲,不压栈)");
"TC-PROTSTOR-011 read capacity = PROT_SET_BLOB_SZ (static buffer, not on stack)");
}
/* ===================== save 与脏标志 ===================== */
@@ -311,30 +311,30 @@ static void tc_save_dirty(void)
backend_reset();
dirty_clear();
rc = prot_set_store_save();
CHECK((rc == 0) && (g_write_calls == 0), "TC-PROTSTOR-012 无脏不写盘");
CHECK((rc == 0) && (g_write_calls == 0), "TC-PROTSTOR-012 not dirty -> no flash write");
prot_set_dirty_mark();
g_write_rc = 0;
rc = prot_set_store_save();
CHECK((rc == 0) && (g_write_calls == 1) &&
(strcmp(g_write_path, "/prot_set.bin") == 0) && (g_write_len > 0u),
"TC-PROTSTOR-013 有脏且写成功:路径与长度正确");
"TC-PROTSTOR-013 dirty + write ok: path and length correct");
rc = prot_set_store_save();
CHECK((rc == 0) && (g_write_calls == 1),
"TC-PROTSTOR-014 脏已消费,紧接着再调不再刷 Flash");
"TC-PROTSTOR-014 dirty consumed, immediate second call does not flush flash again");
prot_set_dirty_mark();
g_write_rc = -1;
rc = prot_set_store_save();
CHECK((rc == -1) && (g_write_calls == 2), "TC-PROTSTOR-015 写失败返回 -1");
CHECK((rc == -1) && (g_write_calls == 2), "TC-PROTSTOR-015 write failure returns -1");
/* 关键:失败必须把脏标志还回去,否则这一轮定值永久丢失 */
g_write_rc = 0;
g_write_calls = 0;
rc = prot_set_store_save();
CHECK((rc == 0) && (g_write_calls == 1),
"TC-PROTSTOR-016 上一轮写失败后下一轮仍真的再写(脏已回置)");
"TC-PROTSTOR-016 after a failed round the next round really writes again (dirty re-armed)");
dirty_clear();
}
+13 -13
View File
@@ -73,15 +73,15 @@ static void tc_vectors(void)
sha256_calc("", 0u, d);
CHECK(hex_eq_vector(d, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
"TC-SHA256-001 空串向量");
"TC-SHA256-001 empty-string vector");
sha256_calc("abc", 3u, d);
CHECK(hex_eq_vector(d, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"),
"TC-SHA256-002 \"abc\" 向量");
"TC-SHA256-002 \"abc\" vector");
sha256_calc("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56u, d);
CHECK(hex_eq_vector(d, "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"),
"TC-SHA256-003 56 字节向量(补位跨分组)");
"TC-SHA256-003 56-byte vector (padding crosses block)");
{
sha256_ctx_t ctx;
@@ -93,7 +93,7 @@ static void tc_vectors(void)
sha256_final(&ctx, d);
}
CHECK(hex_eq_vector(d, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"),
"TC-SHA256-004 1e6 × 'a' 向量(流式)");
"TC-SHA256-004 1e6 x 'a' vector (streaming)");
}
/* ===================== TC-SHA256-005:流式等价 ===================== */
@@ -111,7 +111,7 @@ static void tc_stream_equiv(void)
sha256_update(&ctx, &msg[i], 1u);
}
sha256_final(&ctx, d2);
CHECK(memcmp(d1, d2, sizeof(d1)) == 0, "TC-SHA256-005 逐字节喂入与一次性等价");
CHECK(memcmp(d1, d2, sizeof(d1)) == 0, "TC-SHA256-005 byte-by-byte feed equals one-shot");
}
/* ===================== TC-SHA256-006..007:hex_eq ===================== */
@@ -131,11 +131,11 @@ static void tc_hex_eq(void)
}
upper[64] = '\0';
CHECK(sha256_hex_eq(d, lower) == 1, "TC-SHA256-006a 小写 hex 匹配");
CHECK(sha256_hex_eq(d, upper) == 1, "TC-SHA256-006b 大写 hex 匹配(大小写不敏感)");
CHECK(sha256_hex_eq(d, lower) == 1, "TC-SHA256-006a lowercase hex matches");
CHECK(sha256_hex_eq(d, upper) == 1, "TC-SHA256-006b uppercase hex matches (case-insensitive)");
lower[63] = (char)((lower[63] == '0') ? '1' : '0');
CHECK(sha256_hex_eq(d, lower) == 0, "TC-SHA256-007a 内容不等 → 0");
CHECK(sha256_hex_eq(d, lower) == 0, "TC-SHA256-007a content mismatch -> 0");
lower[63] = (char)((lower[63] == '0') ? '1' : '0'); /* 还原 */
{
@@ -144,19 +144,19 @@ static void tc_hex_eq(void)
memcpy(bad, lower, 64);
bad[64] = '\0';
bad[10] = 'g';
CHECK(sha256_hex_eq(d, bad) == 0, "TC-SHA256-007b 非法 hex 字符 → 0");
CHECK(sha256_hex_eq(d, bad) == 0, "TC-SHA256-007b invalid hex char -> 0");
}
memcpy(shortv, lower, 63);
shortv[63] = '\0';
CHECK(sha256_hex_eq(d, shortv) == 0, "TC-SHA256-007c 长度 63 → 0");
CHECK(sha256_hex_eq(d, shortv) == 0, "TC-SHA256-007c length 63 -> 0");
memcpy(longv, lower, 64);
longv[64] = 'a';
longv[65] = '\0';
CHECK(sha256_hex_eq(d, longv) == 0, "TC-SHA256-007d 长度 65 → 0");
CHECK(sha256_hex_eq(d, longv) == 0, "TC-SHA256-007d length 65 -> 0");
CHECK(sha256_hex_eq(d, NULL) == 0, "TC-SHA256-007e NULL → 0");
CHECK(sha256_hex_eq(d, NULL) == 0, "TC-SHA256-007e NULL -> 0");
}
/* ===================== TC-SHA256-008:默认口令哈希钉值 ===================== */
@@ -168,7 +168,7 @@ static void tc_default_hash(void)
/* 与 ftp_cfg.h FTP_CFG_DEF_HASH 同源钉值(两处若漂移本测试立刻红) */
sha256_calc("adminadmin", 10u, d);
CHECK(sha256_hex_eq(d, "d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892") == 1,
"TC-SHA256-008 SHA256(\"adminadmin\") = 出厂默认 pwd_hash");
"TC-SHA256-008 SHA256(\"adminadmin\") = factory default pwd_hash");
}
/* ===================== main ===================== */
+28 -28
View File
@@ -365,7 +365,7 @@ static void tc_post_side(void)
uint32_t i;
int ok;
printf("-- TC-SOESTORE-001..007 post 侧(零 I/O 契约)--\n");
printf("-- TC-SOESTORE-001..007 post side (zero-IO contract) --\n");
/* 001:post 3 条只写环,绝不碰盘、绝不碰卷锁、绝不打日志 */
reset_all();
@@ -379,7 +379,7 @@ static void tc_post_side(void)
}
}
CHECK(ok && (s_lock_waits == 0) && (g_soe_err_count == 0) && (g_soe_info_count == 0),
"TC-SOESTORE-001 post 零 FatFs / 零卷锁 / 零日志(protectTask 栈预算)");
"TC-SOESTORE-001 post does zero FatFs / zero volume lock / zero logging (protectTask stack budget)");
/* 002:墙钟未建立 -> IV + epoch 字段 0(不造假时标) */
reset_all();
@@ -389,7 +389,7 @@ static void tc_post_side(void)
ok = read_rec(0u, &out);
CHECK(ok && (out.flags & SOE_FLG_IV) != 0u && (out.epoch_s == 0u)
&& (out.cp56[2] & 0x80u) != 0u && (out.value == 0xF0u),
"TC-SOESTORE-002 无墙钟 -> flags.IV + epoch=0 + CP56 IV 位(B9 口径)");
"TC-SOESTORE-002 no wall clock -> flags.IV + epoch=0 + CP56 IV bit (B9 rule)");
/* 003:seq 单调;丢弃的那条也占号(真机丢号 = 主机看到的号不连续是**如实**) */
reset_all();
@@ -397,7 +397,7 @@ static void tc_post_side(void)
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, (uint8_t)i, i);
}
ok = (s_seq == 9u) && (s_drop_cnt == 1u);
CHECK(ok, "TC-SOESTORE-003 seq 单调递增,丢弃也占号(号不跳才是假象)");
CHECK(ok, "TC-SOESTORE-003 seq increases monotonically, dropped entries still consume a number");
/* 004:环满丢弃不阻塞、不崩、**不打日志**(留痕归 drain 侧) */
reset_all();
@@ -406,7 +406,7 @@ static void tc_post_side(void)
}
CHECK((s_ring_head == 8u) && (s_drop_cnt == 1u)
&& (g_soe_err_count == 0) && (g_soe_info_count == 0),
"TC-SOESTORE-004 环满丢弃:只计数,post 侧零 DBG(栈尾链 +320B 教训)");
"TC-SOESTORE-004 ring-full drop: count only, zero DBG on post side (lesson: +320B tail chain)");
/* 005:内核运行 + 队列已建 -> 走队列,环不再写 */
reset_all();
@@ -417,7 +417,7 @@ static void tc_post_side(void)
s_ring_tail = 0u;
soe_store_post((uint8_t)SOE_T_ELM_RETURN, 1u, 2u);
CHECK((s_ring_head == 0u) && (s_q_cnt == 1u) && (s_q_msgsz == SOE_REC_SIZE),
"TC-SOESTORE-005 队列已建 + 内核运行 -> 条目进队列、环不再写");
"TC-SOESTORE-005 queue created + kernel running -> entries go to the queue, ring not written");
/* 006:队列满 -> 丢弃计数(与环满同一条可观测出口) */
reset_all();
@@ -429,14 +429,14 @@ static void tc_post_side(void)
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, (uint8_t)i, i);
}
CHECK((s_q_cnt == SOE_QUEUE_MAX) && (s_drop_cnt == 2u),
"TC-SOESTORE-006 队列满 -> 丢弃并计数(soe_store_dropped 同一出口)");
"TC-SOESTORE-006 queue full -> drop and count through the same soe_store_dropped exit");
/* 007:计数饱和不回绕 */
reset_all();
s_drop_cnt = UINT32_MAX;
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, 1u, 1u);
CHECK((s_drop_cnt == UINT32_MAX) && (soe_store_dropped() == UINT32_MAX),
"TC-SOESTORE-007 丢弃计数饱和:UINT32_MAX 不回绕");
"TC-SOESTORE-007 drop counter saturates at UINT32_MAX, never wraps");
}
/* ===================== TC-SOESTORE-010..021:drain 落盘与故障注入 ===================== */
@@ -449,7 +449,7 @@ static void tc_drain_paths(void)
int opens_before;
int writes_before;
printf("-- TC-SOESTORE-010..021 drain 落盘 / 故障注入 --\n");
printf("-- TC-SOESTORE-010..021 drain to disk / fault injection --\n");
/* 010:首次 drain 惰性建队列 + 1 条 64B 落盘且能解析回来 */
reset_all();
@@ -459,7 +459,7 @@ static void tc_drain_paths(void)
&& (ff_fake_size(SOE_PATH) == SOE_REC_SIZE);
CHECK(ok && read_rec(0u, &out) && (out.type == (uint8_t)SOE_T_ELM_ASSERT)
&& (out.elm_id == 3u) && (out.value == 0xABCDu) && (out.seq == 0u),
"TC-SOESTORE-010 惰性建队列 + 64B 定长落盘 + 记录可解回");
"TC-SOESTORE-010 lazy queue creation + fixed 64B record on disk + record decodes back");
/* 011:打开模式必须带 FA_WRITE —— 陷阱一 */
reset_all();
@@ -478,7 +478,7 @@ static void tc_drain_paths(void)
{
FRESULT fr3 = f_write(&probe, payload, SOE_REC_SIZE, &bw);
CHECK(ok && (fr2 == FR_OK) && (fr3 == FR_DENIED) && (bw == 0u),
"TC-SOESTORE-011 模式必为 FA_WRITE|FA_OPEN_APPEND;缺写位 f_write 恒 FR_DENIED");
"TC-SOESTORE-011 mode must be FA_WRITE|FA_OPEN_APPEND; without the write bit f_write is FR_DENIED");
}
(void)f_close(&probe);
}
@@ -499,14 +499,14 @@ static void tc_drain_paths(void)
}
}
}
CHECK(ok, "TC-SOESTORE-012 逐条 open/write/close(无批量合并)+ FIFO 顺序正确");
CHECK(ok, "TC-SOESTORE-012 per-entry open/write/close (no batch merge) + correct FIFO order");
/* 013:open/close 严格配对 */
reset_all();
soe_store_post((uint8_t)SOE_T_SAMPLE_RESUME, SOE_ELM_NONE, 0u);
soe_store_drain();
CHECK(g_ff_obs.calls[FF_FAKE_OP_CLOSE] == g_ff_obs.calls[FF_FAKE_OP_OPEN],
"TC-SOESTORE-013 open/close 配对(脏句柄不跨调用残留)");
"TC-SOESTORE-013 open/close stay paired (no stale handle left across calls)");
/* 014:短写判失败 -> unlink 重建 -> 重试成功 -> 恰好 64 字节 */
reset_all();
@@ -517,7 +517,7 @@ static void tc_drain_paths(void)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] >= 1)
&& (g_ff_obs.last_write_bw == SOE_REC_SIZE)
&& (g_soe_err_count == 0),
"TC-SOESTORE-014 FR_OK+短写被当失败处理(不校验字节数就会静默收下 63B)");
"TC-SOESTORE-014 FR_OK plus short write counts as failure (63B would pass silently without check)");
/* 015:连续**两轮** drain 都失败 -> 留痕只许 1 次(一轮 = 首写 + 自愈重试
* = 2 次 f_write,所以注入 4 次零字节短写才够撑两轮) */
@@ -528,7 +528,7 @@ static void tc_drain_paths(void)
ok = (g_soe_err_count == 1) && (s_ring_tail == 0u) && (s_fault_reported == 1u);
soe_store_drain(); /* 第 2 轮仍失败:不得再留痕 */
CHECK(ok && (g_soe_err_count == 1) && (s_ring_tail == 0u),
"TC-SOESTORE-015 写故障留痕去重(防串口与 selftest 被同一条故障刷屏)");
"TC-SOESTORE-015 write-fault trace is de-duplicated (one fault must not flood UART and selftest)");
/* 015b:成功一次复位去重位,之后再失败才会重新留痕 */
/* 015b:失败(留痕1) -> 恢复(去重位复位) -> 再失败(必须留痕2)。
@@ -543,7 +543,7 @@ static void tc_drain_paths(void)
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, 2u, 2u);
soe_store_drain(); /* 又失败 -> 留痕 2 */
CHECK(ok && (g_soe_err_count == 2) && (s_fault_reported == 1u),
"TC-SOESTORE-015b 恢复后再失败必须重新留痕(漏复位 = 永久静默)");
"TC-SOESTORE-015b a failure after recovery must trace again (missing reset = permanent silence)");
/* 016:卷忙 -> -2:不碰 FatFs、不 release、环不推进 */
reset_all();
@@ -554,7 +554,7 @@ static void tc_drain_paths(void)
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 0)
&& (s_ring_tail == 0u) && (g_soe_err_count == 0),
"TC-SOESTORE-016 卷忙 = 顺延:零 FatFs、零 release、不计故障(喂狗点纪律)");
"TC-SOESTORE-016 volume busy means defer: zero FatFs, zero release, no fault counted (feed duty)");
/* 017:写失败时环不推进 tail -> 下轮重试、既不丢也不重复 */
reset_all();
@@ -566,7 +566,7 @@ static void tc_drain_paths(void)
soe_store_drain(); /* 下轮成功 */
CHECK(ok && (s_ring_tail == 1u) && (ff_fake_size(SOE_PATH) == SOE_REC_SIZE)
&& read_rec(0u, &out) && (out.elm_id == 5u),
"TC-SOESTORE-017 环条目写失败不推进 tail:不丢、下轮也不重复落");
"TC-SOESTORE-017 ring write failure does not advance tail: nothing lost, no duplicate next round");
/* 018:队列条目已出队 -> 卷忙只丢当前一条,剩余留在队列(现状不对称) */
reset_all();
@@ -584,18 +584,18 @@ static void tc_drain_paths(void)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == opens_before)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == writes_before)
&& (s_lock_waits >= 1) && (s_lock_seq_err == 0) && (s_q_get_calls >= 2),
"TC-SOESTORE-018 队列路径卷忙:只丢已出队那一条、本轮零 FatFs(与环路径不对称)");
"TC-SOESTORE-018 queue path with busy volume: only the dequeued entry is lost, zero FatFs now");
/* 019:容量满 -> unlink 重建 + INFO 留痕,新文件恰好 64 字节 */
reset_all();
CHECK(ff_fake_mkfile_size(SOE_PATH, (uint32_t)SOE_STORE_MAX * SOE_REC_SIZE) == 0,
"TC-SOESTORE-019 前置:铺 1024 条(64KB)假记录");
"TC-SOESTORE-019 setup: lay down 1024 fake records (64KB)");
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, 9u, 0x99u);
soe_store_drain();
CHECK((g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (ff_fake_size(SOE_PATH) == SOE_REC_SIZE)
&& (g_soe_info_count == 1) && read_rec(0u, &out) && (out.elm_id == 9u),
"TC-SOESTORE-019 满则删除重建:新文件只剩本次这一条");
"TC-SOESTORE-019 full file is deleted and recreated: the new file holds only this entry");
/* 020:丢弃留痕按差值上报一次,不刷屏 */
reset_all();
@@ -605,7 +605,7 @@ static void tc_drain_paths(void)
soe_store_drain(); /* 差值上报 1 条 */
soe_store_drain(); /* 无新丢弃 -> 不再报 */
CHECK((s_drop_cnt == 3u) && (s_drop_logged == 3u) && (g_soe_err_count == 1),
"TC-SOESTORE-020 丢弃留痕按差值上报一次(defaultTask 侧才是日志安全区)");
"TC-SOESTORE-020 drop trace reports the delta once (only the defaultTask side is log-safe)");
/* 021:队列创建失败 -> 条目滞留环、下轮再试、不崩 */
/* 021:osMessageQueueNew 返回 NULL —— 生产代码里环在队列**之前**排空,
@@ -620,12 +620,12 @@ static void tc_drain_paths(void)
&& (s_ring_tail == 1u) && (ff_fake_size(SOE_PATH) == SOE_REC_SIZE);
soe_store_post((uint8_t)SOE_T_ELM_ASSERT, 5u, 5u); /* 队列没建成 -> 仍进环 */
CHECK(ok && (s_q_cnt == 0u) && (s_ring_head == 2u),
"TC-SOESTORE-021 队列创建失败:环路径照常落盘不丢条目 + 留痕一次");
"TC-SOESTORE-021 queue creation failure: ring path still writes to disk, loses nothing + one trace");
s_q_new_fail = 0;
soe_store_drain();
CHECK((s_queue != NULL) && (s_ring_tail == 2u)
&& (ff_fake_size(SOE_PATH) == 2u * SOE_REC_SIZE) && (g_soe_err_count == 1),
"TC-SOESTORE-021b 堆恢复后队列补建、滞留条目全部落盘");
"TC-SOESTORE-021b once the heap recovers the queue is created and all pending entries reach disk");
}
/* ===================== TC-SOESTORE-022..024:陷阱二 —— 名字/参数类错误码 ===================== */
@@ -635,7 +635,7 @@ static void tc_name_error_not_volume(void)
uint32_t i;
int ok;
printf("-- TC-SOESTORE-022..024 名字/参数类错误码严禁升级成卷操作 --\n");
printf("-- TC-SOESTORE-022..024 name/parameter errors must never escalate into volume operations --\n");
/* 022【D 项已修 2026-10-24:正向断言】f_open 失败(名字/参数类码)
* -> 只留痕返回 -1:**绝不 unlink**、不动 /log 目录、已有记录一条不少。
@@ -692,7 +692,7 @@ static void tc_name_error_not_volume(void)
&& (s_ring_tail == 0u);
ff_fake_inject_write_short(SOE_PATH, 0u, 0); /* 清掉不再需要的注入 */
(void)ok;
CHECK(ok, "TC-SOESTORE-023 长跑反复失败:unlink 有界(<=1/轮),绝不升级为整卷动作");
CHECK(ok, "TC-SOESTORE-023 repeated failures: unlink stays bounded (<=1 per round), never a volume action");
/* 024:假卷严格 8.3 校验的保真自检 —— 生产路径必过、点开头必被拒 */
reset_all();
@@ -715,7 +715,7 @@ static void tc_name_error_not_volume(void)
CHECK((fr_ok == FR_OK)
&& ((fr_dot == FR_INVALID_NAME) || (fr_dot == FR_INVALID_DRIVE))
&& (fr_colon == FR_INVALID_DRIVE),
"TC-SOESTORE-024 假件保真:/log/soe.log 过 8.3,.fschk 与坏盘符被拒");
"TC-SOESTORE-024 fake fidelity: /log/soe.log passes 8.3, .fschk and a bad drive prefix are rejected");
}
/* 生产路径本身合规的正向断言:改名成非 8.3 会让上面那条保真自检变红 */
reset_all();
@@ -723,7 +723,7 @@ static void tc_name_error_not_volume(void)
soe_store_drain();
CHECK((strcmp(g_ff_obs.last_open_path, "/LOG/SOE.LOG") == 0)
&& (g_ff_obs.last_open_rc == (int)FR_OK),
"TC-SOESTORE-024b SOE_PATH 在严格 8.3 卷上确实打得开(改名=静默写失败的防线)");
"TC-SOESTORE-024b SOE_PATH really opens on a strict 8.3 volume (guard against silent write failure)");
}
/* ===================== 入口 ===================== */
+100 -100
View File
@@ -189,19 +189,19 @@ static void tc_000_uninitialized(void) {
sd2506_time_t t;
char buf[32];
printf("TC-SYSCLK-000 未初始化语义\n");
printf("TC-SYSCLK-000 uninitialised semantics\n");
/* 注意:本用例不做 sys_clock_init / sys_clock_set */
CHECK_EQ_U32(sys_clock_get(), UINT32_MAX, "未初始化 get 返回 UINT32_MAX");
CHECK(sys_clock_get_time(&t) == -1, "未初始化 get_time 返回 -1");
CHECK_EQ_U32(sys_clock_get(), UINT32_MAX, "get before init returns UINT32_MAX");
CHECK(sys_clock_get_time(&t) == -1, "get_time before init returns -1");
memset(buf, 0, sizeof(buf));
CHECK(sys_clock_get_str(buf, sizeof(buf)) == buf, "get_str 返回 buf 指针");
CHECK(strcmp(buf, "----/--/-- --:--:--") == 0, "get_str 未初始化占位串");
CHECK(sys_clock_get_str(buf, sizeof(buf)) == buf, "get_str returns the buf pointer");
CHECK(strcmp(buf, "----/--/-- --:--:--") == 0, "get_str placeholder string before init");
}
static void tc_001_init_ok(void) {
int rc;
printf("TC-SYSCLK-001 init 正常(RTC 合法)\n");
printf("TC-SYSCLK-001 init normal (valid RTC)\n");
rtc_set(2026, 8, 24, 0, 0, 0);
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
@@ -209,60 +209,60 @@ static void tc_001_init_ok(void) {
s_tick = 12345u;
rc = sys_clock_init();
CHECK(rc == 0, "RTC 合法 → init 返回 0");
CHECK(rc == 0, "valid RTC -> init returns 0");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 8, 24, 0, 0, 0),
"基准 = 2026-08-24 00:00:00 的 Unix 秒");
CHECK(s_rtc_set_calls == 0, "合法 RTC 不被写回(无校时)");
"base = Unix seconds of 2026-08-24 00:00:00");
CHECK(s_rtc_set_calls == 0, "valid RTC is never written back (no time correction)");
}
static void tc_002_init_leap(void) {
printf("TC-SYSCLK-002 init 闰年换算\n");
printf("TC-SYSCLK-002 init leap-year conversion\n");
/* 2024 是闰年:2/29 存在 */
rtc_set(2024, 2, 29, 12, 0, 0);
s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2024-02-29 合法");
CHECK(sys_clock_init() == 0, "2024-02-29 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 2, 29, 12, 0, 0),
"2024-02-29 12:00:00 换算正确");
"2024-02-29 12:00:00 converts correctly");
/* 2000 是世纪闰年:2/29 存在 */
rtc_set(2000, 2, 29, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2000-02-29 合法(世纪闰年)");
CHECK(sys_clock_init() == 0, "2000-02-29 accepted (century leap year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 2, 29, 0, 0, 0),
"2000-02-29 换算正确");
"2000-02-29 converts correctly");
/* 3 月 1 日:验证闰年 2 月多 1 天被正确累计 */
rtc_set(2024, 3, 1, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2024-03-01 合法");
CHECK(sys_clock_init() == 0, "2024-03-01 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 3, 1, 0, 0, 0),
"2024-03-01 与 2024-02-29 相差恰 1 天");
"2024-03-01 is exactly 1 day after 2024-02-29");
CHECK_EQ_U32(ref_unix(2024, 3, 1, 0, 0, 0) - ref_unix(2024, 2, 29, 0, 0, 0),
86400u, "闰日间隔 = 86400 s");
86400u, "leap-day gap = 86400 s");
}
static void tc_003_init_month_end(void) {
printf("TC-SYSCLK-003 init 月末边界\n");
printf("TC-SYSCLK-003 init end-of-month boundaries\n");
rtc_set(2026, 1, 31, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "1/31 合法");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 1, 31, 0, 0, 0), "1/31 换算");
CHECK(sys_clock_init() == 0, "1/31 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 1, 31, 0, 0, 0), "1/31 conversion");
rtc_set(2026, 2, 28, 23, 59, 59); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2/28 合法(平年)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 2, 28, 23, 59, 59), "2/28 末端换算");
CHECK(sys_clock_init() == 0, "2/28 accepted (common year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 2, 28, 23, 59, 59), "2/28 end-of-day conversion");
rtc_set(2026, 4, 30, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "4/30 合法(30 天月)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 4, 30, 0, 0, 0), "4/30 换算");
CHECK(sys_clock_init() == 0, "4/30 accepted (30-day month)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 4, 30, 0, 0, 0), "4/30 conversion");
/* 2/28 → 3/1 间隔恰 1 天(平年) */
rtc_set(2026, 2, 28, 0, 0, 0); sys_clock_init();
uint32_t a = sys_clock_get();
rtc_set(2026, 3, 1, 0, 0, 0); sys_clock_init();
uint32_t b = sys_clock_get();
CHECK_EQ_U32(b - a, 86400u, "平年 2/28 → 3/1 间隔 = 86400 s");
CHECK_EQ_U32(b - a, 86400u, "common year 2/28 -> 3/1 gap = 86400 s");
}
static void tc_004_init_epoch_bounds(void) {
printf("TC-SYSCLK-004 init 世纪边界(Unix 下限 / 2038 上限)\n");
printf("TC-SYSCLK-004 init century boundaries (Unix lower bound / 2038 upper bound)\n");
/*
* 注意:init 接受的 RTC 年份范围是 2000~2099(越界即判故障并自愈),
* 故"Unix 0(1970-01-01)"**不可能经 init 建立**——1970 会被判越界。
@@ -270,46 +270,46 @@ static void tc_004_init_epoch_bounds(void) {
* 这里验证 init 可接受的最早年月,以及 uint32 上限。
*/
rtc_set(2000, 1, 1, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2000-01-01 合法(可接受最早年)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 1, 1, 0, 0, 0), "2000-01-01 换算");
CHECK(sys_clock_init() == 0, "2000-01-01 accepted (earliest acceptable year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 1, 1, 0, 0, 0), "2000-01-01 conversion");
/* 2038 上限:2038-01-19 03:14:07 = 0x7FFFFFFF */
rtc_set(2038, 1, 19, 3, 14, 7); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2038-01-19 合法(uint32 上限内)");
CHECK_EQ_U32(sys_clock_get(), 0x7FFFFFFFu, "2038-01-19 03:14:07 → 0x7FFFFFFF");
CHECK(sys_clock_init() == 0, "2038-01-19 accepted (within uint32 limit)");
CHECK_EQ_U32(sys_clock_get(), 0x7FFFFFFFu, "2038-01-19 03:14:07 -> 0x7FFFFFFF");
/* Unix 0 经 set 路径显式可达(与 init 的范围限制无关) */
clear_obs();
CHECK(sys_clock_set(0u, NULL) == 0, "set(0) 合法(Unix 零点)");
CHECK_EQ_U32(sys_clock_get(), 0u, "set(0) 后 get == 0(Unix 零点)");
CHECK(sys_clock_set(0u, NULL) == 0, "set(0) accepted (Unix epoch)");
CHECK_EQ_U32(sys_clock_get(), 0u, "after set(0) get == 0 (Unix epoch)");
{
sd2506_time_t t;
CHECK(sys_clock_get_time(&t) == 0
&& t.year == 1970 && t.month == 1 && t.day == 1,
"Unix 0 → 1970-01-01(unix_to_sd2506 反向换算)");
"Unix 0 -> 1970-01-01 (unix_to_sd2506 reverse conversion)");
}
}
static void tc_005_init_rtc_read_fail(void) {
printf("TC-SYSCLK-005 init RTC 读失败 → 降级默认时间并返回 -1\n");
printf("TC-SYSCLK-005 init RTC read failure -> degrade to default time and return -1\n");
s_rtc_read_rc = BSP_ERR_IO; /* 注入读故障 */
s_rtc_write_rc = BSP_OK;
clear_obs();
s_tick = 777u;
int rc = sys_clock_init();
CHECK(rc == -1, "RTC 读失败 → init 返回 -1(报告芯片故障)");
CHECK(rc == -1, "RTC read failure -> init returns -1 (reports the chip fault)");
/* 降级基准 = 编译期默认 RTC_DEFAULT_*(2026-08-24 00:00:00) */
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, RTC_DEFAULT_HOUR,
RTC_DEFAULT_MINUTE, RTC_DEFAULT_SECOND),
"降级基准 = 编译期默认时间(墙钟仍可用)");
CHECK(g_sclk_err_count >= 1, "打了 DBG_ERROR(RTC 异常)");
"degraded base = compile-time default time (wall clock still usable)");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted (RTC abnormal)");
s_rtc_read_rc = BSP_OK; /* 复位注入 */
}
static void tc_006_init_out_of_range_selfheal(void) {
printf("TC-SYSCLK-006 init RTC 越界 → 自愈写回默认时间(返回 0)\n");
printf("TC-SYSCLK-006 init RTC out of range -> self-heal by writing back the default time (returns 0)\n");
/*
* RTC_DEFAULT_INIT_ENABLE=1 且 FORCE=0 时:越界值触发"写回默认时间",
* 写成功且复读合法 → rtc_ok 恢复为 1 → **返回 0**(自愈,非降级)。
@@ -319,23 +319,23 @@ static void tc_006_init_out_of_range_selfheal(void) {
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
clear_obs();
CHECK(sys_clock_init() == 0, "year=1999 → 自愈写回成功 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR, "阴影 RTC 已修正为默认年");
CHECK(sys_clock_init() == 0, "year=1999 -> self-heal write-back ok -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR, "shadow RTC corrected to the default year");
rtc_set(2026, 13, 5, 0, 0, 0); /* month > 12 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "month=13 → 自愈 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
CHECK(sys_clock_init() == 0, "month=13 -> self-heal -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
rtc_set(2026, 5, 0, 0, 0, 0); /* day < 1 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "day=0 → 自愈 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
CHECK(sys_clock_init() == 0, "day=0 -> self-heal -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
}
static void tc_020_init_selfheal_fail_degrades(void) {
printf("TC-SYSCLK-020 init 越界且写回失败 → 降级默认时间并返回 -1\n");
printf("TC-SYSCLK-020 init out of range and write-back fails -> degrade to default time and return -1\n");
/*
* 组合故障:读成功但值越界(触发写回),写回失败 → 复读仍越界 →
* rtc_ok 清 0 → 走降级分支(启用默认值时建基准 + 返回 -1)。
@@ -346,81 +346,81 @@ static void tc_020_init_selfheal_fail_degrades(void) {
clear_obs();
s_tick = 42u;
int rc = sys_clock_init();
CHECK(rc == -1, "越界 + 写回失败 → init 返回 -1");
CHECK(s_rtc_set_calls == 1, "尝试写回一次(失败)");
CHECK(g_sclk_err_count >= 1, "打了 DBG_ERROR");
CHECK(rc == -1, "out of range + write-back failure -> init returns -1");
CHECK(s_rtc_set_calls == 1, "write-back attempted once (failed)");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"降级基准 = 编译期默认时间(墙钟仍可用)");
"degraded base = compile-time default time (wall clock still usable)");
s_rtc_write_rc = BSP_OK;
}
static void tc_007_init_corrects_rtc(void) {
printf("TC-SYSCLK-007 init 越界 → 自动写回默认时间并复验\n");
printf("TC-SYSCLK-007 init out of range -> auto write-back of default time and re-verify\n");
rtc_set(1999, 1, 1, 0, 0, 0); /* 越界,触发校时写回 */
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
clear_obs();
int rc = sys_clock_init();
CHECK(s_rtc_set_calls == 1, "越界 → 写回 RTC 一次");
CHECK(rc == 0, "写回成功 + 复读合法 → 返回 0(自愈成功,非降级)");
CHECK(s_rtc_set_calls == 1, "out of range -> RTC written back once");
CHECK(rc == 0, "write-back ok + re-read valid -> returns 0 (self-heal succeeded, not a degrade)");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR && s_rtc.month == RTC_DEFAULT_MONTH
&& s_rtc.day == RTC_DEFAULT_DAY, "阴影 RTC 已被写入默认时间");
&& s_rtc.day == RTC_DEFAULT_DAY, "shadow RTC was written with the default time");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"基准 = 修正后的默认时间");
"base = the corrected default time");
}
static void tc_008_get_advance(void) {
printf("TC-SYSCLK-008 get 正常推进(含亚秒截断)\n");
printf("TC-SYSCLK-008 get advances normally (sub-second truncation included)\n");
base_at(1000000u, 5000u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "基准时刻 = 基准值");
CHECK_EQ_U32(sys_clock_get(), 1000000u, "at the base instant the value equals the base");
tick_advance(999u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "999ms 未满 1s → 不变(截断)");
CHECK_EQ_U32(sys_clock_get(), 1000000u, "999ms is under 1 s -> unchanged (truncated)");
tick_advance(1u);
CHECK_EQ_U32(sys_clock_get(), 1000001u, "累计 1000ms → +1s");
CHECK_EQ_U32(sys_clock_get(), 1000001u, "accumulated 1000ms -> +1 s");
tick_advance(2500u);
CHECK_EQ_U32(sys_clock_get(), 1000003u, "再 +2500ms → 共 +3s(余 500ms 截断)");
CHECK_EQ_U32(sys_clock_get(), 1000003u, "another +2500ms -> +3 s total (remaining 500 ms truncated)");
}
static void tc_009_get_no_rebase_below_threshold(void) {
printf("TC-SYSCLK-009 未达 30 天阈值不触发再基准化\n");
printf("TC-SYSCLK-009 below the 30-day threshold no re-baselining\n");
base_at(2000000u, 0u);
/* 29 天 < 30 天阈值:线性推进,基准不动 */
tick_advance(29u * 24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), 2000000u + 29u * 24u * 3600u,
"29 天线性推进正确(未再基准化)");
"29 days advance linearly and correctly (no re-baselining)");
}
static void tc_010_get_rebase_at_threshold(void) {
printf("TC-SYSCLK-010 30 天惰性再基准化(窗口清零、值无损、余数结转)\n");
printf("TC-SYSCLK-010 30-day lazy re-baselining (window cleared, value lossless, remainder carried over)\n");
base_at(3000000u, 0u);
/* 推进恰好 30 天 + 1500ms(余数 500ms 应结转到新窗) */
tick_advance(30u * 24u * 3600u * 1000u + 1500u);
uint32_t expect = 3000000u + 30u * 24u * 3600u + 1u; /* 1500ms → +1s,余 500ms */
CHECK_EQ_U32(sys_clock_get(), expect, "再基准化后墙钟值无损(+30 天 1 秒)");
CHECK_EQ_U32(sys_clock_get(), expect, "after re-baselining the wall clock value is lossless (+30 days 1 second)");
/* 再推 500ms:结转的余数正好补足,再多 1 秒 */
tick_advance(500u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u, "亚秒余数已结转(+500ms 补足 1 秒)");
CHECK_EQ_U32(sys_clock_get(), expect + 1u, "sub-second remainder carried over (+500 ms completes 1 second)");
/* 再推一整天:仍线性 */
tick_advance(24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u + 86400u, "再基准化后继续线性推进");
CHECK_EQ_U32(sys_clock_get(), expect + 1u + 86400u, "keeps advancing linearly after re-baselining");
}
static void tc_011_get_tick_wrap(void) {
printf("TC-SYSCLK-011 tick 回绕(无符号差)安全\n");
printf("TC-SYSCLK-011 tick wraparound (unsigned diff) is safe\n");
/* 基准设在接近 2^32 处 */
base_at(4000000u, 0xFFFFF000u);
/* 推进 0x2000 ms(跨越 2^32 回绕):差 = 0x2000 = 8192ms → +8s */
tick_advance(0x2000u);
CHECK_EQ_U32(sys_clock_get(), 4000000u + 8u, "跨 tick 回绕后差值正确");
CHECK_EQ_U32(sys_clock_get(), 4000000u + 8u, "diff is correct across the tick wraparound");
}
static void tc_012_get_long_run_no_drift(void) {
printf("TC-SYSCLK-012 多轮再基准化长期不漂移\n");
printf("TC-SYSCLK-012 many re-baselining rounds do not drift over time\n");
base_at(5000000u, 0u);
/*
* 惰性再基准化的**正确用法**:周期性调用 get() 让差值窗口始终 < 30 天。
@@ -434,44 +434,44 @@ static void tc_012_get_long_run_no_drift(void) {
for (i = 1u; i <= 9u; i++) {
tick_advance(10u * 86400u * 1000u); /* 每次 +10 天(< 30 天阈值) */
CHECK_EQ_U32(sys_clock_get(), base + i * 10u * 86400u,
"10 天步进(含 3 轮再基准)线性无漂移");
"10-day steps (with 3 re-baseline rounds) stay linear with no drift");
}
}
static void tc_013_get_str(void) {
printf("TC-SYSCLK-013 get_str 格式化\n");
printf("TC-SYSCLK-013 get_str formatting\n");
char buf[32];
base_at(ref_unix(2026, 10, 2, 13, 4, 5), 0u);
memset(buf, 0, sizeof(buf));
sys_clock_get_str(buf, sizeof(buf));
CHECK(strcmp(buf, "2026-10-02 13:04:05") == 0, "格式化 YYYY-MM-DD HH:MM:SS");
CHECK(strcmp(buf, "2026-10-02 13:04:05") == 0, "formatted as YYYY-MM-DD HH:MM:SS");
base_at(ref_unix(2000, 1, 1, 0, 0, 0), 0u);
memset(buf, 0, sizeof(buf));
sys_clock_get_str(buf, sizeof(buf));
CHECK(strcmp(buf, "2000-01-01 00:00:00") == 0, "零点补零格式正确");
CHECK(strcmp(buf, "2000-01-01 00:00:00") == 0, "zero-padded midnight format is correct");
}
static void tc_014_get_time_fields(void) {
printf("TC-SYSCLK-014 get_time 日历字段\n");
printf("TC-SYSCLK-014 get_time calendar fields\n");
sd2506_time_t t;
/* 2026-10-02 是周五(已知) */
base_at(ref_unix(2026, 10, 2, 8, 9, 10), 0u);
CHECK(sys_clock_get_time(&t) == 0, "get_time 返回 0");
CHECK(sys_clock_get_time(&t) == 0, "get_time returns 0");
CHECK(t.year == 2026 && t.month == 10 && t.day == 2,
"年月日正确");
CHECK(t.hour == 8 && t.minute == 9 && t.second == 10, "时分秒正确");
CHECK(t.week == 5, "2026-10-02 星期五(week==5,0=周日)");
"year/month/day correct");
CHECK(t.hour == 8 && t.minute == 9 && t.second == 10, "hour/minute/second correct");
CHECK(t.week == 5, "2026-10-02 is Friday (week==5, 0=Sunday)");
}
static void tc_015_get_time_null(void) {
printf("TC-SYSCLK-015 get_time NULL 参数被拒\n");
printf("TC-SYSCLK-015 get_time rejects a NULL argument\n");
base_at(6000000u, 0u);
CHECK(sys_clock_get_time(NULL) == -1, "p_time == NULL → 返回 -1");
CHECK(sys_clock_get_time(NULL) == -1, "p_time == NULL -> returns -1");
}
static void tc_016_set_writes_rtc(void) {
printf("TC-SYSCLK-016 set 更新基准 + 写回 RTC\n");
printf("TC-SYSCLK-016 set updates the base + writes back RTC\n");
sd2506_time_t sd;
memset(&sd, 0, sizeof(sd));
sd.year = 2027; sd.month = 3; sd.day = 15;
@@ -480,48 +480,48 @@ static void tc_016_set_writes_rtc(void) {
s_rtc_write_rc = BSP_OK;
clear_obs();
s_tick = 100u;
CHECK(sys_clock_set(7000000u, &sd) == 0, "set 返回 0");
CHECK_EQ_U32(sys_clock_get(), 7000000u, "基准已更新");
CHECK(s_rtc_set_calls == 1, "sd_time != NULL → 写回 RTC 一次");
CHECK(sys_clock_set(7000000u, &sd) == 0, "set returns 0");
CHECK_EQ_U32(sys_clock_get(), 7000000u, "base has been updated");
CHECK(s_rtc_set_calls == 1, "sd_time != NULL -> RTC written back once");
CHECK(s_rtc.year == 2027 && s_rtc.month == 3 && s_rtc.day == 15
&& s_rtc.hour == 6 && s_rtc.minute == 7 && s_rtc.second == 8,
"RTC 写入内容与传入一致");
"RTC written content equals what was passed in");
/* 写失败:基准仍更新,但打错误日志 */
clear_obs();
s_rtc_write_rc = BSP_ERR_IO;
sys_clock_set(7000001u, &sd);
CHECK_EQ_U32(sys_clock_get(), 7000001u, "RTC 写失败仍更新基准");
CHECK(g_sclk_err_count >= 1, "RTC 写失败打 DBG_ERROR");
CHECK_EQ_U32(sys_clock_get(), 7000001u, "base is still updated when the RTC write fails");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted when the RTC write fails");
s_rtc_write_rc = BSP_OK;
}
static void tc_017_set_no_rtc(void) {
printf("TC-SYSCLK-017 set sd_time=NULL 不碰 RTC\n");
printf("TC-SYSCLK-017 set sd_time=NULL never touches the RTC\n");
clear_obs();
CHECK(sys_clock_set(8000000u, NULL) == 0, "set(NULL) 返回 0");
CHECK(s_rtc_set_calls == 0, "sd_time == NULL → 不写 RTC");
CHECK_EQ_U32(sys_clock_get(), 8000000u, "基准更新");
CHECK(sys_clock_set(8000000u, NULL) == 0, "set(NULL) returns 0");
CHECK(s_rtc_set_calls == 0, "sd_time == NULL -> RTC not written");
CHECK_EQ_U32(sys_clock_get(), 8000000u, "base updated");
}
static void tc_018_set_unix_fields(void) {
printf("TC-SYSCLK-018 set_unix 写回 RTC 且字段正确\n");
printf("TC-SYSCLK-018 set_unix writes back the RTC with correct fields\n");
sd2506_time_t expect_week;
uint32_t u = ref_unix(2026, 10, 2, 15, 30, 45);
clear_obs();
s_rtc_write_rc = BSP_OK;
s_tick = 0u;
CHECK(sys_clock_set_unix(u) == 0, "set_unix 返回 0");
CHECK(s_rtc_set_calls == 1, "写回 RTC 一次");
CHECK(sys_clock_set_unix(u) == 0, "set_unix returns 0");
CHECK(s_rtc_set_calls == 1, "RTC written back once");
CHECK(s_rtc.year == 2026 && s_rtc.month == 10 && s_rtc.day == 2
&& s_rtc.hour == 15 && s_rtc.minute == 30 && s_rtc.second == 45,
"RTC 日历字段与 Unix 秒一致");
CHECK(s_rtc.week == 5, "RTC 星期字段 = 5(2026-10-02 周五)");
"RTC calendar fields match the Unix seconds");
CHECK(s_rtc.week == 5, "RTC weekday field = 5 (2026-10-02 is Friday)");
(void)expect_week;
}
static void tc_019_week_known_dates(void) {
printf("TC-SYSCLK-019 星期(Zeller)已知日期穷举\n");
printf("TC-SYSCLK-019 weekday (Zeller) exhaustive check against known dates\n");
/* 期望值经 Python datetime 独立核对(0=周日);
* 覆盖 1/2 月(Zeller 需上调为上年 13/14 月的分支)与 3~12 月(2026-10-02
* 修正前全部错误),以及世纪闰年、非闰世纪、范围首尾。*/
@@ -554,14 +554,14 @@ static void tc_019_week_known_dates(void) {
cases[i].y, cases[i].m, cases[i].d, cases[i].w);
CHECK(t.week == cases[i].w, nm);
} else {
CHECK(0, "get_time 失败(用例数据问题)");
CHECK(0, "get_time failed (bad test data)");
}
}
}
int main(void) {
printf("=== System Clock 主机侧单元测试 ===\n");
printf("被测:App/util/sys_clock.c(时间换算 + 墙钟基准)\n\n");
printf("=== System Clock host-side unit tests ===\n");
printf("Target: App/util/sys_clock.c (time conversion + wall-clock base)\n\n");
/* tc_000 必须最先(依赖"刚上电"的静态态) */
tc_000_uninitialized();
+77 -77
View File
@@ -538,7 +538,7 @@ static void tc_format(void)
int ok;
uint32_t i;
printf("-- TC-SYSLOG-001..004 记录格式(64B 定长)--\n");
printf("-- TC-SYSLOG-001..004 record format (fixed 64B) --\n");
/* 001 布局 */
reset_all();
@@ -551,14 +551,14 @@ static void tc_format(void)
&& (rec[SYSLOG_EPOCH_OFFS - 1u] == ' ')
&& (memcmp(rec + SYSLOG_DESC_OFFS, "hello", 5u) == 0)
&& (rec[SYSLOG_DESC_OFFS + 5u] == '\0');
CHECK(ok, "TC-SYSLOG-001 [0..18]ts / [19]空格 / [20..29]epoch / [30]空格 / [31..]desc");
CHECK(ok, "TC-SYSLOG-001 [0..18]ts / [19]space / [20..29]epoch / [30]space / [31..]desc");
ok = 1;
for (i = SYSLOG_DESC_OFFS + 6u; i < SYSLOG_REC_SIZE; i++) {
if (rec[i] != 0u) {
ok = 0;
}
}
CHECK(ok, "TC-SYSLOG-001b 尾部 NUL 填充到 64B(定长解析的地基)");
CHECK(ok, "TC-SYSLOG-001b tail padded with NUL to 64B (basis of fixed-length parsing)");
/* 002 描述截断 */
reset_all();
@@ -575,7 +575,7 @@ static void tc_format(void)
}
CHECK(ok && (rec[SYSLOG_DESC_OFFS + SYSLOG_DESC_MAX] == 0u)
&& (rec[SYSLOG_REC_SIZE - 1u] == 0u),
"TC-SYSLOG-002 超长描述截到 28B,不越 64B 边界");
"TC-SYSLOG-002 over-long desc truncated to 28B, never crossing the 64B boundary");
}
/* 003 墙钟未建立 */
@@ -584,7 +584,7 @@ static void tc_format(void)
s_format_rec(SYSLOG_RUN, "x", rec);
CHECK((memcmp(rec, "1970-01-01 08:00:00", SYSLOG_TS_LEN) == 0)
&& (memcmp(rec + SYSLOG_EPOCH_OFFS, "0000000000", SYSLOG_EPOCH_LEN) == 0),
"TC-SYSLOG-003 无墙钟 -> 占位时间戳 + epoch 全 0(不造假时标)");
"TC-SYSLOG-003 no wall clock -> placeholder timestamp + epoch all zero (never fake a time stamp)");
/* 004 单条 vs 批量逐字节相同 */
reset_all();
@@ -594,7 +594,7 @@ static void tc_format(void)
(void)memset(b, 0, sizeof(b));
s_format_rec(SYSLOG_RUN, "same-content", b);
CHECK(memcmp(rec, b, SYSLOG_REC_SIZE) == 0,
"TC-SYSLOG-004 同一输入两次格式化逐字节一致(UI 定长偏移解析的前提)");
"TC-SYSLOG-004 same input formats byte-identical twice (premise of UI fixed-offset parsing)");
reset_all();
s_now = 1700000000u;
ready_only();
@@ -606,7 +606,7 @@ static void tc_format(void)
s_format_rec(SYSLOG_RUN, "ab", batch + SYSLOG_REC_SIZE);
CHECK((memcmp(rec, batch, SYSLOG_REC_SIZE) == 0)
&& (memcmp(rec, batch + SYSLOG_REC_SIZE, SYSLOG_REC_SIZE) == 0),
"TC-SYSLOG-004b 批量路径复用同一 s_format_rec(两条都等于单条)");
"TC-SYSLOG-004b batch path reuses the same s_format_rec (both equal the single-record result)");
}
}
@@ -617,7 +617,7 @@ static void tc_write_single(void)
uint8_t rec[SYSLOG_REC_SIZE];
int ok;
printf("-- TC-SYSLOG-010..019 单条写路径 --\n");
printf("-- TC-SYSLOG-010..019 single-record write path --\n");
/* 010 成功 + 打开模式 */
reset_all();
@@ -630,7 +630,7 @@ static void tc_write_single(void)
&& (g_ff_obs.calls[FF_FAKE_OP_CLOSE] == 1)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0)
&& (g_sys_err_count == 0);
CHECK(ok, "TC-SYSLOG-010 单条落盘成功:模式带写位 + open/write/close 各一次 + 放锁");
CHECK(ok, "TC-SYSLOG-010 single write ok: mode carries the write bit + open/write/close once each + lock released");
/* 011 陷阱一保真对照 */
reset_all();
@@ -644,7 +644,7 @@ static void tc_write_single(void)
fr2 = f_write(&probe, rec, SYSLOG_REC_SIZE, &bw);
(void)f_close(&probe);
CHECK((fr1 == FR_OK) && (fr2 == FR_DENIED) && (bw == 0u),
"TC-SYSLOG-011 只给 FA_OPEN_APPEND -> f_write 恒 FR_DENIED(2026-09-20 真根因)");
"TC-SYSLOG-011 FA_OPEN_APPEND alone -> f_write is always FR_DENIED (real root cause 2026-09-20)");
}
/* 012 卷忙 */
@@ -655,7 +655,7 @@ static void tc_write_single(void)
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 0)
&& (s_lock_releases == 0) && (s_lock_seq_err == 0) && (g_sys_err_count == 0),
"TC-SYSLOG-012 卷忙 = 顺延:不碰盘、不 release、不计故障(喂狗点绝不阻塞)");
"TC-SYSLOG-012 volume busy means defer: no disk touch, no release, no fault counted (feed point never blocks)");
/* 013 短写 -> 自愈 */
reset_all();
@@ -665,7 +665,7 @@ static void tc_write_single(void)
CHECK(ok && (ff_fake_size(P_RUN) == SYSLOG_REC_SIZE)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_ff_obs.last_write_bw == SYSLOG_REC_SIZE),
"TC-SYSLOG-013 FR_OK+短写 63B 被判失败 -> unlink 重建 -> 恰好 64 字节");
"TC-SYSLOG-013 FR_OK + short write 63B treated as failure -> unlink and rebuild -> exactly 64 bytes");
/* 014 容量满 -> 删除重建 */
reset_all();
@@ -675,7 +675,7 @@ static void tc_write_single(void)
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (ff_fake_size(P_RUN) == SYSLOG_REC_SIZE)
&& (syslog_count(SYSLOG_RUN) == 1u),
"TC-SYSLOG-014 满 1024 条 -> 先 unlink 再写,新文件只剩本次一条");
"TC-SYSLOG-014 at 1024 records -> unlink first then write, new file holds only this record");
/* 015 满 + unlink 真失败 */
reset_all();
@@ -685,7 +685,7 @@ static void tc_write_single(void)
ok = (s_append(SYSLOG_RUN, "x") == -1);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-SYSLOG-015 容量轮转时 unlink 失败 -> 返回 -1 且**已释放卷锁**(漏放 = FTP 永久拿不到卷)");
"TC-SYSLOG-015 unlink failure during rotation -> -1 and **the volume lock is released** (leak = FTP never gets the volume)");
/* 016 满 + unlink 返 FR_NO_FILE 无害 */
reset_all();
@@ -698,7 +698,7 @@ static void tc_write_single(void)
ok = (s_append(SYSLOG_RUN, "gone") == 0);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 1) && (g_sys_err_count == 0),
"TC-SYSLOG-016 unlink 返 FR_NO_FILE 被当无害:不进错误分支、不留痕、继续写");
"TC-SYSLOG-016 unlink returning FR_NO_FILE is harmless: no error branch, no trace, write continues");
/* 017 入参守卫 */
reset_all();
@@ -708,7 +708,7 @@ static void tc_write_single(void)
&& (s_append(SYSLOG_RUN, NULL) == -1)
&& (s_append_rec((syslog_id_t)9u, rec) == -1);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-SYSLOG-017 id 越界 / 空 rec / 空 desc -> -1 且不碰卷锁与盘");
"TC-SYSLOG-017 id out of range / empty rec / empty desc -> -1 without touching the lock or the disk");
/* 018 写成功 -> 读侧代数自增 */
reset_all();
@@ -717,7 +717,7 @@ static void tc_write_single(void)
ok = (s_rd_gen[SYSLOG_RUN] == 1u) && (s_rd_gen[SYSLOG_SELFTEST] == 0u);
(void)s_append(SYSLOG_SELFTEST, "b");
CHECK(ok && (s_rd_gen[SYSLOG_SELFTEST] == 1u),
"TC-SYSLOG-018 每次落盘只自增本 id 的代数(另一文件的窗口不被误失效)");
"TC-SYSLOG-018 each write bumps only this id's generation (the other file's window stays valid)");
/* 019 留痕去重 */
reset_all();
@@ -730,7 +730,7 @@ static void tc_write_single(void)
ff_fake_inject_write_short(P_RUN, 0u, 2);
(void)s_append(SYSLOG_RUN, "e3"); /* 再失败 -> 再留痕 */
CHECK(ok && (g_sys_err_count == 2),
"TC-SYSLOG-019 持续故障只报第一次;恢复后再失败必须重新留痕");
"TC-SYSLOG-019 a persistent fault reports once; a failure after recovery must trace again");
}
static void tc_write_many(void)
@@ -739,7 +739,7 @@ static void tc_write_many(void)
uint32_t i;
int ok;
printf("-- TC-SYSLOG-020..024 批量写路径(N-24)--\n");
printf("-- TC-SYSLOG-020..024 batch write path (N-24) --\n");
/* 020 8 条一次 I/O */
reset_all();
@@ -752,7 +752,7 @@ static void tc_write_many(void)
&& (g_ff_obs.last_write_btw == SYSLOG_BATCH_MAX * SYSLOG_REC_SIZE)
&& (g_ff_obs.last_write_bw == SYSLOG_BATCH_MAX * SYSLOG_REC_SIZE)
&& (g_ff_obs.calls[FF_FAKE_OP_CLOSE] == 1),
"TC-SYSLOG-020 一批 8 条 = 一次 open + 一次 512B write + 一次 close(旧实现 8 轮)");
"TC-SYSLOG-020 a batch of 8 = one open + one 512B write + one close (old code needed 8 rounds)");
/* 021 入参守卫 */
reset_all();
@@ -762,7 +762,7 @@ static void tc_write_many(void)
&& (s_append_many(SYSLOG_RUN, NULL, 1u) == -1)
&& (s_append_many((syslog_id_t)SYSLOG_COUNT, batch, 1u) == -1);
CHECK(ok && (s_lock_waits == 0) && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-SYSLOG-021 批量入参守卫在**取卷锁之前**(越界 n 不占用卷)");
"TC-SYSLOG-021 batch argument guards run **before taking the volume lock** (bad n never occupies it)");
/* 022 容量不足 -> -3 且不 unlink */
reset_all();
@@ -773,7 +773,7 @@ static void tc_write_many(void)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0)
&& (ff_fake_size(P_RUN) == ((uint32_t)SYSLOG_RUN_MAX - 3u) * SYSLOG_REC_SIZE),
"TC-SYSLOG-022 装不下一批 -> -3 交回慢路径;批量路径**绝不**自己 unlink 重建");
"TC-SYSLOG-022 batch does not fit -> -3 back to the slow path; the batch path **never** unlinks itself");
/* 023 批量卷忙 / 短写自愈 */
reset_all();
@@ -785,7 +785,7 @@ static void tc_write_many(void)
CHECK(ok && (ff_fake_size(P_RUN) == 4u * SYSLOG_REC_SIZE)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_sys_err_count == 0),
"TC-SYSLOG-023 批量卷忙 -> -2;短写(256 只回 100)-> 自愈重建后整批落全");
"TC-SYSLOG-023 batch with busy volume -> -2; short write (256 asked, 100 back) -> rebuild, whole batch lands");
/* 024 批量持续失败 + 去重 */
reset_all();
@@ -794,7 +794,7 @@ static void tc_write_many(void)
ok = (s_append_many(SYSLOG_RUN, batch, 2u) == -1);
(void)s_append_many(SYSLOG_RUN, batch, 2u);
CHECK(ok && (g_sys_err_count == 1) && (s_rd_gen[SYSLOG_RUN] == 2u),
"TC-SYSLOG-024 批量持续失败留痕去重;代数照样自增(宁多失效一轮也不读脏窗)");
"TC-SYSLOG-024 batch failure trace de-duplicated; generation still bumps (invalidate a window rather than read stale)");
(void)i;
}
@@ -806,7 +806,7 @@ static void tc_drain(void)
uint32_t i;
int ok;
printf("-- TC-SYSLOG-030..041 syslog_drain 排空 --\n");
printf("-- TC-SYSLOG-030..041 syslog_drain --\n");
/* 030 卷未就绪:条目滞留 preboot,drain 直接返回。
* 注意两条容易被读漏的事实:
@@ -821,7 +821,7 @@ static void tc_drain(void)
syslog_drain();
CHECK(ok && (s_q_new_calls == 1) && (s_preboot_cnt == 1u)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0) && (s_soe_drain_calls == 0),
"TC-SYSLOG-030 卷未就绪 -> 队列先建成、条目滞留 preboot、不落盘也不调 SOE");
"TC-SYSLOG-030 volume not ready -> queue created, entries stay in preboot, nothing written and no SOE call");
/* 031 惰性建队列 */
reset_all();
@@ -829,7 +829,7 @@ static void tc_drain(void)
ok = (s_queue == NULL);
syslog_drain();
CHECK(ok && (s_queue != NULL) && (s_q_new_calls == 1) && (s_q_msgsz == sizeof(syslog_entry_t)),
"TC-SYSLOG-031 队列在**首次 drain**(defaultTask 上下文)才创建(内核前建对象 = 启动失败)");
"TC-SYSLOG-031 the queue is created at the **first drain** (defaultTask context); creating objects pre-kernel kills boot");
/* 032 preboot 8 条一批落 */
reset_all();
@@ -837,12 +837,12 @@ static void tc_drain(void)
for (i = 0u; i < SYSLOG_QUEUE_MAX; i++) {
syslog_post(SYSLOG_RUN, "pre");
}
CHECK(s_preboot_cnt == SYSLOG_QUEUE_MAX, "TC-SYSLOG-032a 内核未运行:8 条全进 preboot 数组");
CHECK(s_preboot_cnt == SYSLOG_QUEUE_MAX, "TC-SYSLOG-032a kernel not running: all 8 entries go to the preboot array");
syslog_drain();
ok = (s_preboot_cnt == 0u) && (syslog_count(SYSLOG_RUN) == SYSLOG_BATCH_MAX)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 1)
&& (g_ff_obs.last_write_btw == SYSLOG_BATCH_MAX * SYSLOG_REC_SIZE);
CHECK(ok, "TC-SYSLOG-032b preboot 8 条同文件 -> 一次 512B 合并写(数组下标看而不删)");
CHECK(ok, "TC-SYSLOG-032b 8 preboot entries of one file -> one merged 512B write (index inspected, not deleted)");
/* 033 + 034 两文件混排:换文件那条不消耗 */
reset_all();
@@ -864,7 +864,7 @@ static void tc_drain(void)
CHECK(ok && (s_preboot_cnt == 0u)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 3)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] == 3),
"TC-SYSLOG-033/034 换文件即分批(run 被切成两批 = 共 3 轮 I/O)+ 顺序不乱");
"TC-SYSLOG-033/034 a file change splits the batch (run split in two = 3 I/O rounds) + order preserved");
/* 035【A 项已修 2026-10-24:正向断言】卷忙 -> **整批回投队列**,一条不丢。
* 生产侧修法:syslog_drain 批量路径补 else if (rc == -2) { done = 0u; },
@@ -913,7 +913,7 @@ static void tc_drain(void)
syslog_drain();
CHECK((s_q_cnt == 3u) && (syslog_count(SYSLOG_RUN) == SYSLOG_RUN_MAX - 1u)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0),
"TC-SYSLOG-035b 慢路径遇卷忙 -> 3 条**整批回投**(同一码在两条路径上行为相反)");
"TC-SYSLOG-035b slow path meets busy volume -> all 3 **requeued whole** (same code, opposite behaviour per path)");
/* 035c 一整批(8 条)在卷忙时全部回投,不是只保住一条 */
reset_all();
@@ -966,7 +966,7 @@ static void tc_drain(void)
syslog_drain();
CHECK(ok && (s_q_cnt == SYSLOG_QUEUE_MAX) && (s_preboot_cnt == 0u)
&& (g_sys_err_count == 1 + 5 + 1),
"TC-SYSLOG-036 回投装不下 -> 5 次 hold-over drop + drop/wfail 各 1 条留痕");
"TC-SYSLOG-036 requeue does not fit -> 5 hold-over drops + one drop trace and one wfail trace");
/* 037 -3 退回慢路径:容量轮转只发生一次 */
reset_all();
@@ -982,7 +982,7 @@ static void tc_drain(void)
&& (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_ff_obs.calls[FF_FAKE_OP_WRITE] >= 4);
CHECK(ok && (s_q_cnt == 0u),
"TC-SYSLOG-037 -3 -> 逐条慢路径:装满剩位 -> 满 -> unlink 重建 -> 继续,全程 unlink 恰一次");
"TC-SYSLOG-037 -3 -> per-record slow path: fill the rest -> full -> unlink rebuild -> continue, unlink exactly once");
/* 038 慢路径中途写失败 -> 该条丢 + 尾部回投 */
reset_all();
@@ -999,7 +999,7 @@ static void tc_drain(void)
ok = (s_q_cnt == 2u) && (ff_fake_size(P_RUN) == 0u) && (g_sys_err_count >= 2);
syslog_drain(); /* 之后成功 */
CHECK(ok && (s_q_cnt == 0u) && (syslog_count(SYSLOG_RUN) == 2u),
"TC-SYSLOG-038 慢路径写失败:只丢**当前那一条**,尾部 2 条下轮补齐");
"TC-SYSLOG-038 slow-path write failure: only **that one record** is lost, the last 2 land next round");
/* 039【B 项已修 2026-10-24:正向断言】rc == -1:整批回投 + 留痕文案如实
* (旧版这里打 "drop %lu entries" 但一条没丢,文案与行为相反)。
@@ -1038,7 +1038,7 @@ static void tc_drain(void)
ok = (s_soe_drain_calls == 1);
syslog_drain();
CHECK(ok && (s_soe_drain_calls == 2),
"TC-SYSLOG-040 drain 尾部必调 soe_store_drain(同写者、同卷就绪门)");
"TC-SYSLOG-040 drain must call soe_store_drain (same writer, same volume-ready gate)");
/* 041 post 分流与守卫 */
reset_all();
@@ -1046,7 +1046,7 @@ static void tc_drain(void)
syslog_post((syslog_id_t)SYSLOG_COUNT, "bad-id");
syslog_post(SYSLOG_RUN, NULL);
CHECK((s_preboot_cnt == 0u) && (s_q_cnt == 0u),
"TC-SYSLOG-041a id 越界 / NULL 描述 -> 静默丢弃(不占待发位)");
"TC-SYSLOG-041a id out of range / NULL desc -> silently dropped (does not occupy a pending slot)");
reset_all();
ready_only();
for (i = 0u; i < SYSLOG_QUEUE_MAX + 2u; i++) {
@@ -1087,7 +1087,7 @@ static void tc_drain(void)
syslog_post(SYSLOG_RUN, "q2");
syslog_post(SYSLOG_RUN, "q3");
CHECK((s_q_cnt == 2u) && (g_sys_err_count == 1),
"TC-SYSLOG-041c 内核运行 -> 走队列;队列满丢弃 + 留痕");
"TC-SYSLOG-041c kernel running -> queue path; queue full drops with a trace");
}
/* ===================== D 读侧 ===================== */
@@ -1100,17 +1100,17 @@ static void tc_read(void)
uint32_t i;
int ok;
printf("-- TC-SYSLOG-050..058 读侧(N-24 滑动窗口)--\n");
printf("-- TC-SYSLOG-050..058 read side (N-24 sliding window) --\n");
/* 050 count */
reset_all();
ready_only();
CHECK(syslog_count(SYSLOG_RUN) == 0u, "TC-SYSLOG-050a 文件缺失 -> count 0");
CHECK(syslog_count(SYSLOG_RUN) == 0u, "TC-SYSLOG-050a missing file -> count 0");
for (i = 0u; i < 3u; i++) {
(void)s_append(SYSLOG_RUN, "c");
}
ok = (syslog_count(SYSLOG_RUN) == 3u) && (syslog_count((syslog_id_t)7u) == 0u);
CHECK(ok, "TC-SYSLOG-050b count = filesize/64(O(1),不读盘内容);id 越界 -> 0");
CHECK(ok, "TC-SYSLOG-050b count = filesize/64 (O(1), no content read); id out of range -> 0");
/* 051 窗口命中零 I/O */
reset_all();
@@ -1131,7 +1131,7 @@ static void tc_read(void)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == opens_after) /* 命中 -> 零 I/O */
&& (g_ff_obs.calls[FF_FAKE_OP_READ] == reads_base + 1);
}
CHECK(ok, "TC-SYSLOG-051 窗口命中 -> 第二次读**零 FatFs 调用**(UI 翻页不再逐条开合)");
CHECK(ok, "TC-SYSLOG-051 window hit -> the second read makes **zero FatFs calls** (UI paging no longer opens per record)");
/* 052 写者代数失效 */
reset_all();
@@ -1142,7 +1142,7 @@ static void tc_read(void)
(void)s_append(SYSLOG_RUN, "new"); /* 代数 +1 -> 窗口必须重填 */
ok = ok && (syslog_get_record(SYSLOG_RUN, 1u, ts, sizeof(ts), desc, sizeof(desc)) == 0)
&& (strcmp(desc, "new") == 0);
CHECK(ok, "TC-SYSLOG-052 写者一落地代数就变 -> 窗口重填,绝不返回陈旧内容");
CHECK(ok, "TC-SYSLOG-052 once the writer lands the generation changes -> window refills, never returns stale content");
/* 053 跨窗口 */
reset_all();
@@ -1158,7 +1158,7 @@ static void tc_read(void)
&& (g_ff_obs.last_lseek_ofs == (FSIZE_t)(SYSLOG_RD_WINDOW * SYSLOG_REC_SIZE))
&& (g_ff_obs.last_read_btr == SYSLOG_REC_SIZE);
}
CHECK(ok, "TC-SYSLOG-053 idx=8 跨窗 -> 一次 open + lseek(8*64) + read(1 条)(窗口按 8 对齐)");
CHECK(ok, "TC-SYSLOG-053 idx=8 crosses the window -> one open + lseek(8*64) + read(1 record) (window aligned to 8)");
/* 054 越界 */
reset_all();
@@ -1169,7 +1169,7 @@ static void tc_read(void)
reset_all();
ready_only();
ok = ok && (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == -1);
CHECK(ok, "TC-SYSLOG-054 越界 idx / 空文件 -> -1(等锁前后各校验一次 count)");
CHECK(ok, "TC-SYSLOG-054 out-of-range idx / empty file -> -1 (count checked before and after taking the lock)");
/* 055 读时卷忙 */
reset_all();
@@ -1181,7 +1181,7 @@ static void tc_read(void)
lock_busy_next(1);
ok = (syslog_get_record(SYSLOG_SELFTEST, 0u, ts, sizeof(ts), desc, sizeof(desc)) == -1)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] >= 1) && (s_lock_seq_err == 0);
CHECK(ok, "TC-SYSLOG-055 读时卷忙 -> -1(读窗口 try 语义:uiTask 也绝不阻塞等锁,下轮刷新重读)");
CHECK(ok, "TC-SYSLOG-055 busy volume while reading -> -1 (try semantics: uiTask never blocks for the lock, retry next refresh)");
/* 056 短读作废窗口 */
reset_all();
@@ -1193,7 +1193,7 @@ static void tc_read(void)
ff_fake_inject_read_short(P_RUN, 1u, 1);
ok = (s_read_raw(SYSLOG_RUN, 0u, (uint8_t *)desc) == -1)
&& (s_rd_win_cnt == 0u) && (s_rd_win_id == -1);
CHECK(ok, "TC-SYSLOG-056 短读 -> 窗口作废(半窗当有效命中 = UI 显示半条脏记录)");
CHECK(ok, "TC-SYSLOG-056 short read -> window invalidated (a half window treated as valid = UI shows half a stale record)");
/* 057 get_record 守卫与截断 */
reset_all();
@@ -1213,7 +1213,7 @@ static void tc_read(void)
* 整 28 字符而不是截断结果 —— 即"传刚够的缓冲会被拒、传更小的也被拒,
* 从来没有截断"。登记为死分支,见文件头"本套照出的现状"。 */
&& (strlen(desc) == (size_t)SYSLOG_DESC_MAX);
CHECK(ok, "TC-SYSLOG-057 守卫:ts_len<=19 或 desc_len<=28 一律 -1;29 才读得到整 28 字符");
CHECK(ok, "TC-SYSLOG-057 guards: ts_len<=19 or desc_len<=28 always -1; only 29 reads the full 28 characters");
/* C 项整改(2026-10-24):容量不足一律拒绝,且**不得**碰调用方缓冲——
* 上面每次被拒的调用都用了同一对缓冲,哨兵还在就说明没写进去 */
reset_all();
@@ -1243,7 +1243,7 @@ static void tc_read(void)
s_now = 1700000123u;
(void)s_append(SYSLOG_RUN, "e");
ok = ok && (syslog_last_epoch(SYSLOG_RUN, &epoch) == 0) && (epoch == 1700000123u);
CHECK(ok, "TC-SYSLOG-058 last_epoch:无记录 -1;有记录解析 10 位 ASCII");
CHECK(ok, "TC-SYSLOG-058 last_epoch: no record -> -1; with a record parse the 10 ASCII digits");
}
/* ===================== E mounted / 上电重启分类 ===================== */
@@ -1253,7 +1253,7 @@ static void tc_mounted(void)
uint8_t rec[SYSLOG_REC_SIZE];
int ok;
printf("-- TC-SYSLOG-060..064 挂载后处理(挂载幂等 + 上电/重启分类)--\n");
printf("-- TC-SYSLOG-060..064 post-mount handling (idempotent mount + power/reboot classification) --\n");
/* 060 /log 不在 -> 建目录 + 写上电记录;第二次调用完全静默 */
reset_all();
@@ -1264,7 +1264,7 @@ static void tc_mounted(void)
syslog_mcu_mounted();
ok = ok && (s_fs_ready == 1u) && (syslog_count(SYSLOG_RUN) == 1u);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_MKDIR] == 0) && (g_sys_err_count == 0),
"TC-SYSLOG-060a /log 已存在 -> 只写上电/重启记录一条");
"TC-SYSLOG-060a /log exists -> write only the one power/reboot record");
/* 060b 幂等:再来一次既不建目录也不写第二条 */
reset_all();
s_fs_ready = 0u;
@@ -1275,7 +1275,7 @@ static void tc_mounted(void)
syslog_mcu_mounted(); /* 第二次:应完全静默 */
CHECK((s_fs_ready == 1u) && (syslog_count(SYSLOG_RUN) == cnt1)
&& (g_ff_obs.calls[FF_FAKE_OP_MKDIR] == mkdirs1),
"TC-SYSLOG-060b 幂等再调用:不再建目录、不再写上电记录(2s 复查不刷屏)");
"TC-SYSLOG-060b idempotent re-entry: no mkdir, no second power-on record (the 2s recheck must not flood)");
}
(void)rec;
@@ -1291,7 +1291,7 @@ static void tc_mounted(void)
ok = ok && (g_sys_err_count == 1) && (s_lock_releases == 2) && (s_lock_seq_err == 0);
syslog_mcu_mounted(); /* 注入耗尽 -> 成功 */
CHECK(ok && (s_fs_ready == 1u) && (s_mount_fail_reported == 0u),
"TC-SYSLOG-061 mkdir 失败:不就绪、留痕一次(防 2s 刷屏)、第三轮自愈并复位标志");
"TC-SYSLOG-061 mkdir failure: not ready, trace once (no 2s flooding), third round self-heals and resets the flag");
/* 062 卷忙:本轮顺延,不置就绪、不碰盘 */
reset_all();
@@ -1300,7 +1300,7 @@ static void tc_mounted(void)
syslog_mcu_mounted();
CHECK((s_fs_ready == 0u) && (g_ff_obs.calls[FF_FAKE_OP_STAT] == 0)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 0) && (s_lock_releases == 0),
"TC-SYSLOG-062 卷忙 -> 直接返回(try 失败不 release,下轮 2s 后再试)");
"TC-SYSLOG-062 busy volume -> return at once (a failed try must not release; retry after the next 2s)");
/* 063 上电 / 重启分类:阈值 SYSLOG_REBOOT_WINDOW_S 两侧 + 墙钟无效 */
reset_all();
@@ -1320,7 +1320,7 @@ static void tc_mounted(void)
syslog_mcu_mounted();
ok = raw_at(P_RUN, 1u, rec) && (memcmp(rec + SYSLOG_DESC_OFFS, "reboot", 6u) == 0)
&& (rec[SYSLOG_DESC_OFFS + 6u] == '\0');
CHECK(ok, "TC-SYSLOG-063a 末条 epoch 距 now 300s(<600) -> 描述落 \"reboot\"");
CHECK(ok, "TC-SYSLOG-063a last epoch 300s(<600) before now -> desc is \"reboot\"");
reset_all();
s_fs_ready = 0u;
@@ -1335,7 +1335,7 @@ static void tc_mounted(void)
}
syslog_mcu_mounted();
ok = raw_at(P_RUN, 1u, rec) && (memcmp(rec + SYSLOG_DESC_OFFS, "power on", 8u) == 0);
CHECK(ok, "TC-SYSLOG-063b 间隔 >=600s -> \"power on\"(掉电超过 10 分钟算重新上电)");
CHECK(ok, "TC-SYSLOG-063b gap >=600s -> \"power on\" (over ten minutes off counts as a fresh power-up)");
reset_all();
s_fs_ready = 0u;
@@ -1348,14 +1348,14 @@ static void tc_mounted(void)
}
syslog_mcu_mounted();
ok = raw_at(P_RUN, 1u, rec) && (memcmp(rec + SYSLOG_DESC_OFFS, "power on", 8u) == 0);
CHECK(ok, "TC-SYSLOG-063c 墙钟无效(UINT32_MAX)-> 保守判 \"power on\",不拿假差值判重启");
CHECK(ok, "TC-SYSLOG-063c wall clock invalid (UINT32_MAX) -> conservatively \"power on\", never judge a reboot from a fake delta");
/* 064 幂等靠 stat,不靠状态位:/log 已在就不再 mkdir */
reset_all();
s_fs_ready = 0u;
syslog_mcu_mounted();
ok = (g_ff_obs.calls[FF_FAKE_OP_MKDIR] == 0) && (g_ff_obs.calls[FF_FAKE_OP_STAT] >= 1);
CHECK(ok, "TC-SYSLOG-064 /log 已存在时靠 f_stat 短路,f_mkdir 一次都不调");
CHECK(ok, "TC-SYSLOG-064 with /log present f_stat short-circuits, f_mkdir is never called");
}
/* ===================== F 取证通道 sys_log_evidence ===================== */
@@ -1365,7 +1365,7 @@ static void tc_evidence(void)
uint8_t raw[8u * SYSLOG_REC_SIZE];
int ok;
printf("-- TC-SYSLOG-070..074 sys_log_evidence(wdev.log 取证通道)--\n");
printf("-- TC-SYSLOG-070..074 sys_log_evidence (wdev.log forensics channel) --\n");
/* 070 变长行 + 第二条 run.log */
reset_all();
@@ -1379,14 +1379,14 @@ static void tc_evidence(void)
&& raw_at(P_RUN, 0u, raw)
&& (memcmp(raw + SYSLOG_DESC_OFFS, "wd stall declared", 17u) == 0);
}
CHECK(ok, "TC-SYSLOG-070 wdev.log 写变长行(带 CRLF),并补一条 run.log 独立证据");
CHECK(ok, "TC-SYSLOG-070 wdev.log writes a variable-length line (with CRLF) plus a separate run.log evidence record");
/* 071 卷锁顺序:第二条写必须在释放之后(非递归锁,反了 = 自死锁) */
reset_all();
(void)sys_log_evidence("order probe");
CHECK((s_lock_waits == 2) && (s_lock_releases == 2) && (s_lock_seq_err == 0)
&& (s_lock_held == 0),
"TC-SYSLOG-071 取锁两次各放一次(在持锁状态下调 s_append 会永远拿不到锁)");
"TC-SYSLOG-071 the lock is taken twice and released twice (calling s_append while holding it would deadlock)");
/* 072 >8192B 护栏 */
reset_all();
@@ -1394,7 +1394,7 @@ static void tc_evidence(void)
ok = (sys_log_evidence("after rotate") == 0);
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (ff_fake_size(P_WDEV) > 0u) && (ff_fake_size(P_WDEV) < 200u),
"TC-SYSLOG-072 超过 8KB -> 先 unlink 再写(长跑不被无界撑爆)");
"TC-SYSLOG-072 beyond 8KB -> unlink first then write (a long run must not grow without bound)");
/* 073 卷忙 */
reset_all();
@@ -1402,14 +1402,14 @@ static void tc_evidence(void)
ok = (sys_log_evidence("busy probe") == -1);
CHECK(ok && (ff_fake_exists(P_WDEV) == 0) && (syslog_count(SYSLOG_RUN) == 0u)
&& (s_lock_releases == 0) && (s_lock_seq_err == 0),
"TC-SYSLOG-073 卷忙 -> -1 且**两条写都没发生**(喂狗点绝不阻塞等锁)");
"TC-SYSLOG-073 busy volume -> -1 and **neither write happened** (feed point never blocks for the lock)");
/* 074 wdev 写失败:返回 -1,但第二条仍写("独立证据"语义) */
reset_all();
ff_fake_inject_rc(P_WDEV, FF_FAKE_OP_OPEN, FR_DISK_ERR, 1);
ok = (sys_log_evidence("second write still") == -1);
CHECK(ok && (syslog_count(SYSLOG_RUN) == 1u) && (g_sys_err_count == 0),
"TC-SYSLOG-074 wdev 写失败不阻断第二条 run.log(多一条独立证据的原始意图)");
"TC-SYSLOG-074 a wdev write failure does not block the second run.log record (the intent of extra evidence)");
}
/* ===================== G 陷阱二:名字/参数类错误码严禁升级成卷操作 ===================== */
@@ -1425,7 +1425,7 @@ static void tc_name_rules(void)
int ok;
uint32_t i;
printf("-- TC-SYSLOG-080..082 陷阱二分档与「绝不升级」 --\n");
printf("-- TC-SYSLOG-080..082 name class split and never escalate --\n");
/* 080 短名违规**分档**:每条都落到 FR_INVALID_NAME;':' 落到 FR_INVALID_DRIVE */
reset_all();
@@ -1442,7 +1442,7 @@ static void tc_name_rules(void)
CHECK((fr_body == FR_INVALID_NAME) && (fr_ext == FR_INVALID_NAME)
&& (fr_bad == FR_INVALID_NAME) && (fr_dot == FR_INVALID_NAME)
&& (fr_colon == FR_INVALID_DRIVE),
"TC-SYSLOG-080 分档:体>8 / 扩展>3 / 非法字符 / 点开头 -> FR_INVALID_NAME;':' -> FR_INVALID_DRIVE");
"TC-SYSLOG-080 classes: body>8 / ext>3 / bad char / leading dot -> FR_INVALID_NAME; ':' -> FR_INVALID_DRIVE");
/* 081 生产实际用的每个名字都必须过严格 8.3(改名 = 静默写失败的第一道防线) */
reset_all();
@@ -1452,7 +1452,7 @@ static void tc_name_rules(void)
ok = ok && (f_open(&probe, P_WDEV, FA_WRITE | FA_OPEN_APPEND) == FR_OK)
&& (f_close(&probe) == FR_OK);
ok = ok && (f_stat("/log", NULL) == FR_OK);
CHECK(ok, "TC-SYSLOG-081 生产路径 /log、run.log、selftest.log、wdev.log 全部 8.3 合规");
CHECK(ok, "TC-SYSLOG-081 production paths /log, run.log, selftest.log, wdev.log are all valid 8.3 names");
/* 082 名字类错误码不得升级成卷操作:反复失败也只动自己的文件 */
reset_all();
@@ -1485,7 +1485,7 @@ static void tc_extra_branches(void)
uint32_t i;
int ok;
printf("-- TC-SYSLOG-090..100 补齐分支 --\n");
printf("-- TC-SYSLOG-090..100 remaining branch coverage --\n");
/* 090 批量路径的 L1 自愈:open 失败一次 -> unlink + 重开 -> 整批照常落盘
* (这条钉的是 s_append_many 里那段"和单条同一份自愈表";漏了它,
@@ -1502,7 +1502,7 @@ static void tc_extra_branches(void)
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == 1)
&& (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 3) /* 单条 1 + 批量首开 1 + 自愈重开 1 */
&& (ff_fake_size(P_RUN) == 2u * SYSLOG_REC_SIZE) && (g_sys_err_count == 0),
"TC-SYSLOG-090 批量 open 失败一次 -> unlink 重开 -> 整批 2 条落全、不留痕");
"TC-SYSLOG-090 one batch open failure -> unlink and reopen -> whole batch of 2 lands, no trace");
/* 091 批量 open 永久失败 -> -1(走的是"打开失败"那条 else,不是写失败) */
reset_all();
@@ -1514,7 +1514,7 @@ static void tc_extra_branches(void)
}
CHECK(ok && (ff_fake_exists(P_RUN) == 0) && (g_sys_err_count == 1)
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
"TC-SYSLOG-091 批量打开持续失败 -> -1 + 留痕一次 + 已释放卷锁");
"TC-SYSLOG-091 batch open keeps failing -> -1 + one trace + the volume lock released");
/* 092 取锁后二次校验:等锁期间写者可能已把文件删掉(容量轮转/取证重建)
* -> 释放锁再返回 -1,绝不能拿锁前的旧 total 去 lseek */
@@ -1527,7 +1527,7 @@ static void tc_extra_branches(void)
ok = ok && (s_read_raw(SYSLOG_RUN, 0u, raw) == -1)
&& (s_lock_releases == 2) && (s_lock_seq_err == 0);
CHECK(ok && (s_rd_win_cnt == 0u),
"TC-SYSLOG-092 锁后二次 count 失败 -> 放锁返回 -1(漏放 = FTP 永久等不到卷)");
"TC-SYSLOG-092 second count after the lock fails -> release the lock and return -1 (leak = FTP waits forever)");
/* 093 读时 f_open 失败 -> 放锁返回 -1(另一条提前退出路径) */
reset_all();
@@ -1540,7 +1540,7 @@ static void tc_extra_branches(void)
&& (s_lock_releases == 2) && (s_lock_seq_err == 0);
/* 第二次读(注入耗尽)应成功,证明窗口没被留在半初始化状态 */
ok = ok && (s_read_raw(SYSLOG_RUN, 0u, raw) == 0) && (s_rd_win_cnt == 1u);
CHECK(ok, "TC-SYSLOG-093 读打开失败 -> 放锁 -1;下轮恢复后窗口正常重填");
CHECK(ok, "TC-SYSLOG-093 read open failure -> release lock and -1; next round refills the window after recovery");
/* 094 墙钟未建立时 evidence 用占位时标(不得写半截或未初始化缓冲) */
reset_all();
@@ -1550,7 +1550,7 @@ static void tc_extra_branches(void)
(void)ff_fake_fetch(P_WDEV, raw, 0u, SYSLOG_TS_LEN);
ok = (memcmp(raw, "1970-01-01 08:00:00", SYSLOG_TS_LEN) == 0);
}
CHECK(ok, "TC-SYSLOG-094 无墙钟 -> wdev.log 落占位时标(取证行也必须可解析)");
CHECK(ok, "TC-SYSLOG-094 no wall clock -> wdev.log gets a placeholder timestamp (forensic lines must stay parseable)");
/* 095 wdev 短写:FR_OK + 字节数不足 -> -1,但第二条 run.log 照写 */
reset_all();
@@ -1558,7 +1558,7 @@ static void tc_extra_branches(void)
ok = (sys_log_evidence("short line") == -1);
CHECK(ok && (ff_fake_size(P_WDEV) == 3u) && (syslog_count(SYSLOG_RUN) == 1u)
&& (g_sys_err_count == 0),
"TC-SYSLOG-095 wdev 短写判失败返回 -1,第二条独立证据仍落 run.log");
"TC-SYSLOG-095 a wdev short write fails with -1 while the second run.log evidence record still lands");
/* 096 队列创建失败:条目滞留 preboot、留痕一次、下轮建成后排空 */
reset_all();
@@ -1573,7 +1573,7 @@ static void tc_extra_branches(void)
syslog_drain();
CHECK(ok && (s_queue != NULL) && (syslog_count(SYSLOG_RUN) == 1u)
&& (s_preboot_cnt == 0u) && (s_soe_drain_calls == 1),
"TC-SYSLOG-096 队列没建成 -> 一条都不排(不静默丢),堆恢复后排空并接上 SOE");
"TC-SYSLOG-096 queue not created -> nothing is drained (no silent loss); after the heap recovers it empties into SOE");
/* 097 换文件那条来自队列 -> 原样回投队列(不是 preboot 下标回退)
* 队列里 RUN/SELFTEST/RUN 交替:三条必须全落且各自文件内顺序不乱 */
@@ -1594,7 +1594,7 @@ static void tc_extra_branches(void)
&& (s_q_cnt == 0u);
}
CHECK(ok && (g_ff_obs.calls[FF_FAKE_OP_OPEN] == 3),
"TC-SYSLOG-097 队列侧换文件回投:三条全落、各文件内顺序不乱(回投不是丢弃)");
"TC-SYSLOG-097 queue-side file change requeues: all three land, order inside each file kept (requeue is not a drop)");
/* 098【C 项已修 2026-10-24】原来的 "if (dl >= desc_len) dl = desc_len - 1"
* 是永不可达的死分支(守卫已挡掉 desc_len <= SYSLOG_DESC_MAX,而 dl 恒
* <= SYSLOG_DESC_MAX),已从生产码删除。本条改为正向钉住"读侧不再截断":
@@ -1617,7 +1617,7 @@ static void tc_extra_branches(void)
&& (syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) == 0)
&& (sys_log_evidence(NULL) == -1);
CHECK(ok && (s_lock_seq_err == 0),
"TC-SYSLOG-099 p_rec / p_desc 空指针守卫(守卫顺序在取锁之前,不放锁)");
"TC-SYSLOG-099 p_rec / p_desc NULL guards (guards run before taking the lock, so nothing is released)");
/* 100 last_epoch:有记录但读失败 -> -1 且把 *p_epoch 清 0
* (调用方 syslog_mcu_mounted 直接拿这个值判上电/重启,留旧值就会误判) */
@@ -1629,7 +1629,7 @@ static void tc_extra_branches(void)
epoch = 0xA5A5A5A5u;
ff_fake_inject_rc(P_RUN, FF_FAKE_OP_OPEN, FR_DISK_ERR, 1);
ok = (syslog_last_epoch(SYSLOG_RUN, &epoch) == -1) && (epoch == 0u);
CHECK(ok, "TC-SYSLOG-100 last_epoch 读失败 -> -1 且 *p_epoch 清零(不留调用方旧值)");
CHECK(ok, "TC-SYSLOG-100 last_epoch read failure -> -1 and *p_epoch cleared (never leave the caller's old value)");
(void)i;
}
+27 -27
View File
@@ -178,26 +178,26 @@ static void tc_sample_gate(void)
CHECK((s_prof[TASK_PROF_UI_PERIOD].count == 1u) &&
(s_prof[TASK_PROF_UI_PERIOD].last == 500u) &&
(s_prof[TASK_PROF_UI_PERIOD].max == 500u),
"TC-TASKPROF-001 有效样本记 count/last/max");
"TC-TASKPROF-001 valid sample records count/last/max");
task_prof_sample(TASK_PROF_UI_PERIOD, 300u);
CHECK((s_prof[TASK_PROF_UI_PERIOD].max == 500u) &&
(s_prof[TASK_PROF_UI_PERIOD].last == 300u) &&
(s_prof[TASK_PROF_UI_PERIOD].count == 2u),
"TC-TASKPROF-002 更小样本不改 max");
"TC-TASKPROF-002 smaller sample does not change max");
reset_all();
task_prof_sample(TASK_PROF_UI_PERIOD, 0u);
CHECK((s_prof[TASK_PROF_UI_PERIOD].glitch == 1u) &&
(s_prof[TASK_PROF_UI_PERIOD].count == 0u) &&
(s_prof[TASK_PROF_UI_PERIOD].max == 0u),
"TC-TASKPROF-003 cyc=0 只累加 glitch(DWT 复位防线)");
"TC-TASKPROF-003 cyc=0 only accumulates glitch (DWT reset defence)");
reset_all();
task_prof_sample(TASK_PROF_UI_PERIOD, SystemCoreClock * 10u);
CHECK((s_prof[TASK_PROF_UI_PERIOD].glitch == 1u) &&
(s_prof[TASK_PROF_UI_PERIOD].count == 0u),
"TC-TASKPROF-004 超上界样本只累加 glitch");
"TC-TASKPROF-004 over-upper-bound sample only accumulates glitch");
reset_all();
task_prof_sample((task_prof_id_t)TASK_PROF_ID_COUNT, 100u);
@@ -205,7 +205,7 @@ static void tc_sample_gate(void)
task_prof_enter((task_prof_id_t)TASK_PROF_ID_COUNT);
task_prof_exit((task_prof_id_t)TASK_PROF_ID_COUNT);
CHECK((s_prof[0].count == 0u) && (s_prof[TASK_PROF_ID_COUNT - 1].count == 0u),
"TC-TASKPROF-005 id 越界安全返回,不碰任何槽");
"TC-TASKPROF-005 out-of-range id returns safely, touches no slot");
}
/* ===================== 时基与 enter/exit 配对 ===================== */
@@ -219,27 +219,27 @@ static void tc_enter_exit(void)
CHECK(((g_coredebug.DEMCR & CoreDebug_DEMCR_TRCENA_Msk) != 0u) &&
((g_dwt.CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0u) &&
(s_dwt_ok == 1u),
"TC-TASKPROF-006 首次取时基即惰性使能 DWT");
"TC-TASKPROF-006 first timebase read lazily enables DWT");
reset_all();
set_cyc(2000u);
task_prof_enter(TASK_PROF_UI_WORK);
CHECK((s_prof[TASK_PROF_UI_WORK].in_seg == 1u) &&
(s_prof[TASK_PROF_UI_WORK].t_start == 2000u),
"TC-TASKPROF-007 enter 置 in_seg 并记起点");
"TC-TASKPROF-007 enter sets in_seg and records the start point");
set_cyc(2640u);
task_prof_exit(TASK_PROF_UI_WORK);
CHECK((s_prof[TASK_PROF_UI_WORK].in_seg == 0u) &&
(s_prof[TASK_PROF_UI_WORK].last == 640u) &&
(s_prof[TASK_PROF_UI_WORK].count == 1u),
"TC-TASKPROF-008 exit 结算 Δ 并清 in_seg");
"TC-TASKPROF-008 exit settles the delta and clears in_seg");
reset_all();
task_prof_exit(TASK_PROF_ADC_BEAT);
CHECK((s_prof[TASK_PROF_ADC_BEAT].count == 0u) &&
(s_prof[TASK_PROF_ADC_BEAT].glitch == 0u),
"TC-TASKPROF-009 未 enter 直接 exit 不记账(防 0 起点假超大样本)");
"TC-TASKPROF-009 exit without enter records nothing (guards fake huge sample from 0 start)");
reset_all();
set_cyc(100u);
@@ -247,7 +247,7 @@ static void tc_enter_exit(void)
set_cyc(150u);
task_prof_exit(TASK_PROF_NET_POLL);
task_prof_exit(TASK_PROF_NET_POLL);
CHECK(s_prof[TASK_PROF_NET_POLL].count == 1u, "TC-TASKPROF-010 重复 exit 不重复记账");
CHECK(s_prof[TASK_PROF_NET_POLL].count == 1u, "TC-TASKPROF-010 repeated exit does not double count");
}
/* ===================== 上报节流与通道 ===================== */
@@ -258,26 +258,26 @@ static void tc_report_throttle(void)
reset_all();
g_tick = 4999u;
task_prof_report();
CHECK((g_line_calls == 0) && (g_raw_calls == 0), "TC-TASKPROF-011 首 5 s 前不发");
CHECK((g_line_calls == 0) && (g_raw_calls == 0), "TC-TASKPROF-011 nothing sent before the first 5 s");
g_tick = 5000u;
task_prof_report();
CHECK((g_line_calls == 2) && (g_raw_calls == 0),
"TC-TASKPROF-012 到点发两行走环形缓冲");
"TC-TASKPROF-012 on schedule: sends two lines via the ring buffer");
g_tick = 6999u;
task_prof_report();
CHECK(g_line_calls == 2, "TC-TASKPROF-013 不足 2 s 不发");
CHECK(g_line_calls == 2, "TC-TASKPROF-013 less than 2 s apart sends nothing");
g_tick = 7000u;
task_prof_report();
CHECK(g_line_calls == 4, "TC-TASKPROF-014 满 2 s 再发两行");
CHECK(g_line_calls == 4, "TC-TASKPROF-014 after a full 2 s sends two lines again");
g_line_calls = 0;
g_raw_calls = 0;
task_prof_report_raw();
CHECK((g_raw_calls == 2) && (g_line_calls == 0),
"TC-TASKPROF-015 report_raw 无视节流且走裸发通道");
"TC-TASKPROF-015 report_raw ignores throttling and uses the raw channel");
}
/* ===================== 行内容与换算 ===================== */
@@ -293,11 +293,11 @@ static void tc_line_format(void)
CHECK((g_line_calls == 2) &&
(strncmp(g_line[0], "[PROF] ", 7) == 0) &&
(strncmp(g_line[1], "[PROF2] ", 8) == 0),
"TC-TASKPROF-016 两行行首分别是 [PROF] / [PROF2]");
"TC-TASKPROF-016 the two lines start with [PROF] / [PROF2] respectively");
CHECK((strstr(g_line[0], "\r\n") != NULL) &&
(strstr(g_line[1], "\r\n") != NULL),
"TC-TASKPROF-016b 两行都以 CRLF 结尾");
CHECK(field_of(0, "uip=") == 0L, "TC-TASKPROF-016c 未采样的槽打印 0");
"TC-TASKPROF-016b both lines end with CRLF");
CHECK(field_of(0, "uip=") == 0L, "TC-TASKPROF-016c an unsampled slot prints 0");
/* 017:打印的是**微秒**,分母是 SystemCoreClock —— 断言落在打印字段上 */
reset_all();
@@ -306,14 +306,14 @@ static void tc_line_format(void)
g_tick = 5000u;
task_prof_report();
CHECK(field_of(0, "uip=") == 10L,
"TC-TASKPROF-017 168 MHz:1680 周期的 max 打印成 uip=10");
"TC-TASKPROF-017 168 MHz: max of 1680 cycles prints as uip=10");
CHECK(field_of(0, "adcm=") == 20L,
"TC-TASKPROF-017b 同频:3360 周期的 max 打印成 adcm=20");
"TC-TASKPROF-017b same clock: max of 3360 cycles prints as adcm=20");
SystemCoreClock = 84000000u; /* 改时钟树而忘了同步 → 整表偏移,这里必须显式跟着变 */
g_tick = 7000u;
task_prof_report();
CHECK(field_of(2, "uip=") == 20L,
"TC-TASKPROF-017c 84 MHz 下同一批周期数打印成 20");
"TC-TASKPROF-017c at 84 MHz the same cycle counts print as 20");
SystemCoreClock = 168000000u;
/* 018:gl= 是**所有槽**的 glitch 汇总,不是当前槽 */
@@ -323,7 +323,7 @@ static void tc_line_format(void)
task_prof_sample(TASK_PROF_NET_POLL, 0u);
g_tick = 5000u;
task_prof_report();
CHECK(field_of(0, "gl=") == 3L, "TC-TASKPROF-018 gl= 汇总所有槽的 glitch");
CHECK(field_of(0, "gl=") == 3L, "TC-TASKPROF-018 gl= sums the glitch of all slots");
/* 019:段未结算时,"此刻已在段内多久"要看得见(uipn / netn 两个字段) */
/* 字段口径 = 上报时刻的 CYCCNT 减去各自的进段时刻(**不是**样本值):
@@ -338,13 +338,13 @@ static void tc_line_format(void)
g_tick = 5000u;
task_prof_report();
CHECK(field_of(0, "uipn=") == 301L,
"TC-TASKPROF-019 UI_PERIOD 段内:uipn= 上报此刻已停留的 us");
"TC-TASKPROF-019 inside UI_PERIOD segment: uip= reports the us elapsed so far");
CHECK(field_of(1, "netn=") == 248L,
"TC-TASKPROF-019b NET_POLL 段内:[PROF2] 的 netn= 同步可见");
"TC-TASKPROF-019b inside NET_POLL segment: netn= of [PROF2] is visible too");
CHECK(field_of(0, "adcn=") == 0L,
"TC-TASKPROF-019c 未进段的 ADC_BEAT 打印 0(不串用别的槽)");
"TC-TASKPROF-019c ADC_BEAT not in segment prints 0 (no cross-slot leakage)");
CHECK(s_prof[TASK_PROF_UI_PERIOD].in_seg == 1u,
"TC-TASKPROF-019d 上报不消费段状态(只观测)");
"TC-TASKPROF-019d reporting does not consume segment state (observe only)");
/* 020:count 是累计量,两轮上报之间不能清零 */
reset_all();
@@ -355,7 +355,7 @@ static void tc_line_format(void)
task_prof_report();
CHECK((s_prof[TASK_PROF_UI_WORK].count == 1u) && (field_of(2, "uic=") == 0L) &&
(field_of(0, "uic=") == 0L),
"TC-TASKPROF-020 上报不清零(UI_WORK 不在 uic= 字段,仍为累计 1)");
"TC-TASKPROF-020 reporting does not clear counters (UI_WORK not in uic= field, still cumulative 1)");
}
+81 -81
View File
@@ -418,31 +418,31 @@ static int delays_all(uint32_t v, int n) {
static void tc_delay_fed(void) {
fakes_reset();
time_sync_delay_fed(0U);
CHECK((s_beat_calls == 0) && (s_delay_calls == 0), "TC-TIMESYNC-001 delay_fed(0) 不睡眠不喂狗");
CHECK((s_beat_calls == 0) && (s_delay_calls == 0), "TC-TIMESYNC-001 delay_fed(0) neither sleeps nor beats");
fakes_reset();
time_sync_delay_fed(50U);
CHECK((s_beat_calls == 1) && (s_delay_calls == 1) && (s_delays[0] == 50U),
"TC-TIMESYNC-002 delay_fed(50) = 1 片 50ms + 1 次心跳");
"TC-TIMESYNC-002 delay_fed(50) = one 50 ms slice + 1 beat");
fakes_reset();
time_sync_delay_fed(120U);
CHECK((s_beat_calls == 3) && (s_delay_calls == 3), "TC-TIMESYNC-003 delay_fed(120) 切成 3 片");
CHECK((s_beat_calls == 3) && (s_delay_calls == 3), "TC-TIMESYNC-003 delay_fed(120) splits into 3 slices");
CHECK((s_delays[0] == 50U) && (s_delays[1] == 50U) && (s_delays[2] == 20U),
"TC-TIMESYNC-003b 片长 50/50/20(末片为余数)");
"TC-TIMESYNC-003b slice lengths 50/50/20 (last slice is the remainder)");
fakes_reset();
time_sync_delay_fed(2000U);
CHECK((s_delay_calls == 40) && (s_beat_calls == 40) && delays_all(50U, 40),
"TC-TIMESYNC-004 delay_fed(2000) = 40 片 × 50ms,每片喂一次心跳");
"TC-TIMESYNC-004 delay_fed(2000) = 40 slices x 50 ms, one beat per slice");
fakes_reset();
time_sync_delay_fed(49U);
CHECK((s_beat_calls == 1) && (s_delays[0] == 49U), "TC-TIMESYNC-005 不足一片(49ms)单片处理");
CHECK((s_beat_calls == 1) && (s_delays[0] == 49U), "TC-TIMESYNC-005 less than one slice (49 ms) handled as a single slice");
fakes_reset();
time_sync_delay_fed(30000U);
CHECK(s_delay_calls == 600, "TC-TIMESYNC-006 总预算 30s = 600 片(片长上限恒 50ms)");
CHECK(s_delay_calls == 600, "TC-TIMESYNC-006 total budget 30 s = 600 slices (slice cap is always 50 ms)");
}
/* ===================== TC-TIMESYNC-010..015:open_listener ===================== */
@@ -451,13 +451,13 @@ static void tc_open_listener(void) {
fakes_reset();
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TIMESYNC-010 open_listener 成功返回 socket 描述符");
CHECK(s == 5, "TC-TIMESYNC-010 open_listener success returns the socket descriptor");
CHECK((s_socket_calls == 1) && (s_bind_calls == 1) && (s_listen_calls == 1),
"TC-TIMESYNC-010b socket / bind / listen 各一次");
CHECK(s_htons_last == (uint16_t)TIME_SYNC_PORT, "TC-TIMESYNC-010c 端口经 net_htons(8888)");
CHECK(s_bind_port == net_htons((uint16_t)TIME_SYNC_PORT), "TC-TIMESYNC-010d sin_port 为网络序");
"TC-TIMESYNC-010b socket / bind / listen called once each");
CHECK(s_htons_last == (uint16_t)TIME_SYNC_PORT, "TC-TIMESYNC-010c port goes through net_htons(8888)");
CHECK(s_bind_port == net_htons((uint16_t)TIME_SYNC_PORT), "TC-TIMESYNC-010d sin_port is in network byte order");
CHECK(s_bind_family == (uint16_t)NET_AF_INET, "TC-TIMESYNC-010e sin_family = NET_AF_INET");
CHECK(s_delay_calls == 0, "TC-TIMESYNC-010f 成功路径无退避延时");
CHECK(s_delay_calls == 0, "TC-TIMESYNC-010f no backoff delay on the success path");
}
fakes_reset();
@@ -465,10 +465,10 @@ static void tc_open_listener(void) {
s_bind_seq_n = 1;
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TIMESYNC-011 bind 失败一次后重试成功");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-011b bind 失败关闭该 socket");
CHECK(s_delay_calls == 40, "TC-TIMESYNC-011c bind 失败退避 2000ms(40 片)");
CHECK(g_tstest_err_count == 1, "TC-TIMESYNC-011d bind 失败留痕 DBG_ERROR");
CHECK(s == 5, "TC-TIMESYNC-011 bind succeeds on retry after one failure");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-011b bind failure closes that socket");
CHECK(s_delay_calls == 40, "TC-TIMESYNC-011c bind failure backs off 2000 ms (40 slices)");
CHECK(g_tstest_err_count == 1, "TC-TIMESYNC-011d bind failure leaves a DBG_ERROR trace");
}
fakes_reset();
@@ -476,19 +476,19 @@ static void tc_open_listener(void) {
s_listen_seq_n = 1;
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TIMESYNC-012 listen 失败一次后重试成功");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-012b listen 失败关闭该 socket");
CHECK(s_delay_calls == 40, "TC-TIMESYNC-012c listen 失败退避 2000ms");
CHECK(s == 5, "TC-TIMESYNC-012 listen succeeds on retry after one failure");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-012b listen failure closes that socket");
CHECK(s_delay_calls == 40, "TC-TIMESYNC-012c listen failure backs off 2000 ms");
}
fakes_reset();
s_socket_default = -1;
{
int s = time_sync_open_listener();
CHECK(s == -1, "TC-TIMESYNC-013 socket 连续失败 -> 返回 -1");
CHECK(s_socket_calls == 5, "TC-TIMESYNC-013b 重试上限 5 次");
CHECK(g_tstest_err_count == 5, "TC-TIMESYNC-013c 每次失败各留痕一条(5 条 DBG_ERROR)");
CHECK(s_delay_calls == 50, "TC-TIMESYNC-013d 每次失败退避 500ms(5 × 10 片)");
CHECK(s == -1, "TC-TIMESYNC-013 socket keeps failing -> returns -1");
CHECK(s_socket_calls == 5, "TC-TIMESYNC-013b retry cap is 5 attempts");
CHECK(g_tstest_err_count == 5, "TC-TIMESYNC-013c every failure leaves its own trace (5 DBG_ERROR lines)");
CHECK(s_delay_calls == 50, "TC-TIMESYNC-013d every failure backs off 500 ms (5 x 10 slices)");
}
fakes_reset();
@@ -498,16 +498,16 @@ static void tc_open_listener(void) {
s_socket_seq_n = 3;
{
int s = time_sync_open_listener();
CHECK(s == 9, "TC-TIMESYNC-014 第 3 次重试成功并返回新描述符");
CHECK(s_socket_calls == 3, "TC-TIMESYNC-014b 恰好尝试 3 次");
CHECK(s_delay_calls == 20, "TC-TIMESYNC-014c 两次失败退避 500ms × 2");
CHECK(s == 9, "TC-TIMESYNC-014 3rd retry succeeds and returns the new descriptor");
CHECK(s_socket_calls == 3, "TC-TIMESYNC-014b exactly 3 attempts");
CHECK(s_delay_calls == 20, "TC-TIMESYNC-014c two failures back off 500 ms x 2");
}
fakes_reset();
s_socket_default = -1;
(void)time_sync_open_listener();
CHECK(s_beat_calls == 55, "TC-TIMESYNC-015 每轮重试(5) + 每片延时(50) 各喂一次心跳");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-015b 心跳位恒为 TASK_WD_TIMESYNC");
CHECK(s_beat_calls == 55, "TC-TIMESYNC-015 every retry round (5) + every delay slice (50) beats once");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-015b the beat bit is always TASK_WD_TIMESYNC");
}
/* ===================== TC-TIMESYNC-020..029:handle_conn =====================
@@ -526,13 +526,13 @@ static void tc_handle_conn(void) {
s_recv_seq_n = 1;
s_tick = 100U;
time_sync_handle_conn(7);
CHECK(s_set_unix_calls == 1, "TC-TIMESYNC-020 TIME 包一次收齐 -> 更新墙钟一次");
CHECK(s_set_unix_last == 0x12345678u, "TC-TIMESYNC-020b epoch 按大端解析");
CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TIMESYNC-020c 首次请求 16 字节");
CHECK(s_recv_to_log[0] == 50U, "TC-TIMESYNC-020d 单次等待片长 = 50ms");
CHECK(s_close_calls == 0, "TC-TIMESYNC-020e 处理完**不**关闭连接(close 交外层 conn!=ls 分支)");
CHECK(s_drop_calls == 0, "TC-TIMESYNC-020g 成功会话不拆(drop 只用于放弃路径)");
CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-020f 留痕 wall clock updated");
CHECK(s_set_unix_calls == 1, "TC-TIMESYNC-020 TIME packet in one shot -> wall clock updated once");
CHECK(s_set_unix_last == 0x12345678u, "TC-TIMESYNC-020b epoch parsed big-endian");
CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TIMESYNC-020c the first request asks for 16 bytes");
CHECK(s_recv_to_log[0] == 50U, "TC-TIMESYNC-020d single wait slice length = 50 ms");
CHECK(s_close_calls == 0, "TC-TIMESYNC-020e after handling, the connection is **not** closed (close is the outer conn!=ls branch job)");
CHECK(s_drop_calls == 0, "TC-TIMESYNC-020g a successful session is not torn down (drop is only for give-up paths)");
CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-020f leaves the wall clock updated trace");
/* 2) 分段送达:4 + 4 */
fakes_reset();
@@ -541,11 +541,11 @@ static void tc_handle_conn(void) {
recv_set(1, 4, pkt + 4, 4, 10U);
s_recv_seq_n = 2;
time_sync_handle_conn(3);
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 10u), "TC-TIMESYNC-021 分段送达累积后解析正确");
CHECK(s_recv_calls == 2, "TC-TIMESYNC-021b 读了两次");
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 10u), "TC-TIMESYNC-021 pieces accumulate and then parse correctly");
CHECK(s_recv_calls == 2, "TC-TIMESYNC-021b read twice");
CHECK((s_recv_len_log[0] == 16) && (s_recv_len_log[1] == 12),
"TC-TIMESYNC-021c 第二次只请求剩余 12 字节");
CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U), "TC-TIMESYNC-021d 每片 50ms(预算充裕)");
"TC-TIMESYNC-021c the second request asks only for the remaining 12 bytes");
CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U), "TC-TIMESYNC-021d 50 ms per slice (budget is ample)");
/* 3) 16 字节一次到达(只取前 8) */
fakes_reset();
@@ -553,8 +553,8 @@ static void tc_handle_conn(void) {
recv_set(0, 16, pkt, 16, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(4);
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 5u), "TC-TIMESYNC-022 超长包只取前 8 字节");
CHECK(s_recv_calls == 1, "TC-TIMESYNC-022b 一次读够即停");
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 5u), "TC-TIMESYNC-022 an over-long packet only takes the first 8 bytes");
CHECK(s_recv_calls == 1, "TC-TIMESYNC-022b stops as soon as one read is enough");
/* 4) 前缀不是 TIME */
fakes_reset();
@@ -569,23 +569,23 @@ static void tc_handle_conn(void) {
recv_set(0, 8, pkt, 8, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(4);
CHECK(s_set_unix_calls == 0, "TC-TIMESYNC-023 非 TIME 前缀不更新墙钟");
CHECK(s_close_calls == 0, "TC-TIMESYNC-023b 非 TIME 前缀也不关闭连接");
CHECK(s_set_unix_calls == 0, "TC-TIMESYNC-023 a non-TIME prefix does not update the wall clock");
CHECK(s_close_calls == 0, "TC-TIMESYNC-023b a non-TIME prefix also does not close the connection");
/* 5) 对端关闭(EOF) */
fakes_reset();
recv_set(0, 0, NULL, 0, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_set_unix_calls == 0) && (s_recv_calls == 1), "TC-TIMESYNC-024 对端关闭(EOF)立即退出不解析");
CHECK(s_close_calls == 0, "TC-TIMESYNC-024b EOF 也不关闭连接(net 层 auto_relisten 收口监听)");
CHECK((s_set_unix_calls == 0) && (s_recv_calls == 1), "TC-TIMESYNC-024 peer close (EOF) exits immediately without parsing");
CHECK(s_close_calls == 0, "TC-TIMESYNC-024b EOF also does not close the connection (net-layer auto_relisten re-arms the listener)");
/* 6) 真错误 */
fakes_reset();
recv_set(0, -1, NULL, 0, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_recv_calls == 1) && (s_set_unix_calls == 0), "TC-TIMESYNC-025 真错误立即退出不解析");
CHECK((s_recv_calls == 1) && (s_set_unix_calls == 0), "TC-TIMESYNC-025 a real error exits immediately without parsing");
/* 7) 4 字节后 EOF */
fakes_reset();
@@ -594,27 +594,27 @@ static void tc_handle_conn(void) {
recv_set(1, 0, NULL, 0, 0U);
s_recv_seq_n = 2;
time_sync_handle_conn(6);
CHECK(s_set_unix_calls == 0, "TC-TIMESYNC-026 不足 8 字节不解析");
CHECK(s_recv_calls == 2, "TC-TIMESYNC-026b 继续等待后续字节");
CHECK(s_set_unix_calls == 0, "TC-TIMESYNC-026 fewer than 8 bytes are not parsed");
CHECK(s_recv_calls == 2, "TC-TIMESYNC-026b keeps waiting for the remaining bytes");
/* 8) 30s 预算耗尽 */
fakes_reset();
recv_set(0, -2, NULL, 0, 30000U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK(s_recv_calls == 1, "TC-TIMESYNC-027 预算耗尽后不再读(只读 1 次)");
CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-027b 留痕 no TIME packet in 30s, drop conn");
CHECK(s_close_calls == 0, "TC-TIMESYNC-027c 预算耗尽也不关闭连接");
CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d 预算耗尽主动拆会话(E-02 收口:net_drop_conn 拆连接保监听)");
CHECK(s_recv_calls == 1, "TC-TIMESYNC-027 no more reads once the budget is exhausted (only 1 read)");
CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-027b leaves the no TIME packet in 30s, drop conn trace");
CHECK(s_close_calls == 0, "TC-TIMESYNC-027c budget exhausted still does not close the connection");
CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d budget exhausted drops the session on purpose (E-02 wrap-up: net_drop_conn kills the connection, keeps the listener)");
/* 9) 剩余预算不足一片 */
fakes_reset();
recv_set(0, -2, NULL, 0, 29980U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK(s_recv_calls == 2, "TC-TIMESYNC-028 两次等待后结束");
CHECK(s_recv_calls == 2, "TC-TIMESYNC-028 ends after two waits");
CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 20U),
"TC-TIMESYNC-028b 第二次片长 = 剩余 20ms");
"TC-TIMESYNC-028b second slice length = the remaining 20 ms");
/* 10) 32 位 tick 回绕 */
fakes_reset();
@@ -623,7 +623,7 @@ static void tc_handle_conn(void) {
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_recv_calls == 2) && (s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U),
"TC-TIMESYNC-029 32 位 tick 回绕后预算计算仍正确");
"TC-TIMESYNC-029 after a 32-bit tick wraparound the budget math is still correct");
}
/* ===================== TC-TIMESYNC-040..044:time_sync_task ===================== */
@@ -646,15 +646,15 @@ static void tc_task(void) {
time_sync_task(NULL);
}
CHECK((s_wd_bind_calls == 1) && (s_wd_bind_id_bad == 0),
"TC-TIMESYNC-040 任务入口登记心跳位(task_wd_bind 一次)");
CHECK(s_evt_calls == 0, "TC-TIMESYNC-040b sys_evtHandle==NULL 时跳过就绪屏障");
CHECK(s_accept_calls == 3, "TC-TIMESYNC-040c accept 轮询 3 次(2 次无连接 + 1 次成功)");
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 9u), "TC-TIMESYNC-040d 收到时间包并更新墙钟");
CHECK(s_close_calls == 1, "TC-TIMESYNC-040e 连接被关闭一次");
CHECK(s_delay_calls == 9, "TC-TIMESYNC-040f 延时片数 = 4+4+1(200+200+50ms)");
CHECK(delays_all(50U, 9), "TC-TIMESYNC-040g 所有延时均按 50ms 分片");
CHECK(s_beat_calls == 13, "TC-TIMESYNC-040h 心跳次数 = 主循环1 + 退避8 + 收连接1 + 收尾1 + 次轮1");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-040i 所有心跳均打 TIMESYNC 位");
"TC-TIMESYNC-040 task entry registers the watchdog beat bit (task_wd_bind once)");
CHECK(s_evt_calls == 0, "TC-TIMESYNC-040b skips the readiness barrier when sys_evtHandle==NULL");
CHECK(s_accept_calls == 3, "TC-TIMESYNC-040c accept polled 3 times (2 with no connection + 1 success)");
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 9u), "TC-TIMESYNC-040d the time packet arrived and the wall clock was updated");
CHECK(s_close_calls == 1, "TC-TIMESYNC-040e the connection is closed once");
CHECK(s_delay_calls == 9, "TC-TIMESYNC-040f delay slice count = 4+4+1 (200+200+50 ms)");
CHECK(delays_all(50U, 9), "TC-TIMESYNC-040g every delay is sliced at 50 ms");
CHECK(s_beat_calls == 13, "TC-TIMESYNC-040h beat count = main loop 1 + backoff 8 + accepted connection 1 + wrap-up 1 + next round 1");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-040i every beat sets the TIMESYNC bit");
/* B) 就绪屏障:2 次未就绪 + 1 次就绪 */
fakes_reset();
@@ -667,12 +667,12 @@ static void tc_task(void) {
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_evt_calls == 3, "TC-TIMESYNC-041 就绪屏障轮询 3 次(2 次未就绪 + 1 次就绪)");
CHECK(s_evt_last_timeout == 100U, "TC-TIMESYNC-041b 屏障等待按 100ms 分片");
CHECK(s_evt_last_flags == SYS_EVT_NET_READY, "TC-TIMESYNC-041c 等待 NET_READY 位");
CHECK(s_evt_last_options == (osFlagsWaitAny | osFlagsNoClear), "TC-TIMESYNC-041d 就绪位不被消费");
CHECK(s_beat_calls == 3, "TC-TIMESYNC-041e 未就绪的两次各喂一次心跳(+主循环 1 次)");
CHECK(s_wd_bind_calls == 1, "TC-TIMESYNC-041f 仍先登记心跳路由");
CHECK(s_evt_calls == 3, "TC-TIMESYNC-041 readiness barrier polled 3 times (2 not ready + 1 ready)");
CHECK(s_evt_last_timeout == 100U, "TC-TIMESYNC-041b barrier waits are sliced at 100 ms");
CHECK(s_evt_last_flags == SYS_EVT_NET_READY, "TC-TIMESYNC-041c waits for the NET_READY bit");
CHECK(s_evt_last_options == (osFlagsWaitAny | osFlagsNoClear), "TC-TIMESYNC-041d the ready bit is not consumed");
CHECK(s_beat_calls == 3, "TC-TIMESYNC-041e the two not-ready rounds each beat once (+ 1 in the main loop)");
CHECK(s_wd_bind_calls == 1, "TC-TIMESYNC-041f still registers the beat routing first");
/* C) 屏障期间持续喂心跳(就绪位长期不置位) */
fakes_reset();
@@ -682,8 +682,8 @@ static void tc_task(void) {
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_evt_calls == 4, "TC-TIMESYNC-042 屏障等待 4 次均未就绪(循环未挂死)");
CHECK(s_beat_calls == 3, "TC-TIMESYNC-042b 每次未就绪后各喂一次心跳(第 4 次跳出前)");
CHECK(s_evt_calls == 4, "TC-TIMESYNC-042 barrier waited 4 times, none ready (loop did not hang)");
CHECK(s_beat_calls == 3, "TC-TIMESYNC-042b beats once after each not-ready round (before the 4th breaks out)");
/* D) 网络代次变化 -> 关闭旧监听并重监听 */
fakes_reset();
@@ -695,12 +695,12 @@ static void tc_task(void) {
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-043 代次变化时关闭旧监听");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-043 closes the old listener when the network generation changes");
/* INFO 三条:两次 open_listener 成功各一条 "TCP listen on" + 一条 "network re-init, relisten" */
CHECK(g_tstest_info_count == 3, "TC-TIMESYNC-043b 留痕 network re-init, relisten(另含 2 条 listen 成功)");
CHECK(s_accept_calls == 2, "TC-TIMESYNC-043c 每次循环只轮询一次 accept 即放弃");
CHECK(g_tstest_info_count == 3, "TC-TIMESYNC-043b leaves the network re-init, relisten trace (plus 2 listen-success lines)");
CHECK(s_accept_calls == 2, "TC-TIMESYNC-043c polls accept only once per loop then gives up");
CHECK((s_socket_calls == 2) && (s_bind_calls == 2) && (s_listen_calls == 2),
"TC-TIMESYNC-043d 第二次循环重建了监听");
"TC-TIMESYNC-043d the second loop rebuilt the listener");
/* E) 监听建立失败 -> 5 次重试后放弃并退避 2000ms */
fakes_reset();
@@ -709,11 +709,11 @@ static void tc_task(void) {
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_socket_calls == 5, "TC-TIMESYNC-044 open_listener 内部重试 5 次后放弃");
CHECK(g_tstest_err_count == 6, "TC-TIMESYNC-044b 5 条 net_socket FAIL + 1 条 retry later");
CHECK(s_delay_calls == 90, "TC-TIMESYNC-044c 退避 5×500ms + 2000ms = 90 片");
CHECK(s_beat_calls == 97, "TC-TIMESYNC-044d 心跳次数 = 2 + 5 + 50 + 40");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-044e 所有心跳均打 TIMESYNC 位");
CHECK(s_socket_calls == 5, "TC-TIMESYNC-044 open_listener gives up after 5 internal retries");
CHECK(g_tstest_err_count == 6, "TC-TIMESYNC-044b 5 net_socket FAIL lines + 1 retry later line");
CHECK(s_delay_calls == 90, "TC-TIMESYNC-044c backoff 5x500 ms + 2000 ms = 90 slices");
CHECK(s_beat_calls == 97, "TC-TIMESYNC-044d beat count = 2 + 5 + 50 + 40");
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-044e every beat sets the TIMESYNC bit");
}
/* ===================== main ===================== */
+33 -33
View File
@@ -114,21 +114,21 @@ static void fakes_reset(void) {
static void tc_init(void) {
fakes_reset();
CHECK(vol_lock_init() == 0, "TC-VOLLOCK-001 init 首次调用返回 0");
CHECK(s_new_calls == 1, "TC-VOLLOCK-001b init 创建互斥量一次(osMutexNew)");
CHECK(s_lock != NULL, "TC-VOLLOCK-001c 模块静态态已持有句柄");
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 init 重复调用仍返回 0(幂等)");
CHECK(s_new_calls == 1, "TC-VOLLOCK-002b 幂等:不再创建第二个互斥量");
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 失败 -> init 返回 -1");
CHECK(s_lock == NULL, "TC-VOLLOCK-003b 失败时不留下半初始化句柄");
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 失败后重试可成功(返回 0)");
CHECK(s_new_calls == 2, "TC-VOLLOCK-004b 重试确实再次调用 osMutexNew");
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 ===================== */
@@ -137,46 +137,46 @@ static void tc_wait(void) {
/* --- 已创建:超时值原样透传 --- */
fakes_reset();
(void)vol_lock_init();
CHECK(vol_lock_wait(100U) == 1, "TC-VOLLOCK-005 wait 获取成功返回 1");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-005b osMutexWait 被调用一次");
CHECK(s_last_timeout == 100U, "TC-VOLLOCK-005c timeout 原样透传给 osMutexWait");
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) 获取成功返回 1(try 语义)");
CHECK(s_last_timeout == 0U, "TC-VOLLOCK-014b wait(0) 的 timeout 透传为 0");
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 非 osOK -> wait 返回 0");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-006b 超时路径确实尝试过等待");
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 自动 init 并获取");
CHECK(s_new_calls == 1, "TC-VOLLOCK-007b 自动 init 创建了互斥量");
CHECK(s_wait_calls == 1, "TC-VOLLOCK-007c 随后执行了一次等待");
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:锁创建失败仍返回 1(不阻塞喂狗点)");
CHECK(s_wait_calls == 0, "TC-VOLLOCK-008b fail-open 时**不调用** osMutexWait(零阻塞)");
CHECK(s_lock == NULL, "TC-VOLLOCK-008c fail-open 后仍无句柄");
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 fail-open 后 release 不调用 osMutexRelease(判空静默)");
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 多次 wait 不重复创建互斥量");
CHECK(s_wait_calls == 2, "TC-VOLLOCK-010b 每次 wait 各等待一次");
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 ===================== */
@@ -187,23 +187,23 @@ static void tc_release(void) {
(void)vol_lock_init();
(void)vol_lock_wait(0U);
vol_lock_release();
CHECK(s_release_calls == 1, "TC-VOLLOCK-011 已持锁时 release 调用 osMutexRelease 一次");
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 静默返回(不调用、不崩溃)");
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 周期:init -> 0");
CHECK(vol_lock_wait(0U) == 1, "TC-VOLLOCK-013b 周期:wait -> 1");
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 周期:再 wait -> 1(可重复获取)");
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 周期:全程只创建一次互斥量");
CHECK(s_release_calls == 2, "TC-VOLLOCK-013e 周期:释放两次");
CHECK(s_last_timeout == 100U, "TC-VOLLOCK-013f 周期:第二次 wait 的 timeout 透传正确");
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");
}
/* ===================== main ===================== */
+49 -49
View File
@@ -131,44 +131,44 @@ static void beat_all(void) {
/* ===================== 用例 ===================== */
static void tc_001_not_armed(void) {
printf("TC-WATCHDOG-001 未启动时 poll 不做判定\n");
printf("TC-WATCHDOG-001 poll makes no decision before start\n");
beat_all();
poll_advance(1000);
CHECK(s_feed_count == 0, "未启动时 poll 不喂狗");
CHECK(task_wd_is_armed() == 0, "is_armed == 0(未启动)");
CHECK(s_feed_count == 0, "poll does not feed before start");
CHECK(task_wd_is_armed() == 0, "is_armed == 0 (not started)");
}
static void tc_002_throttle(void) {
printf("TC-WATCHDOG-002 节流:< 100ms 直接返回\n");
printf("TC-WATCHDOG-002 throttle: < 100ms returns directly\n");
start_and_clear();
beat_all();
poll_advance(50); /* 50ms < 100ms → 不抽样 */
CHECK(s_feed_count == 0, "50ms 内 poll 不喂狗");
CHECK(s_feed_count == 0, "poll within 50ms does not feed");
poll_advance(50); /* 累计 100ms → 抽样 */
CHECK(s_feed_count == 1, "累计 100ms 后喂狗 1 次");
CHECK(s_feed_count == 1, "after 100ms accumulated, fed exactly once");
}
static void tc_003_all_beat_feeds(void) {
printf("TC-WATCHDOG-003 全员到位 → 喂狗一次\n");
printf("TC-WATCHDOG-003 all tasks present -> feed once\n");
start_and_clear();
beat_all();
poll_advance(100);
CHECK(s_feed_count == 1, "全员到位喂狗 1 次");
CHECK(task_wd_last_mask() == 0x1F7u, "last_mask == 0x1F7(bit0|1|2|4|5|6|7|8)");
CHECK(s_feed_count == 1, "all present feeds exactly once");
CHECK(task_wd_last_mask() == 0x1F7u, "last_mask == 0x1F7 (bit0|1|2|4|5|6|7|8)");
}
static void tc_004_take_and_clear(void) {
printf("TC-WATCHDOG-004 取走清零:旧位不残留\n");
printf("TC-WATCHDOG-004 take-and-clear: no stale bits left\n");
start_and_clear();
beat_all();
poll_advance(100); /* 消费位图并喂狗 */
CHECK(s_feed_count == 1, "第一轮喂狗");
CHECK(s_feed_count == 1, "first round fed");
poll_advance(100); /* 不重新置位 */
CHECK(s_feed_count == 1, "未重新置位 → 第二轮不喂狗(位图已取走清零)");
CHECK(s_feed_count == 1, "no re-set -> second round does not feed (bitmap already taken and cleared)");
}
static void tc_005_mask_required(void) {
printf("TC-WATCHDOG-005 掩码判定:缺任一位不喂\n");
printf("TC-WATCHDOG-005 mask check: missing any bit -> no feed\n");
start_and_clear();
/* 故意缺 UI(bit4) */
task_wd_beat(TASK_WD_DEFAULT);
@@ -179,31 +179,31 @@ static void tc_005_mask_required(void) {
task_wd_beat(TASK_WD_IEC104);
task_wd_beat(TASK_WD_PROTECT);
poll_advance(100);
CHECK(s_feed_count == 0, "缺 UI 位 → 不喂狗");
CHECK(s_feed_count == 0, "UI bit missing -> no feed");
CHECK(task_wd_last_mask() == (0x1F7u & ~(1u << TASK_WD_UI)),
"last_mask 精确记录缺失位");
"last_mask records the missing bit exactly");
}
static void tc_006_stall_8(void) {
printf("TC-WATCHDOG-006 连续 8 次缺席 → 打 stall 并停喂\n");
printf("TC-WATCHDOG-006 8 consecutive misses -> print stall and stop feeding\n");
start_and_clear();
beat_all(); poll_advance(100); /* streak 清零 */
int err0 = g_wdtest_err_count;
for (int i = 1; i <= 7; i++) {
poll_advance(100);
CHECK(g_wdtest_err_count == err0, "第 1..7 次缺席不报 stall");
CHECK(g_wdtest_err_count == err0, "misses 1..7 do not report stall");
}
poll_advance(100); /* 第 8 次 */
CHECK(g_wdtest_err_count == err0 + 1, "第 8 次缺席打 [WD] task stall");
CHECK(s_feed_count == 1, "全程仅在开始时喂过 1 次(缺席期间未喂)");
CHECK(g_wdtest_err_count == err0 + 1, "8th miss prints [WD] task stall");
CHECK(s_feed_count == 1, "fed exactly once at the start only (nothing fed during the missing period)");
}
static void tc_007_stall_repeats_until_reset(void) {
printf("TC-WATCHDOG-007 停喂后 stall 日志持续复报(当前设计:每 100ms 一条)\n");
printf("TC-WATCHDOG-007 after feeding stops the stall log keeps repeating (current design: one per 100ms)\n");
start_and_clear();
for (int i = 0; i < 8; i++) poll_advance(100); /* 第 8 次触发 */
int err8 = g_wdtest_err_count;
CHECK(err8 == 1, "第 8 次缺席后已报 1 条 stall");
CHECK(err8 == 1, "after the 8th miss exactly 1 stall line was reported");
poll_advance(100); /* 第 9 次 */
poll_advance(100); /* 第 10 次 */
/*
@@ -213,89 +213,89 @@ static void tc_007_stall_repeats_until_reset(void) {
* 代价是日志冗余(约 9 条/停顿周期)。若改为"每停顿周期仅报一次",
* 需加一个 s_stall_reported 标志,见文档"已知可优化点"。
*/
CHECK(g_wdtest_err_count == err8 + 2, "停喂后每次抽样都复报 stall(现状)");
CHECK(g_wdtest_err_count == err8 + 2, "after feeding stops every sample re-reports stall (current behaviour)");
}
static void tc_008_recover(void) {
printf("TC-WATCHDOG-008 恢复:重新全员到位 → 喂狗 + streak 归零\n");
printf("TC-WATCHDOG-008 recovery: all present again -> feed + streak reset\n");
start_and_clear();
for (int i = 0; i < 8; i++) poll_advance(100); /* 触发 stall */
beat_all();
poll_advance(100);
CHECK(s_feed_count == 1, "恢复后立即喂狗");
CHECK(s_feed_count == 1, "feeds immediately after recovery");
int err0 = g_wdtest_err_count;
poll_advance(100); /* 再缺一次 */
CHECK(g_wdtest_err_count == err0, "streak 已归零:单次缺席不报 stall");
CHECK(g_wdtest_err_count == err0, "streak already reset: a single miss does not report stall");
}
static void tc_009_throttle_boundary(void) {
printf("TC-WATCHDOG-009 节流窗口边界 99/100/199/200\n");
printf("TC-WATCHDOG-009 throttle window edges 99/100/199/200\n");
start_and_clear();
beat_all();
poll_advance(99); CHECK(s_feed_count == 0, "99ms 不判");
poll_advance(1); CHECK(s_feed_count == 1, "100ms 判(喂狗)");
poll_advance(99); CHECK(s_feed_count == 0, "99ms: no decision");
poll_advance(1); CHECK(s_feed_count == 1, "100ms: decides (feed)");
beat_all();
poll_advance(99); CHECK(s_feed_count == 1, "再 99ms 不判");
poll_advance(1); CHECK(s_feed_count == 2, "累计 100ms 判");
poll_advance(99); CHECK(s_feed_count == 1, "another 99ms: no decision");
poll_advance(1); CHECK(s_feed_count == 2, "accumulated 100ms: decides");
}
static void tc_010_tick_wrap(void) {
printf("TC-WATCHDOG-010 tick 回绕安全\n");
printf("TC-WATCHDOG-010 tick wraparound is safe\n");
start_and_clear();
s_tick = 0xFFFFFF00u; /* 推到接近 2^32 回绕 */
task_wd_poll(); /* 建立基线(此处可能不判,取决节流) */
beat_all();
s_feed_count = 0;
poll_advance(0x100u); /* 跨越回绕:0xFFFFFF00 + 0x100 = 0 */
CHECK(s_feed_count == 1, "回绕处 100ms 仍正确判(无符号差)");
CHECK(s_feed_count == 1, "across the wraparound 100ms still decides correctly (unsigned diff)");
}
static void tc_011_observers(void) {
printf("TC-WATCHDOG-011 观测接口语义\n");
printf("TC-WATCHDOG-011 observation API semantics\n");
start_and_clear();
CHECK(task_wd_is_armed() == 1, "is_armed == 1(已启动)");
CHECK(task_wd_is_armed() == 1, "is_armed == 1 (started)");
beat_all();
poll_advance(100);
CHECK(task_wd_last_mask() == 0x1F7u, "last_mask 反映最近一次抽样");
CHECK(task_wd_miss_count() == 0u, "全程达标 → miss_count == 0");
CHECK(task_wd_last_mask() == 0x1F7u, "last_mask reflects the most recent sample");
CHECK(task_wd_miss_count() == 0u, "all rounds met -> miss_count == 0");
}
static void tc_013_bad_id(void) {
printf("TC-WATCHDOG-013 非法任务 ID 被忽略\n");
printf("TC-WATCHDOG-013 invalid task id is ignored\n");
start_and_clear();
task_wd_beat((task_wd_id_t)(TASK_WD_ID_MAX + 3)); /* 非法:应被忽略 */
poll_advance(100);
CHECK(s_feed_count == 0, "非法 ID 未污染位图(必需位全缺 → 不喂)");
CHECK(task_wd_last_mask() == 0u, "位图保持 0(非法位未置)");
CHECK(s_feed_count == 0, "invalid id does not pollute the bitmap (all required bits missing -> no feed)");
CHECK(task_wd_last_mask() == 0u, "bitmap stays 0 (invalid bit never set)");
}
static void tc_014_start_idempotent(void) {
printf("TC-WATCHDOG-014 start 幂等\n");
printf("TC-WATCHDOG-014 start is idempotent\n");
task_wd_start();
task_wd_start();
CHECK(s_start_count == 1, "重复 start 只启动 IWDG 一次");
CHECK(s_start_count == 1, "repeated start launches IWDG only once");
CHECK(task_wd_is_armed() == 1, "is_armed == 1");
}
static void tc_015_start_feeds_once(void) {
printf("TC-WATCHDOG-015 start 先喂一次(首个窗口从启动时刻起算)\n");
printf("TC-WATCHDOG-015 start feeds once first (first window counted from the start instant)\n");
task_wd_start();
CHECK(s_start_count == 1, "start 启动 IWDG 一次");
CHECK(s_feed_count == 1, "start 内部先喂狗一次(避免启动即超时)");
CHECK(s_start_count == 1, "start launches IWDG once");
CHECK(s_feed_count == 1, "start feeds once internally (avoids a timeout right at boot)");
}
static void tc_016_stall_not_armed_no_feed(void) {
printf("TC-WATCHDOG-016 未启动时即便全员到位也不喂\n");
printf("TC-WATCHDOG-016 even with all tasks present, nothing feeds before start\n");
/* 未 start:poll 直接返回 */
beat_all();
poll_advance(500);
CHECK(s_feed_count == 0, "未启动 → 不喂狗(无论位图)");
CHECK(task_wd_last_mask() == 0u, "未启动 → 不更新 last_mask");
CHECK(s_feed_count == 0, "not started -> no feed (whatever the bitmap)");
CHECK(task_wd_last_mask() == 0u, "not started -> last_mask not updated");
}
int main(void) {
printf("=== Task Watchdog 主机侧单元测试 ===\n");
printf("被测:App/util/task_watchdog.c(判定状态机)\n\n");
printf("=== Task Watchdog host-side unit tests ===\n");
printf("Target: App/util/task_watchdog.c (decision state machine)\n\n");
fresh(); tc_001_not_armed();
fresh(); tc_016_stall_not_armed_no_feed();