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
+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)