chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
|
||||
|
||||
# 只编译不烧录(S6;不需要 MCU / 串口 / ST-LINK,验证 4 套件可编译性)
|
||||
# 只编译不烧录(不需要 MCU / 串口 / ST-LINK,验证 4 套件可编译性)
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build
|
||||
```
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
| 文件 | 测试对象 / 作用 |
|
||||
|---|---|
|
||||
| `test_config.c/h` | **测试任务配置**:`TEST_SUITE_*` 套件选择开关(互斥)+ `ENABLE_DESTRUCTIVE_TESTS` |
|
||||
| `test_common.h` | **公共断言内核**:`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`;各套件用一行宏绑定自身统计变量(T-4) |
|
||||
| `test_common.h` | **公共断言内核**:`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`;各套件用一行宏绑定自身统计变量 |
|
||||
| `ch395f_test_task.c/h` + `ch395f_test.h` | CH395F 以太网(硬件层 Phase 1~5 / NET 层 Phase 6~11) |
|
||||
| `gd5f_test_task.c/h` + `gd5f_test.h` | GD5F2GQ5UE SPI NAND 存储 |
|
||||
| `sd2506_test_task.c/h` + `sd2506_test.h` | SD2506 RTC |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file ch395f_reuse_bench.c
|
||||
* @brief N-30 芯片级 Socket 复用时序台架(裸机,内核前运行,OS/net/FTP 全剥离)
|
||||
* @brief relisten 沉降段原语化受阻的芯片级 Socket 复用时序台架(裸机,内核前运行,OS/net/FTP 全剥离)
|
||||
* @details 直接调用 ch395f_* 命令,循环执行受控时序矩阵(延迟/关闭次序/载荷/Socket 号),
|
||||
* 每步打印芯片状态与中断寄存器,定位"关→开→监听→连接"复用时序中
|
||||
* 芯片行为的确切规律。配套主机脚本 test/host/ch395f_reuse_host.py。
|
||||
@@ -10,7 +10,7 @@
|
||||
* 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、
|
||||
* Src/main.c 的 USER CODE 2 区在 app_main_init() 之前调 ch395f_reuse_bench()
|
||||
* (内核前裸机循环,永不返回,断电退出),烧录后跑主机脚本对照芯片日志。
|
||||
* 旧 N-30 定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/
|
||||
* 早先那轮定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/
|
||||
* bench_round(),由 s_bench_legacy=0 停泊不运行。
|
||||
*
|
||||
* 驱动原语前置实验:同一固件合并跑 E2/E3/E4——
|
||||
@@ -395,7 +395,7 @@ static void bench_round(unsigned idx) {
|
||||
* 驱动原语前置实验区:E2 / E3 / E4 一次固件合并跑
|
||||
* ===================================================================== */
|
||||
|
||||
/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */
|
||||
/* 早先那轮定案矩阵的停泊开关:0 = 不运行(保留代码备查,勿删) */
|
||||
static const int s_bench_legacy = 0;
|
||||
|
||||
/* --- DWT 周期计数器:168 MHz,1 cycle ~= 5.95 ns(不依赖 HAL 时基/中断) --- */
|
||||
@@ -825,7 +825,7 @@ void ch395f_reuse_bench(void) {
|
||||
if (s_bench_legacy != 0) {
|
||||
/* 丢中断复核实验(2026-10-14):固定 8 周期 × 6 变体后返回正常启动
|
||||
* (主机脚本 ch395f_reuse_host.py --mode legacy --cycles 8 同步收工) */
|
||||
DBG_INFO("BENCH legacy N-30 matrix: 8 cycles x %u variants", (unsigned)BENCH_N_VARS);
|
||||
DBG_INFO("BENCH legacy matrix: 8 cycles x %u variants", (unsigned)BENCH_N_VARS);
|
||||
unsigned idx = 0;
|
||||
for (unsigned cyc = 0U; cyc < 8U; cyc++) {
|
||||
for (unsigned iv = 0U; iv < (unsigned)BENCH_N_VARS; iv++) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* ch395f_reuse_bench.h - N-30 芯片级 Socket 复用时序台架(裸机实验,临时分支专用)
|
||||
* ch395f_reuse_bench.h - relisten 沉降段原语化受阻的芯片级 Socket 复用时序台架(裸机实验,临时分支专用)
|
||||
*/
|
||||
|
||||
#ifndef CH395F_REUSE_BENCH_H
|
||||
|
||||
@@ -86,7 +86,7 @@ extern "C" {
|
||||
* test/run_all_suites.py 的 _rewrite_config 只改 TEST_SUITE_* 行(那些行没有行尾
|
||||
* 跨行注释)所以一直没炸;本文件是"人手工切阶段"的入口,必须自身安全。
|
||||
*
|
||||
* 链接余量构建实测(条目属主 docs/任务规划/测试与架构专项.md P1-10;2026-10-25,
|
||||
* 链接余量构建实测(条目属主 docs/任务规划/测试与架构专项.md;2026-10-25,
|
||||
* RW_IRAM1 = 128 KB 的 .sct 区,92% 红线 = 120,586 B):
|
||||
* 配置(TEST_SUITE_CH395F + 阶段) Code ZI RW_IRAM1 距红线余量
|
||||
* 产品基线(阶段全关) 214,104 131,852 75,632 57.7% 44,954 B
|
||||
|
||||
@@ -46,7 +46,7 @@ test_stats_t g_test_stats;
|
||||
* 测试目的:确认 CH395F 与 MCU 之间的 SPI 通信链路正常,所有底层命令
|
||||
* 能够正确发送并得到预期回复。这是其他所有 Phase 的前提条件。
|
||||
*
|
||||
* 共 9 项测试(P1-01 ~ P1-09),逐项执行,汇总报告。
|
||||
* 共 9 项测试,逐项执行,汇总报告。
|
||||
* =================================================================== */
|
||||
|
||||
/* 解析点分十进制 IP 字符串为数组(避免引入 sscanf 库)。
|
||||
@@ -79,12 +79,12 @@ static void phase1_run(void) {
|
||||
|
||||
/* 全局中断寄存器读即清,唯一读者 = net_poll(F-NET-7;用户裁定 2026-10-13:
|
||||
* 测试代码同样禁自行读取)。net_stack_bring_up() 的 set_phy 强制重协商会
|
||||
* 锁存 GINT_STAT_PHY_CHANGE(0x0004)——由 P1-06 内部双读自清,不在此预清。 */
|
||||
* 锁存 GINT_STAT_PHY_CHANGE(0x0004)——由全局中断状态项内部双读自清,不在此预清。 */
|
||||
|
||||
DBG_INFO("=== CH395F Phase 1 Tests ===");
|
||||
DBG_INFO("--- 9 test items (P1-01 ~ P1-09) ---");
|
||||
DBG_INFO("--- 9 test items ---");
|
||||
|
||||
/* ---- P1-01: ch395f_check_exist() ----
|
||||
/* ---- ch395f_check_exist() ----
|
||||
* 原理:SPI 写入命令 0x06 (CMD_CHECK_EXIST) 后跟测试字节 0x57。
|
||||
* CH395F 硬件将测试字节按位取反后返回,期望收到 ~0x57 = 0xA8。
|
||||
* 通过:函数返回 CH395F_ERR_SUCCESS(0x00)= SPI 回显取反匹配。
|
||||
@@ -95,15 +95,15 @@ static void phase1_run(void) {
|
||||
*/
|
||||
{
|
||||
ch395f_status_t ret = ch395f_check_exist();
|
||||
DBG_INFO(" [P1-01] ch395f_check_exist()");
|
||||
DBG_INFO(" ch395f_check_exist()");
|
||||
DBG_INFO(" SPI write: CMD=0x06, DATA=0x57");
|
||||
DBG_INFO(" expect: ~0x57 = 0xA8");
|
||||
DBG_INFO(" actual: 0x%02X", (uint8_t)ret);
|
||||
TEST_CHECK(ret == CH395F_ERR_SUCCESS,
|
||||
"P1-01 check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret);
|
||||
"check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret);
|
||||
}
|
||||
|
||||
/* ---- P1-02: ch395f_get_version() ----
|
||||
/* ---- ch395f_get_version() ----
|
||||
* 原理:SPI 写入命令 0x27 (CMD_GET_IC_VER),读取芯片版本字节。
|
||||
* 通过:版本值 == 0x4A(CH395F 硬件版本)。
|
||||
* 失败:版本值 ≠ 0x4A(芯片型号不匹配、未正确初始化或损坏)。
|
||||
@@ -111,14 +111,14 @@ static void phase1_run(void) {
|
||||
*/
|
||||
{
|
||||
uint8_t ver = (uint8_t)ch395f_get_version();
|
||||
DBG_INFO(" [P1-02] ch395f_get_version()");
|
||||
DBG_INFO(" ch395f_get_version()");
|
||||
DBG_INFO(" expect: 0x4A");
|
||||
DBG_INFO(" actual: 0x%02X", ver);
|
||||
TEST_CHECK(ver == 0x4A,
|
||||
"P1-02 version = 0x%02X (expect 0x4A)", ver);
|
||||
"version = 0x%02X (expect 0x4A)", ver);
|
||||
}
|
||||
|
||||
/* ---- P1-03: 设置 IP → 回读验证 ----
|
||||
/* ---- 设置 IP → 回读验证 ----
|
||||
* 原理:ch395f_set_ip_addr() 将 IP 写入 CH395F 内部寄存器,
|
||||
* 然后通过 ch395f_get_ip_inf() 回读完整网络信息(IP+GW+MASK)。
|
||||
* 前 4 字节为 IP 地址。
|
||||
@@ -132,15 +132,15 @@ static void phase1_run(void) {
|
||||
ch395f_get_ip_inf(ip_info);
|
||||
int32_t match = (ip_info[0] == 192 && ip_info[1] == 168 &&
|
||||
ip_info[2] == 1 && ip_info[3] == 100);
|
||||
DBG_INFO(" [P1-03] ch395f_set_ip_addr(192.168.1.100)");
|
||||
DBG_INFO(" ch395f_set_ip_addr(192.168.1.100)");
|
||||
DBG_INFO(" expect: IP[0..3] = 192.168.1.100");
|
||||
DBG_INFO(" actual: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
|
||||
TEST_CHECK(match, "P1-03 IP = %d.%d.%d.%d (expect 192.168.1.100)",
|
||||
TEST_CHECK(match, "IP = %d.%d.%d.%d (expect 192.168.1.100)",
|
||||
ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
|
||||
}
|
||||
|
||||
/* ---- P1-04: 设置网关 → 回读验证 ----
|
||||
* 原理:同 P1-03,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf()
|
||||
/* ---- 设置网关 → 回读验证 ----
|
||||
* 原理:同「设置 IP → 回读」项,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf()
|
||||
* 回读的第 5-8 字节为网关地址。
|
||||
* 通过:回读 GW[0..3] == 192.168.1.1。
|
||||
* 失败:任意字节不匹配。
|
||||
@@ -152,15 +152,15 @@ static void phase1_run(void) {
|
||||
ch395f_get_ip_inf(ip_info);
|
||||
int32_t match = (ip_info[4] == 192 && ip_info[5] == 168 &&
|
||||
ip_info[6] == 1 && ip_info[7] == 1);
|
||||
DBG_INFO(" [P1-04] ch395f_set_gwip_addr(192.168.1.1)");
|
||||
DBG_INFO(" ch395f_set_gwip_addr(192.168.1.1)");
|
||||
DBG_INFO(" expect: GW[0..3] = 192.168.1.1");
|
||||
DBG_INFO(" actual: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
|
||||
TEST_CHECK(match, "P1-04 GW = %d.%d.%d.%d (expect 192.168.1.1)",
|
||||
TEST_CHECK(match, "GW = %d.%d.%d.%d (expect 192.168.1.1)",
|
||||
ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
|
||||
}
|
||||
|
||||
/* ---- P1-05: 设置掩码 → 回读验证 ----
|
||||
* 原理:同 P1-03,ch395f_set_mask_addr() 写入,ch395f_get_ip_inf()
|
||||
/* ---- 设置掩码 → 回读验证 ----
|
||||
* 原理:同「设置 IP → 回读」项,ch395f_set_mask_addr() 写入,ch395f_get_ip_inf()
|
||||
* 回读的第 9-12 字节为子网掩码。
|
||||
* 通过:回读 MASK[0..3] == 255.255.255.0。
|
||||
* 失败:任意字节不匹配。
|
||||
@@ -172,14 +172,14 @@ static void phase1_run(void) {
|
||||
ch395f_get_ip_inf(ip_info);
|
||||
int32_t match = (ip_info[8] == 255 && ip_info[9] == 255 &&
|
||||
ip_info[10] == 255 && ip_info[11] == 0);
|
||||
DBG_INFO(" [P1-05] ch395f_set_mask_addr(255.255.255.0)");
|
||||
DBG_INFO(" ch395f_set_mask_addr(255.255.255.0)");
|
||||
DBG_INFO(" expect: MASK[0..3] = 255.255.255.0");
|
||||
DBG_INFO(" actual: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]);
|
||||
TEST_CHECK(match, "P1-05 MASK = %d.%d.%d.%d (expect 255.255.255.0)",
|
||||
TEST_CHECK(match, "MASK = %d.%d.%d.%d (expect 255.255.255.0)",
|
||||
ip_info[8], ip_info[9], ip_info[10], ip_info[11]);
|
||||
}
|
||||
|
||||
/* ---- P1-06: ch395f_get_glob_int_status_all()(16 位版) ----
|
||||
/* ---- ch395f_get_glob_int_status_all()(16 位版) ----
|
||||
* 全库唯一允许 net_poll 之外读全局寄存器的位置——被测对象就是该 API 本身
|
||||
* (硬件层模式独占上下文,无并发消费者)。读即清语义 → 先读一次自清
|
||||
* (吸收 bring_up 锁存的 PHY_CHANGE),短暂延时后第二次读为断言值。
|
||||
@@ -194,14 +194,14 @@ static void phase1_run(void) {
|
||||
(void)ch395f_get_glob_int_status_all(); /* 自清读(被测 API 自身,见上) */
|
||||
HAL_Delay(10);
|
||||
uint16_t gint_all = ch395f_get_glob_int_status_all();
|
||||
DBG_INFO(" [P1-06] ch395f_get_glob_int_status_all()");
|
||||
DBG_INFO(" ch395f_get_glob_int_status_all()");
|
||||
DBG_INFO(" expect: 0x0000");
|
||||
DBG_INFO(" actual: 0x%04X", gint_all);
|
||||
TEST_CHECK(gint_all == 0x0000,
|
||||
"P1-06 glob_int_status_all = 0x%04X (expect 0x0000)", gint_all);
|
||||
"glob_int_status_all = 0x%04X (expect 0x0000)", gint_all);
|
||||
}
|
||||
|
||||
/* ---- P1-07: 打开→关闭 Socket 0 → 验证 CMD_STATUS ----
|
||||
/* ---- 打开→关闭 Socket 0 → 验证 CMD_STATUS ----
|
||||
* 原理:连续执行 open_socket(0) 和 close_socket(0),然后读取
|
||||
* 命令状态寄存器,确认 Socket 状态机正常流转。
|
||||
* 通过:CMD_STATUS == 0x00(CH395F_ERR_SUCCESS),表示两次命令均成功执行。
|
||||
@@ -211,16 +211,16 @@ static void phase1_run(void) {
|
||||
uint8_t open_ret = ch395f_open_socket(0);
|
||||
uint8_t close_ret = ch395f_close_socket(0);
|
||||
uint8_t cmd_st = ch395f_get_cmd_status();
|
||||
DBG_INFO(" [P1-07] ch395f_open_socket(0) -> ch395f_close_socket(0)");
|
||||
DBG_INFO(" ch395f_open_socket(0) -> ch395f_close_socket(0)");
|
||||
DBG_INFO(" expect: CMD_STATUS == 0x00 (CH395F_ERR_SUCCESS)");
|
||||
DBG_INFO(" actual: open=%d, close=%d, CMD_STATUS=0x%02X",
|
||||
open_ret, close_ret, cmd_st);
|
||||
TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
|
||||
"P1-07 open=%d close=%d cmd=0x%02X (expect 0x00)",
|
||||
"open=%d close=%d cmd=0x%02X (expect 0x00)",
|
||||
open_ret, close_ret, cmd_st);
|
||||
}
|
||||
|
||||
/* ---- P1-08: 设置 ARP 参数 ----
|
||||
/* ---- 设置 ARP 参数 ----
|
||||
* 原理:ch395f_set_arp(period, retry) 配置 ARP 请求周期和重试次数。
|
||||
* period = 10(×100ms = 1s)、retry = 5(最多 5 次重试)。
|
||||
* 通过:CMD_STATUS == 0x00(参数写入成功)。
|
||||
@@ -229,14 +229,14 @@ static void phase1_run(void) {
|
||||
{
|
||||
ch395f_set_arp(10, 5);
|
||||
uint8_t cmd_st = ch395f_get_cmd_status();
|
||||
DBG_INFO(" [P1-08] ch395f_set_arp(period=10, retry=5)");
|
||||
DBG_INFO(" ch395f_set_arp(period=10, retry=5)");
|
||||
DBG_INFO(" expect: CMD_STATUS == 0x00");
|
||||
DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st);
|
||||
TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
|
||||
"P1-08 set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
|
||||
"set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
|
||||
}
|
||||
|
||||
/* ---- P1-09: 设置 TTL ----
|
||||
/* ---- 设置 TTL ----
|
||||
* 原理:ch395f_set_ttl(sock, ttl) 设置指定 Socket 的 IP 报 TTL 值。
|
||||
* sock = 0(Socket 0)、ttl = 64(默认值)。
|
||||
* 通过:CMD_STATUS == 0x00。
|
||||
@@ -245,11 +245,11 @@ static void phase1_run(void) {
|
||||
{
|
||||
ch395f_set_ttl(0, 64);
|
||||
uint8_t cmd_st = ch395f_get_cmd_status();
|
||||
DBG_INFO(" [P1-09] ch395f_set_ttl(sock=0, ttl=64)");
|
||||
DBG_INFO(" ch395f_set_ttl(sock=0, ttl=64)");
|
||||
DBG_INFO(" expect: CMD_STATUS == 0x00");
|
||||
DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st);
|
||||
TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
|
||||
"P1-09 set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
|
||||
"set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
|
||||
}
|
||||
|
||||
TEST_REPORT("Phase 1");
|
||||
@@ -1029,7 +1029,7 @@ static void phase2_run(void)
|
||||
*
|
||||
* 前置条件:PC 运行 udp_client 发包到 192.168.1.100:60000
|
||||
* 会话结构:累计 PHASE3_TOTAL_ROUNDS(60) 个有效数据包后关闭 Socket
|
||||
* 第 1~30 轮 = TC-301(PC 发 64B);第 31~60 轮 = TC-302(1B/64B/200B)
|
||||
* 第 1~30 轮 = TC-NET-301(PC 发 64B);第 31~60 轮 = TC-NET-302(1B/64B/200B)
|
||||
* "HELLO"(5B) 为 PC 就绪探针:照常回显但不计入轮次
|
||||
* 通过:60/60 有效轮回显成功。
|
||||
* 失败:任意轮回显超时或解析错误。
|
||||
@@ -1037,7 +1037,7 @@ static void phase2_run(void)
|
||||
|
||||
#ifdef ENABLE_PHASE3_TESTS
|
||||
|
||||
/* UDP 有效回显轮数:1~30 = TC-301(64B),31~60 = TC-302(变长)。
|
||||
/* UDP 有效回显轮数:1~30 = TC-NET-301(64B),31~60 = TC-NET-302(变长)。
|
||||
* "HELLO" 探针只回显不计数,与 test/ch395f_socket_test.py udp_client 约定 */
|
||||
#ifndef PHASE3_TOTAL_ROUNDS
|
||||
#define PHASE3_TOTAL_ROUNDS 60
|
||||
@@ -1329,7 +1329,7 @@ phase4_end:
|
||||
#define PHASE5_TOTAL_BYTES (256*1024) /* 回传总量 */
|
||||
#endif
|
||||
#ifndef PHASE5_CHUNK_SIZE
|
||||
#define PHASE5_CHUNK_SIZE 4093 /* 单块 = socket 默认发送缓冲,DP-04 边界 */
|
||||
#define PHASE5_CHUNK_SIZE 4093 /* 单块 = socket 默认发送缓冲,DMA 边界安全 */
|
||||
#endif
|
||||
#ifndef PHASE5_CONN_TIMEOUT_MS
|
||||
#define PHASE5_CONN_TIMEOUT_MS 15000
|
||||
@@ -1452,7 +1452,7 @@ static void phase5_run(void) {
|
||||
/* ---- 发送缓冲空闲:生成并写入一块 ---- */
|
||||
phase5_gen_block(tx, (uint32_t)blk_tx); /* 确定性生成第 blk_tx 块模式(TX/RX 可复现) */
|
||||
sum_tx += phase5_sum32(tx, PHASE5_CHUNK_SIZE); /* 先累加发送侧总账(与 RX 侧对账用) */
|
||||
ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入(= 发送缓冲上限,DP-04 边界) */
|
||||
ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入(= 发送缓冲上限,DMA 边界安全) */
|
||||
blk_tx++; /* 该块已交出 */
|
||||
can_write = 0; /* 进入等待 SENBUF_FREE 状态 */
|
||||
tx_sw = HAL_GetTick(); /* 记录等待起点(供超时判定) */
|
||||
@@ -1520,7 +1520,7 @@ static void phase5_run(void) {
|
||||
break; /* 缓冲空,跳出排空循环 */
|
||||
}
|
||||
if (rlen > PHASE5_CHUNK_SIZE) {
|
||||
rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小(DP-04 4KB DMA 边界) */
|
||||
rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小(DMA 边界安全,4KB) */
|
||||
}
|
||||
ch395f_read_recv_buf(sock, rx, rlen); /* 一次 SPI 事务读走 rlen 字节 */
|
||||
{
|
||||
|
||||
@@ -822,7 +822,7 @@ static void gd5f_phase11(void)
|
||||
|
||||
blk = gd5f_find_run(1);
|
||||
if (blk >= GD5F_TOTAL_BLOCKS) {
|
||||
GD5F_TEST_CHECK(0, "TC-GD5F-1100 no usable block for repro");
|
||||
GD5F_TEST_CHECK(0, "no usable block for repro");
|
||||
GD5F_TEST_REPORT("GD5F Phase 11");
|
||||
return;
|
||||
}
|
||||
@@ -873,7 +873,7 @@ static void gd5f_phase11(void)
|
||||
round_bad = 1;
|
||||
if (first_bad < 0) {
|
||||
first_bad = round;
|
||||
DBG_INFO("TC-GD5F-1101 r=%d p=%u IMM mismatch head=%02X %02X %02X %02X "
|
||||
DBG_INFO("r=%d p=%u IMM mismatch head=%02X %02X %02X %02X "
|
||||
"tail=%02X %02X %02X %02X",
|
||||
round, (unsigned)s_p11_probe[pi],
|
||||
s_rbuf[0], s_rbuf[1], s_rbuf[2], s_rbuf[3],
|
||||
@@ -913,18 +913,18 @@ static void gd5f_phase11(void)
|
||||
imm_bad++;
|
||||
}
|
||||
if (round == 1 || (round & 3) == 0) {
|
||||
DBG_INFO("TC-GD5F-1101 progress r=%d/%d storm_fail=%d imm_fail=%d ctl_fail=%d",
|
||||
DBG_INFO("progress r=%d/%d storm_fail=%d imm_fail=%d ctl_fail=%d",
|
||||
round, GD5F_P11_ROUNDS, storm_fail, imm_fail, ctl_fail);
|
||||
}
|
||||
}
|
||||
|
||||
GD5F_TEST_CHECK(storm_fail == 0,
|
||||
"TC-GD5F-1100 storm ops: erase/program all OK (fail=%d)", storm_fail);
|
||||
"storm ops: erase/program all OK (fail=%d)", storm_fail);
|
||||
GD5F_TEST_CHECK(imm_bad == 0,
|
||||
"TC-GD5F-1101 immediate read after storm: %d/%d bad rounds (pages=%d first=%d)",
|
||||
"immediate read after storm: %d/%d bad rounds (pages=%d first=%d)",
|
||||
imm_bad, GD5F_P11_ROUNDS, imm_fail, first_bad);
|
||||
GD5F_TEST_CHECK(ctl_fail == 0,
|
||||
"TC-GD5F-1102 delayed(50ms) read: %d/%d bad rounds (pages=%d)",
|
||||
"delayed(50ms) read: %d/%d bad rounds (pages=%d)",
|
||||
ctl_bad, GD5F_P11_ROUNDS, ctl_fail);
|
||||
GD5F_TEST_REPORT("GD5F Phase 11");
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ void net_latency_phase12_run(void)
|
||||
(void)ch395f_check_exist();
|
||||
break;
|
||||
default:
|
||||
/* 例外(同 P1-06):被测对象就是本 API 的 SPI 时延——硬件层
|
||||
/* 例外(同 Phase 1 全局中断状态用例):被测对象就是本 API 的 SPI 时延——硬件层
|
||||
* 独占上下文、无并发消费者;除此之外测试代码禁读全局寄存器
|
||||
* (读即清,唯一读者 = net_poll,F-NET-7) */
|
||||
(void)ch395f_get_glob_int_status_all();
|
||||
@@ -515,7 +515,7 @@ void net_latency_phase12_run(void)
|
||||
* 段定义(说明文档 §1):
|
||||
* L5 queue_rt_recv / queue_rt_send —— 调用方视角 net_recv/net_send
|
||||
* 全程(入队 + netTask 调度 + 处理 + 通知),静默基线档;
|
||||
* 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → E-02 复现)
|
||||
* 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → 头阻塞复现)
|
||||
* L6 req2first_mcu —— 请求收齐 → 应答发送完成(MCU 侧,含队列+1 chunk)
|
||||
* L7 fullframe_mcu —— 9B LCDMEM 请求 → 3209B 全帧发送完成
|
||||
* (netTask 内 4 次分块停止等待,= 生产路径发送段)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
/*
|
||||
* 缓冲区声明前置于全部 Phase 代码:声明必须早于任何使用点(Phase 6
|
||||
* 即首次使用 s_rx_buf)。P1-9 修复:原声明位于文件后段(Phase 8 注释块
|
||||
* 即首次使用 s_rx_buf)。历史修复:原声明位于文件后段(Phase 8 注释块
|
||||
* 之后),phase6_run 使用先于声明 → ARMCC #20 identifier undefined,
|
||||
* 全部 NET 层测试配置不可编译。声明条件按各 Phase 实际用缓冲精确门控:
|
||||
* s_rx_buf —— Phase 6(Echo)/9(Select) 恒用,
|
||||
|
||||
@@ -329,7 +329,7 @@ static void storage_test_name_validation(void)
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DESTRUCTIVE_TESTS
|
||||
/* TC-STORAGE-06: 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱)
|
||||
/* 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱)
|
||||
* 本移植 FA_OPEN_APPEND 不含 FA_WRITE → 不带写位打开 f_write 必返 FR_DENIED;
|
||||
* 追加写必须 FA_WRITE|FA_OPEN_APPEND。另 f_write 簇耗尽返 FR_OK+短写(无错误
|
||||
* 码),判成败必须校验写回字节数。锁定这两条移植专有语义防回归。
|
||||
@@ -339,17 +339,17 @@ static void storage_test_open_modes(void)
|
||||
UINT bw;
|
||||
FRESULT r;
|
||||
|
||||
DBG_INFO("=== Storage Test: TC-STORAGE-06 Open-mode semantics ===");
|
||||
DBG_INFO("=== Storage Test: Open-mode semantics ===");
|
||||
|
||||
if (f_mount(&fs, "", 1) != FR_OK) {
|
||||
STORAGE_TEST_CHECK(0, "TC-STORAGE-06 mount");
|
||||
STORAGE_TEST_CHECK(0, "mount");
|
||||
return;
|
||||
}
|
||||
(void)f_unlink("mode.tmp");
|
||||
|
||||
/* 1) 建一个含初始内容的文件 */
|
||||
r = f_open(&fil, "mode.tmp", FA_CREATE_ALWAYS | FA_WRITE);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 create base file");
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "create base file");
|
||||
if (r == FR_OK) {
|
||||
f_write(&fil, "abc", 3, &bw);
|
||||
f_close(&fil);
|
||||
@@ -357,36 +357,36 @@ static void storage_test_open_modes(void)
|
||||
|
||||
/* 2) FA_OPEN_APPEND(无 FA_WRITE)→ 写必返 FR_DENIED */
|
||||
r = f_open(&fil, "mode.tmp", FA_OPEN_APPEND);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open append(no write)");
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "open append(no write)");
|
||||
if (r == FR_OK) {
|
||||
FRESULT wr = f_write(&fil, "xyz", 3, &bw);
|
||||
STORAGE_TEST_CHECK(wr == FR_DENIED,
|
||||
"TC-STORAGE-06 append-no-write must DENY (fr=%d)", (int)wr);
|
||||
"append-no-write must DENY (fr=%d)", (int)wr);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
/* 3) FA_WRITE|FA_OPEN_APPEND → 追加成功且字节数正确 */
|
||||
r = f_open(&fil, "mode.tmp", FA_WRITE | FA_OPEN_APPEND);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open write|append");
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "open write|append");
|
||||
if (r == FR_OK) {
|
||||
(void)f_write(&fil, "xyz", 3, &bw);
|
||||
STORAGE_TEST_CHECK(bw == 3, "TC-STORAGE-06 append byte-count (%u)", bw);
|
||||
STORAGE_TEST_CHECK(bw == 3, "append byte-count (%u)", bw);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
/* 4) 只读打开后写 → 必返 FR_DENIED(读句柄不可写) */
|
||||
r = f_open(&fil, "mode.tmp", FA_READ);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open read-only");
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "open read-only");
|
||||
if (r == FR_OK) {
|
||||
FRESULT wr = f_write(&fil, "q", 1, &bw);
|
||||
STORAGE_TEST_CHECK(wr == FR_DENIED,
|
||||
"TC-STORAGE-06 read-only write must DENY (fr=%d)", (int)wr);
|
||||
"read-only write must DENY (fr=%d)", (int)wr);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
(void)f_unlink("mode.tmp");
|
||||
}
|
||||
#endif /* ENABLE_DESTRUCTIVE_TESTS: TC-STORAGE-06 */
|
||||
#endif /* ENABLE_DESTRUCTIVE_TESTS: 打开模式语义 */
|
||||
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS: 共享缓冲 + 正常测试函数 */
|
||||
|
||||
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 模块名称:板端测试套件公共断言宏
|
||||
* 模块功能:为 test/board/ 下的各测试套件提供**语义统一**的统计结构体与断言/
|
||||
* 报告宏内核,消除 4 套件(CH395F / STORAGE / GD5F / RTC)之间逐字重复的
|
||||
* 宏定义(2026-10-01 测试架构评审 T-4)。
|
||||
* 宏定义(2026-10-01 测试架构评审)。
|
||||
*
|
||||
* 设计取舍:**统计变量仍按套件独立**(各套件持有自己的 `g_xxx_stats`),
|
||||
* 公共宏只提供"内核",由各套件用一行宏把它绑定到自己的统计变量。
|
||||
|
||||
@@ -47,7 +47,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* 派生标记:任一套件被选中时置位。**2026-10-04 起有唯一消费点**——`Src/freertos.c`
|
||||
* 的 MX_FREERTOS_Init() 用它门控 testTask 的回收(P2-13):产品构建下 CubeMX 生成区
|
||||
* 的 MX_FREERTOS_Init() 用它门控 testTask 的回收:产品构建下 CubeMX 生成区
|
||||
* 无条件创建 testTask,随后本标记未定义 → 立即 vTaskDelete 回收其 8KB CCM 栈 + TCB,
|
||||
* 避免空转占堆。testTask 的调度见 test/board/test_config.c 的 start_test_task(),
|
||||
* 后者**直接按 TEST_SUITE_RTC 等宏分支**,与本标记无关——故"RTC 构建下 testTask 不被
|
||||
|
||||
+9
-9
@@ -9,8 +9,8 @@
|
||||
| 脚本 | 作用 |
|
||||
|---|---|
|
||||
| `run_boot_test.py` | **`@test` 一键闭环**:开串口+清日志 → 烧录 → wait-idle → `verify_log` 校验(退出码 0=PASS) |
|
||||
| `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区(T-2) |
|
||||
| `e02_probe.py` | E-02 专项探针(历史审计编号) |
|
||||
| `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区 |
|
||||
| `e02_probe.py` | 头阻塞专项探针 |
|
||||
|
||||
### 网络 / FTP
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
| `e6_soak.py` | E6 24h 挂机驱动(常态 + 每小时/每 4h E1 压测注入;判据读串口 `U15 probe` 计数) |
|
||||
| `pasv_probe.py` | PASV 数据通道时序探针 |
|
||||
| `ch395f_reuse_host.py` | Socket 复用/中止形态 PC 对端(bench 配套) |
|
||||
| `ftp_auth_bench.py` | FTP 鉴权/锁定台架腿(N-19) |
|
||||
| `ftp_auth_bench.py` | FTP 鉴权/锁定台架腿 |
|
||||
| `iec104_master.py` | **IEC 60870-5-104 主站客户端(B6/B7)**:连设备 :2404 跑 STARTDT → 总召(actcon/四组背景扫描/actterm)→ 经 C_SE 两段写临时压低 OC1 定值确定性触发 YX16 变位(COT=3)→ 恢复 → YK11 两段式 → C_RD 读回;**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、定值与压板态跨复位保持(含非默认 7.5 往返);纯标准库,输出纯 ASCII 可直接重定向归档为台架证据(判据 exit 0/1/2) |
|
||||
|
||||
### 时间 / 时延
|
||||
@@ -62,11 +62,11 @@
|
||||
| `vol_lock/run_unit_test.py` | **卷锁单测**:离线编译真实 `App/fs/vol_lock.c`,33 断言点覆盖创建 / 获取 / 释放契约(**fail-open 不变量**:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传 |
|
||||
| `vol_lock/vol_lock_test.c` | 测试单元本体(`#include` 生产源码 + CMSIS 互斥桩) |
|
||||
| `vol_lock/stub/` | 桩头文件(`cmsis_os.h`) |
|
||||
| `time_sync/run_unit_test.py` | **时间同步单测**:离线编译真实 `App/sync/time_sync.c`,80 断言点覆盖分片喂狗延时 / 监听退避 / 时间包重组(半包·粘连·EOF·真错·预算耗尽·tick 回绕)/ 任务主循环(**N-17 的全部逻辑贡献者**) |
|
||||
| `time_sync/run_unit_test.py` | **时间同步单测**:离线编译真实 `App/sync/time_sync.c`,80 断言点覆盖分片喂狗延时 / 监听退避 / 时间包重组(半包·粘连·EOF·真错·预算耗尽·tick 回绕)/ 任务主循环(**探针窗口心跳缺口的全部逻辑贡献者**) |
|
||||
| `time_sync/time_sync_test.c` | 测试单元本体(`#include` 生产源码 + 网络 / 事件 / RTOS 可脚本化桩 + `longjmp` 跳出 `for(;;)` 任务循环) |
|
||||
| `time_sync/stub/` | 桩头文件(`FreeRTOS.h` / `task.h` / `cmsis_os2.h` / `net_socket.h` / `sys_clock.h` / `sys_events.h` / `dbg_log.h`) |
|
||||
| `iec104/run_unit_test.py` | **IEC 60870-5-104 协议栈单测(B4)**:离线编译真实 `App/protocol/iec104/` 三模块 + 数据源(prot_set/prot_elm/phasor/sample_ring),**82** 用例覆盖 CP56 往返 / APCI 半帧·粘帧·坏帧 / STARTDT 握手 / 总召分片+RBT / COV 死区 / k=12 窗满 / w=8 触发 S / t1·t2·t3 / N(R) 倒退判故障 / 遥控·定值两段式与 PN / C_CS / 未知类型·地址错误应答 |
|
||||
| `nand_port/run_unit_test.py` | **dhara NAND 移植层单测(N-21)**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 `Lib/dhara/{journal,map}.c`,以 **RAM 假芯片**驱动,**149** 断言覆盖 is_free 整页扫描(N-12)/ 六个 HAL 入口的越界守卫(N-13)/ 故障注入 / dhara map"写入 → 重新 resume"场景 |
|
||||
| `nand_port/run_unit_test.py` | **dhara NAND 移植层单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 `Lib/dhara/{journal,map}.c`,以 **RAM 假芯片**驱动,**149** 断言覆盖 is_free 整页扫描 / 六个 HAL 入口的越界守卫 / 故障注入 / dhara map"写入 → 重新 resume"场景 |
|
||||
| `nand_port/nand_port_test.c` | 测试单元本体(`#include` 生产源码 + RAM 假芯片;内联一份"只看页首 64 B"的**旧算法对照**,证明断言能咬人) |
|
||||
| `nand_port/stub/` | 桩头文件(`gd5f2gq5ue.h` 芯片几何与 SPI 原语声明 / `dbg_log.h` 日志宏计数) |
|
||||
|
||||
@@ -86,11 +86,11 @@
|
||||
原语打交道——把它们换成一块 RAM 假芯片,整层即可离线跑。而它要防的两类缺陷恰恰是
|
||||
板端**拿不到观测面**的:
|
||||
|
||||
- **N-12**:`is_free` 旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判
|
||||
- `is_free` 旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判
|
||||
空闲 → dhara 往已编程页再编程 = 静默数据丢失。真机要恰好撞上那种页形态才发作,
|
||||
发作后只表现为"某次复位后丢数据"。假芯片把**双编程做成可数事件**(违规计数),
|
||||
测试还内联一份旧算法做对照:同一页旧算法答"空闲"、新实现答"非空"。
|
||||
- **N-13**:越界页/块号(含 `DHARA_PAGE_NONE` 哨兵)旧会被地址位截断成**真实页**
|
||||
- 越界页/块号(含 `DHARA_PAGE_NONE` 哨兵)旧会被地址位截断成**真实页**
|
||||
→ 静默读写/搬走无辜数据。真机上没有任何路径会主动喂进越界号。假芯片记录**全部
|
||||
访问次数**,于是"越界 → 返回 -1 + `DHARA_E_CORRUPT_MAP` 且零芯片访问、零标坏块"
|
||||
可机械断言。
|
||||
@@ -101,7 +101,7 @@
|
||||
# @test 一键闭环(推荐,需真机)
|
||||
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py
|
||||
|
||||
# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0(T-2)
|
||||
# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0
|
||||
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py `
|
||||
--required "=== Storage Tests:" --forbidden "ERROR;HardFault;[ERR]" `
|
||||
--require-suite-summary "Storage Tests"
|
||||
@@ -157,5 +157,5 @@
|
||||
- `docs/测试说明/ADC输入层单元测试说明.md` — ADC 输入层单测(逐 Rank 钉死映射)
|
||||
- `docs/测试说明/相量层单元测试说明.md` — 相量层单测(傅氏系数 / 双区交换)
|
||||
- `docs/测试说明/卷锁单元测试说明.md` — 卷锁单测(fail-open 不变量)
|
||||
- `docs/测试说明/时间同步单元测试说明.md` — 时间同步单测(N-17 逻辑贡献者)
|
||||
- `docs/测试说明/时间同步单元测试说明.md` — 时间同步单测(探针窗口心跳缺口逻辑贡献者)
|
||||
- `docs/测试说明/CH395F_Test_Guide.md`、`FTP_Test_Guide.md`
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
* TC-ADCIN-021..025 取到新帧:返回 0 / 状态码 / 不计失败 / DMA 只挂一次 / seq=1
|
||||
* TC-ADCIN-026..02B **Rank -> 通道号映射**:UB->CH14(rank9) / UC->CH3(rank4) /
|
||||
* IB->CH6(rank7) / IC->CH1(rank2) / IN->CH2(rank3) / TEMP->CH5(rank6)
|
||||
* TC-ADCIN-02C stm32_take 不置 TPAFE 有效标志(两来源独立)
|
||||
* TC-ADCIN-02c stm32_take 不置 TPAFE 有效标志(两来源独立)
|
||||
* TC-ADCIN-080..089 **乒乓双缓冲**:消费侧读"刚完成的半区",即使另一半正被 DMA
|
||||
* 改写也不得读到(撕裂帧防线);两拍交替半区、seq 推进;
|
||||
* 超拍未消费 -> 记一次丢帧(gap),与停摆(err)分开计数
|
||||
@@ -42,7 +42,7 @@
|
||||
* (别的句柄不得污染本层 seq / hw_errs)
|
||||
* TC-ADCIN-030..034 HAL_ADC_Start_DMA 失败:init 返回 -2 / 状态码 RC_START / 计数 +1
|
||||
* TC-ADCIN-035..039 本拍无新帧:返回 RC_STALL / **单拍不计故障**(相位伪影)/ seq 不前进
|
||||
* TC-ADCIN-03A..03E 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈
|
||||
* TC-ADCIN-03a..03e 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈
|
||||
* TC-ADCIN-070..078 **停摆拍不得沿用上一拍旧值**(2026-10-10 补,真机实测发现的
|
||||
* 正确性缺陷;DMA 版改由"无新帧"触发同一路径):先成功一拍让
|
||||
* STM32 侧进入有效态,再注入无新帧拍 -> adc_input_raw() 必须返 -2;
|
||||
@@ -220,10 +220,10 @@ static void tc_init(void) {
|
||||
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 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(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 after init neither TPAFE nor STM32 side holds valid data");
|
||||
@@ -263,7 +263,7 @@ static void tc_desc(void) {
|
||||
"TC-ADCIN-018 TEMP -> STM32 CH5");
|
||||
|
||||
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");
|
||||
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01a negative index -> NULL");
|
||||
}
|
||||
|
||||
/* ===================== TC-ADCIN-020:map_check ===================== */
|
||||
@@ -296,10 +296,10 @@ static void tc_take_ok(void) {
|
||||
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_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 does not set the TPAFE valid flag");
|
||||
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:乒乓双缓冲(读刚完成半区,不读正在写的半区) ============
|
||||
@@ -316,25 +316,25 @@ static void tc_dma_pingpong(void) {
|
||||
dma_fill(1u, 900u); /* 模拟 DMA 正在写后半区(值 900 系) */
|
||||
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 reads the just completed half (108), not the half being written");
|
||||
"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 second round fetch succeeded");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "second round fetch succeeded");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208),
|
||||
"TC-ADCIN-083 second round reads the later half (208)");
|
||||
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA completion counter = 2");
|
||||
"second round reads the later half (208)");
|
||||
CHECK(adc_input_stm32_seq() == 2u, "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 stays consistent within the frame (203)");
|
||||
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "UC stays consistent within the frame (203)");
|
||||
|
||||
/* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记 1 点丢帧 */
|
||||
dma_beat(300);
|
||||
dma_beat(400);
|
||||
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)");
|
||||
CHECK(adc_input_stm32_gaps() == 1u, "seq advanced by 2 -> records 1 dropped frame");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "what came back is the newest frame (408)");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "a drop is not counted as a stall (two fault classes stay apart)");
|
||||
|
||||
/* 第二档:上面只跨了 1 帧(前进 2 记 1 点),"次数"与"点数"数值相同、钉不出单位。
|
||||
* 这里连投三帧不消费 = 一次跨 2 帧,必须记 2 点(累计 1+2=3),把 2026-10-21 改的
|
||||
@@ -358,15 +358,15 @@ static void tc_hw_errs(void) {
|
||||
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 other ADC handles must not count (hadc guard)");
|
||||
CHECK(adc_input_stm32_hw_errs() == 2u, "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 a completion callback from another handle must not advance seq");
|
||||
CHECK(adc_input_stm32_seq() == 0u, "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 and errs count separately and never interfere");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs does not pollute errs");
|
||||
CHECK(adc_input_stm32_hw_errs() == 3u, "hw_errs and errs count separately and never interfere");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "hw_errs does not pollute errs");
|
||||
}
|
||||
|
||||
/* ===================== TC-ADCIN-030..034:DMA 挂载失败 ===================== */
|
||||
@@ -378,8 +378,8 @@ static void tc_init_dma_fail(void) {
|
||||
s_start_dma_rc = HAL_ERROR;
|
||||
|
||||
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_stm32_rc() == ADC_INPUT_RC_START, "status code = RC_START");
|
||||
CHECK(adc_input_stm32_errs() == 1u, "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");
|
||||
}
|
||||
|
||||
@@ -393,20 +393,20 @@ static void tc_take_stall(void) {
|
||||
/* 没有任何 DMA 完成回调 = TIM3 未启动 / ADC 停摆 / DMA 出错 */
|
||||
|
||||
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_stm32_rc() == ADC_INPUT_RC_STALL, "status code = RC_STALL");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "a single round without a new frame is not a fault (phase artefact)");
|
||||
CHECK(adc_input_stm32_seq() == 0u, "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 two rounds in a row with no new frame -> stall count = 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 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)");
|
||||
CHECK(adc_input_stm32_errs() == 2u, "an ongoing stall accumulates every round = 2");
|
||||
CHECK(adc_input_stm32_gaps() == 0u, "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 after recovery the fetch succeeds");
|
||||
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E the recovery round advances by 1 -> not a drop");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "after recovery the fetch succeeds");
|
||||
CHECK(adc_input_stm32_gaps() == 0u, "the recovery round advances by 1 -> not a drop");
|
||||
}
|
||||
|
||||
/* ============ TC-ADCIN-070..078:停摆拍不得沿用上一拍旧值(陈旧帧) ============
|
||||
@@ -425,23 +425,23 @@ static void tc_take_stale(void) {
|
||||
(void)adc_input_init();
|
||||
dma_beat(100);
|
||||
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)");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "precondition: UB reads 108 (valid state)");
|
||||
|
||||
/* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */
|
||||
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)");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "second round has no new frame");
|
||||
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "a stalled round must not reuse the previous value -> -2");
|
||||
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "a stalled round must not emit any other STM32 channel either");
|
||||
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "a stalled round must not emit TEMP either -> -2");
|
||||
|
||||
dma_beat(200);
|
||||
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)");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "third round recovers successfully");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "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 after an arming failure no value may be read -> -2");
|
||||
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "after an arming failure no value may be read -> -2");
|
||||
}
|
||||
|
||||
/* ===================== TC-ADCIN-040..045:raw 参数守卫与无数据 ===================== */
|
||||
@@ -482,9 +482,9 @@ static void tc_raw_tpafe(void) {
|
||||
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_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 (two's-complement negative kept as is)");
|
||||
"TC-ADCIN-04b IA -> buf[2]=-1234 (two's-complement negative kept as is)");
|
||||
}
|
||||
|
||||
/* ===================== TC-ADCIN-050..058:poll 契约 ===================== */
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* ⚠ 仅供 test/host/adc_input/ 使用,不参与 MCU 构建。
|
||||
*
|
||||
* 被测代码只消费 tpafe5160_snapshot()(E-58 原子快照),故桩只保留该声明;
|
||||
* 被测代码只消费 tpafe5160_snapshot()(原子快照),故桩只保留该声明;
|
||||
* 其返回值语义(1=新帧 / 0=无新帧 / -1=参数错)与真实驱动一致,由测试单元
|
||||
* 给强符号桩并按用例注入。
|
||||
*
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
* TC-CONFIG-021 非法键定义拒绝(UINT len 非 1/2/4、MAC len≠6、STR len>79)
|
||||
* TC-CONFIG-022 段名超长拒绝(≥ CFG_NAME_MAX)
|
||||
* TC-CONFIG-023 挂回调段 data_size > CFG_SNAP_MAX 拒绝
|
||||
* TC-CONFIG-024 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受
|
||||
* 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受
|
||||
* —— load 契约(cfg_file_load)——
|
||||
* TC-CONFIG-030 文件缺失 -> 生成出厂模板写盘 + 返回 0
|
||||
* TC-CONFIG-031 文件存在 -> 解析有效键进 RAM 副本
|
||||
@@ -51,12 +51,12 @@
|
||||
* TC-CONFIG-041 外科手术:保留注释/未知段/未知键,受管键原位改写
|
||||
* TC-CONFIG-042 缺失受管段/键补写
|
||||
* TC-CONFIG-043 输出超长 -> 拒绝保存(-1) 且原文件不动
|
||||
* TC-CONFIG-044 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动(P2-1);
|
||||
* 非受管键的超长行照旧放过(原样保留)
|
||||
* 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动;
|
||||
* 非受管键的超长行照旧放过(原样保留)
|
||||
* —— poll 契约(cfg_file_poll)——
|
||||
* TC-CONFIG-050 首轮建立基线(不重载)
|
||||
* TC-CONFIG-051 沉降去抖:连续 2 次稳定才重载
|
||||
* TC-CONFIG-052 与基线一致不重载
|
||||
* 与基线一致不重载
|
||||
* TC-CONFIG-053 变更后重载 + 挂回调段差异触发回调
|
||||
* TC-CONFIG-054 溢出到文件超长 -> 工厂默认 + 重建模板 + 自检留痕(不触发回调)
|
||||
*
|
||||
@@ -549,10 +549,10 @@ static void tc_parse(void) {
|
||||
"baud = 9600\r\n");
|
||||
|
||||
CHECK(cfg_file_load() == 0, "load returns 0");
|
||||
CHECK(s_net_data.u8 == 42u, "TC-010/013: comment skipped, key parsed, ws trimmed");
|
||||
CHECK(s_net_data.u8 == 42u, "TC-CONFIG-010/013: comment skipped, key parsed, ws trimmed");
|
||||
CHECK(s_net_data.ip[0] == 10u && s_net_data.ip[3] == 1u, "ip parsed");
|
||||
CHECK(s_net_data.u16 == s_net_def.u16, "TC-014: absent key keeps default (u16)");
|
||||
CHECK(s_net_data.name[0] == 'd', "TC-014: unknown key leaves default (name)");
|
||||
CHECK(s_net_data.u16 == s_net_def.u16, "TC-CONFIG-014: absent key keeps default (u16)");
|
||||
CHECK(s_net_data.name[0] == 'd', "TC-CONFIG-014: unknown key leaves default (name)");
|
||||
CHECK(s_sys_data.baud == 9600u, "multi-section: sys parsed");
|
||||
}
|
||||
|
||||
@@ -689,7 +689,7 @@ static void tc_register(void) {
|
||||
CHECK(cfg_file_register(&r) == 0, "same section without callback accepted");
|
||||
}
|
||||
|
||||
printf("[TC-CONFIG-024] key name length limit\n");
|
||||
printf("key name length limit\n");
|
||||
{
|
||||
cfg_section_reg_t r;
|
||||
static const cfg_key_def_t long_key[] = {
|
||||
@@ -838,7 +838,7 @@ static void tc_save(void) {
|
||||
}
|
||||
}
|
||||
|
||||
printf("[TC-CONFIG-044] over-long managed key line -> reject (P2-1)\n");
|
||||
printf("over-long managed key line -> reject\n");
|
||||
{
|
||||
char buf[512];
|
||||
size_t n;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 模块功能:在 PC 上离线验证 App/cfg/cfg_file_store_mcu.c 的五个卷操作 + 三个堆操作:
|
||||
* cfg_store_read / cfg_store_write_atomic / cfg_store_stat /
|
||||
* cfg_store_remove / 两个路径 getter / alloc-free-heap_free
|
||||
* 重心放在**原子写**与它的 v1.2 修复(E-01)上:本 FatFs 移植的 f_rename
|
||||
* 重心放在**原子写**与它的 v1.2 修复上:本 FatFs 移植的 f_rename
|
||||
* 对已存在目标返回 FR_EXIST、**不覆盖**,所以"覆盖写"必须先 f_unlink 目标
|
||||
* 再 f_rename——少了这一步,一切非首启保存(UI 改配置、模板重建)静默失败,
|
||||
* 而模拟器后端(POSIX rename 覆盖语义)照不出来。
|
||||
@@ -11,12 +11,12 @@
|
||||
* 被测对象:真实源文件 App/cfg/cfg_file_store_mcu.c(#include 原样纳入)。
|
||||
* 假件与桩:
|
||||
* test/host/common/fatfs_fake.{h,c} —— 假 FatFs 内存卷(真实 ff.h 声明;
|
||||
* 它的 f_rename **如实返回 FR_EXIST**,正是 E-01 的那颗雷)
|
||||
* 它的 f_rename **如实返回 FR_EXIST**,正是原子写那颗雷)
|
||||
* stub/FreeRTOS.h —— 三个分配器原型(签名逐条对齐 portable.h:163/164/166)
|
||||
* 假卷锁 / 假堆(可注入分配失败)写在本文件里
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只靠台架):
|
||||
* 1. **E-01 原子写**:unlink→rename 这个非原子窗口是"掉电时配置暂失、开机按首启
|
||||
* 1. **原子写**:unlink→rename 这个非原子窗口是"掉电时配置暂失、开机按首启
|
||||
* 重建工厂模板"的有意取舍。任何一次"顺手优化"(比如给 rename 加覆盖、或把
|
||||
* unlink 挪到 rename 之后)都会让配置保存静默失败或留下脏 TMP,而这在 POSIX
|
||||
* 后端上完全看不出来。→ 假卷把 FR_EXIST 原样喂进来。
|
||||
@@ -44,7 +44,7 @@
|
||||
* —— 原子写 ——
|
||||
* TC-CFGFST-010 首启写:TMP 建 -> 写 -> unlink(目标,FR_NO_FILE 容忍) -> rename
|
||||
* TC-CFGFST-011 **覆盖写必须先 unlink 目标**:假卷 rename 对已存在目标返 FR_EXIST,
|
||||
* 少了 unlink 就是 -1 且旧内容原封不动(E-01 的直接复现)
|
||||
* 少了 unlink 就是 -1 且旧内容原封不动(原子写缺陷的直接复现)
|
||||
* TC-CFGFST-012 rename 失败 -> -1 + TMP 清理 + 目标内容保持不动
|
||||
* TC-CFGFST-013 TMP 打开失败 -> -1、零 write、release
|
||||
* TC-CFGFST-014 写短(FR_OK + 字节数不足)-> -1 + TMP 被删 + 目标原内容不动
|
||||
@@ -326,7 +326,7 @@ static void tc_write_atomic(void)
|
||||
size_t n;
|
||||
int ok;
|
||||
|
||||
printf("-- TC-CFGFST-010..020 atomic write (core of E-01) --\n");
|
||||
printf("-- TC-CFGFST-010..020 atomic write (core regression) --\n");
|
||||
|
||||
/* 010 首启写(目标不存在) */
|
||||
reset_all();
|
||||
@@ -339,7 +339,7 @@ static void tc_write_atomic(void)
|
||||
&& (s_lock_releases == 1) && (s_lock_seq_err == 0),
|
||||
"TC-CFGFST-010 first write: TMP -> rename -> target 2 bytes, no TMP left behind");
|
||||
|
||||
/* 011 覆盖写:假卷的 f_rename 对已存在目标返回 FR_EXIST —— 这就是 E-01 */
|
||||
/* 011 覆盖写:假卷的 f_rename 对已存在目标返回 FR_EXIST —— 这就是原子写的雷 */
|
||||
reset_all();
|
||||
ok = (cfg_store_write_atomic(P_CFG, (const uint8_t *)"old-content", 11u) == 0);
|
||||
{
|
||||
@@ -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 overwrite: unlink target exactly once then rename -> new content lands (E-01)");
|
||||
"TC-CFGFST-011 overwrite: unlink target exactly once then rename -> new content lands (atomic-write trap)");
|
||||
}
|
||||
/* 同一条的"反面":直接 rename 到已存在目标必败(证明上一步的 unlink 不是多余的) */
|
||||
{
|
||||
|
||||
@@ -5,12 +5,12 @@ r"""配置存储后端(cfg_file_store_mcu)的主机侧单元测试运行器
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c(以
|
||||
#include 纳入**生产源** App/cfg/cfg_file_store_mcu.c,并纳入共享假卷
|
||||
test/host/common/fatfs_fake.c),在 PC 上离线跑读、原子写(E-01 的
|
||||
test/host/common/fatfs_fake.c),在 PC 上离线跑读、原子写(
|
||||
unlink->rename 全链)、状态令牌、删除、路径 8.3 合规、假堆分配,失败即非 0 退出。
|
||||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||||
|
||||
为什么要主机侧单测(而不是只用台架):
|
||||
1. **E-01 原子写**:本仓 FatFs 移植的 f_rename 对**已存在目标返回 FR_EXIST、不
|
||||
1. **原子写**:本仓 FatFs 移植的 f_rename 对**已存在目标返回 FR_EXIST、不
|
||||
覆盖**,所以覆盖写必须先 unlink 目标。模拟器后端(POSIX rename 覆盖语义)
|
||||
永远照不出这一条 → 曾经"一切非首启保存静默失败"。假卷按真实 ff.c 返回
|
||||
FR_EXIST,把这条做成必然失败的断言。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# N-30 芯片级复用时序台架:主机端(与 test/board/ch395f_reuse_bench.c 共用变体表)
|
||||
# relisten 沉降段原语化受阻:芯片级复用时序台架主机端(与 test/board/ch395f_reuse_bench.c 共用变体表)
|
||||
# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4]
|
||||
# legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。
|
||||
# e4: E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接——
|
||||
@@ -130,7 +130,7 @@ def main():
|
||||
ap.add_argument("--mode", choices=["legacy", "e4"], default="legacy")
|
||||
ap.add_argument("--rounds", type=int, default=10, help="e4: rounds per variant")
|
||||
ap.add_argument("--first-budget", type=int, default=300,
|
||||
help="e4: connect budget for round 0 (bench runs E2/E3 first)")
|
||||
help="e4: connect budget for the first round (bench runs E2/E3 first)")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.mode == "e4":
|
||||
|
||||
@@ -513,12 +513,12 @@ def udp_client_test(args):
|
||||
|
||||
会话结构与固件 phase3_run() 约定:
|
||||
- "HELLO" 探针:回显即就绪,不计入固件轮次
|
||||
- TC-301:30 轮 64B 基础回显(--size 非默认值时覆盖单轮载荷大小)
|
||||
- TC-302:1B/64B/200B 各 --count 轮变长合规(默认 10)
|
||||
- TC-NET-301:30 轮 64B 基础回显(--size 非默认值时覆盖单轮载荷大小)
|
||||
- TC-NET-302:1B/64B/200B 各 --count 轮变长合规(默认 10)
|
||||
任一轮 mismatch/timeout 立即终止;结束输出汇总,失败退出码 1。
|
||||
"""
|
||||
log_info("=" * 60)
|
||||
log_info("UDP Echo 测试(TC-301 基础回显 + TC-302 变长合规)")
|
||||
log_info("UDP Echo 测试(TC-NET-301 基础回显 + TC-NET-302 变长合规)")
|
||||
log_info("=" * 60)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
@@ -546,11 +546,11 @@ def udp_client_test(args):
|
||||
|
||||
sock.settimeout(5)
|
||||
|
||||
# Phase B: TC-301 基础回显 30 轮 x 64B
|
||||
# Phase B: TC-NET-301 基础回显 30 轮 x 64B
|
||||
size_b = 64 if args.size == 1024 else min(args.size, 200)
|
||||
rounds_b = 30
|
||||
log_info("-" * 40)
|
||||
log_info(f"Phase B: TC-301 基础回显({rounds_b} 轮 x {size_b}B)")
|
||||
log_info(f"Phase B: TC-NET-301 基础回显({rounds_b} 轮 x {size_b}B)")
|
||||
ok_b = 0
|
||||
for i in range(rounds_b):
|
||||
td = generate_data(size_b)
|
||||
@@ -558,23 +558,23 @@ def udp_client_test(args):
|
||||
sock.sendto(td, dest)
|
||||
data, _ = sock.recvfrom(size_b + 16)
|
||||
if data != td:
|
||||
log_error(f"[TC-301] #{i+1} mismatch: {len(data)}B != {len(td)}B")
|
||||
log_error(f"[TC-NET-301] #{i+1} mismatch: {len(data)}B != {len(td)}B")
|
||||
break
|
||||
except socket.timeout:
|
||||
log_error(f"[TC-301] #{i+1} echo timeout")
|
||||
log_error(f"[TC-NET-301] #{i+1} echo timeout")
|
||||
break
|
||||
ok_b += 1
|
||||
if ok_b % 10 == 0:
|
||||
log_info(f"[TC-301] 进度 {ok_b}/{rounds_b}")
|
||||
log_info(f"[TC-NET-301] 进度 {ok_b}/{rounds_b}")
|
||||
time.sleep(0.02)
|
||||
|
||||
# Phase C: TC-302 变长合规(仅 Phase B 全过后继续,否则后续必超时)
|
||||
# Phase C: TC-NET-302 变长合规(仅 Phase B 全过后继续,否则后续必超时)
|
||||
sizes_c = [1, 64, 200]
|
||||
total_c = len(sizes_c) * args.count
|
||||
ok_c = 0
|
||||
if ok_b == rounds_b:
|
||||
log_info("-" * 40)
|
||||
log_info(f"Phase C: TC-302 变长合规({sizes_c}B 各 {args.count} 轮)")
|
||||
log_info(f"Phase C: TC-NET-302 变长合规({sizes_c}B 各 {args.count} 轮)")
|
||||
for sz in sizes_c:
|
||||
ok_sz = 0
|
||||
for i in range(args.count):
|
||||
@@ -583,14 +583,14 @@ def udp_client_test(args):
|
||||
sock.sendto(td, dest)
|
||||
data, _ = sock.recvfrom(sz + 16)
|
||||
if data != td:
|
||||
log_error(f"[TC-302] {sz}B #{i+1} mismatch: {len(data)}B != {sz}B")
|
||||
log_error(f"[TC-NET-302] {sz}B #{i+1} mismatch: {len(data)}B != {sz}B")
|
||||
break
|
||||
except socket.timeout:
|
||||
log_error(f"[TC-302] {sz}B #{i+1} echo timeout")
|
||||
log_error(f"[TC-NET-302] {sz}B #{i+1} echo timeout")
|
||||
break
|
||||
ok_sz += 1
|
||||
time.sleep(0.02)
|
||||
log_info(f"[TC-302] {sz}B: {ok_sz}/{args.count}")
|
||||
log_info(f"[TC-NET-302] {sz}B: {ok_sz}/{args.count}")
|
||||
ok_c += ok_sz
|
||||
if ok_sz < args.count:
|
||||
break
|
||||
@@ -598,9 +598,9 @@ def udp_client_test(args):
|
||||
# 汇总与退出码
|
||||
log_info("=" * 60)
|
||||
if ok_b == rounds_b and ok_c == total_c:
|
||||
log_success(f"PASS: TC-301 {ok_b}/{rounds_b}, TC-302 {ok_c}/{total_c}")
|
||||
log_success(f"PASS: TC-NET-301 {ok_b}/{rounds_b}, TC-NET-302 {ok_c}/{total_c}")
|
||||
else:
|
||||
log_error(f"FAIL: TC-301 {ok_b}/{rounds_b}, TC-302 {ok_c}/{total_c}")
|
||||
log_error(f"FAIL: TC-NET-301 {ok_b}/{rounds_b}, TC-NET-302 {ok_c}/{total_c}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
@@ -874,7 +874,7 @@ def _build_dhcp_pkt(op, xid, client_mac, yiaddr, siaddr, options):
|
||||
|
||||
|
||||
def _announce_listener(results):
|
||||
"""后台监听 MCU 的 4B IP 广播宣告(UDP :60001,TC-602 步1 的 PC 侧观测)"""
|
||||
"""后台监听 MCU 的 4B IP 广播宣告(UDP :60001,TC-NET-402 步1 的 PC 侧观测)"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
@@ -1026,7 +1026,7 @@ def dhcp_server_test(args):
|
||||
|
||||
print()
|
||||
|
||||
# 汇总宣告监听结果(TC-602 步 1 PC 侧观测;join 延迟由固件 30s HELLO 窗覆盖)
|
||||
# 汇总宣告监听结果(TC-NET-402 步 1 PC 侧观测;join 延迟由固件 30s HELLO 窗覆盖)
|
||||
listener.join(timeout=10)
|
||||
if results.get('announce'):
|
||||
if results['announce'] != offer_ip:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|---|---|
|
||||
| `unit_test_harness.py` | **通用单测 harness**(纯标准库):探测本机 MSVC → 拼 `INCLUDE`/`LIB` → 编译测试单元 → 运行 → 判定退出码 |
|
||||
|
||||
## 为什么要有这层(2026-10-01 抽取,T-3)
|
||||
## 为什么要有这层(2026-10-01 抽取)
|
||||
|
||||
看门狗单测最早的运行器(`test/host/watchdog/run_unit_test.py`)把工具链探测、
|
||||
`INCLUDE`/`LIB` 拼接、GBK 解码、`/wd4819` 抑制、退出码契约**全写死在模块目录内**。
|
||||
|
||||
@@ -1040,7 +1040,7 @@ FRESULT f_rename(const TCHAR *path_old, const TCHAR *path_new)
|
||||
}
|
||||
dup = s_find(nnew);
|
||||
if (dup >= 0) {
|
||||
/* E-01 台架实证的根因:本移植(与上游同语义)"new name already in use"
|
||||
/* 台架实证的根因:本移植(与上游同语义)"new name already in use"
|
||||
* 一律 FR_EXIST、不覆盖。原子写因此必须先 unlink 目标再 rename。 */
|
||||
return FR_EXIST;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
* - f_stat:父目录在而对象不在 → FR_NO_FILE;父目录也不在 → FR_NO_PATH;
|
||||
* - f_unlink 不存在 → FR_NO_FILE;f_mkdir 已存在 → FR_EXIST;
|
||||
* - **f_rename 撞已存在的新名 → FR_EXIST、不覆盖**(ff.c「new name already in
|
||||
* use」,2026-09-22 台架 E-01 暴露的那条:缺了它,一切「目标存在的覆盖写」
|
||||
* use」,2026-09-22 台架暴露的那条:缺了它,一切「目标存在的覆盖写」
|
||||
* 必然失败,而模拟器端 POSIX rename 的覆盖语义照不出来)。不复刻本条 =
|
||||
* cfg_store_write_atomic 里「先 unlink 再 rename」那一步永远测不出必要性;
|
||||
* - f_lseek 越过文件尾:只读句柄**钳到 objsize 且不报错**(ff.c 正常寻址分支),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
被测模块的差异(源文件、桩目录、额外 include、断言汇总行特征)全部由调用方
|
||||
(各模块自己的 `run_unit_test.py`)传入。
|
||||
|
||||
为什么要 harness(2026-10-01 测试架构评审 T-3):
|
||||
为什么要 harness(2026-10-01 测试架构评审):
|
||||
看门狗单测的运行器(`test/host/watchdog/run_unit_test.py`,170 行)把工具链
|
||||
探测、`INCLUDE`/`LIB` 拼接、GBK 解码、`/wd4819` 抑制、退出码契约**全部写死在
|
||||
模块目录内**。第二个 L0.5 单测出现时须整段复制——工具链细节一旦修正(新增 VS
|
||||
@@ -36,7 +36,7 @@
|
||||
summary_marker="=== Task Watchdog Tests:",
|
||||
)))
|
||||
|
||||
设计依据:docs/任务规划/登记簿.md T-3;docs/测试说明/测试开发标准流程.md §4.2
|
||||
设计依据:docs/测试说明/测试开发标准流程.md §4.2
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
* 读后清、300ms 冷却两端、tick==0 也进冷却(哨兵撞值修好后为正向断言)、
|
||||
* 半块 / 已上线仍判失败
|
||||
* 090..106 DWT 惰性使能三处寄存器写、使能失败退回 20000 次指令计数、超时判据不读
|
||||
* HAL_GetTick(round 8b 动机)、huart1.Instance == NULL 短路两路 +
|
||||
* HAL_GetTick(第 8b 轮台架动机)、huart1.Instance == NULL 短路两路 +
|
||||
* "未初始化绝不 reinit"(误自愈修好后为正向断言)+ 证据不蒸发
|
||||
* 110..117 fputc 重定向(未注册直发 / 注册逐字节入环 / ISR 上下文裸发 / 环满时 ISR
|
||||
* 仍不等待)
|
||||
@@ -864,7 +864,7 @@ static void tc_dwt(void)
|
||||
CHECK(con_dwt_reads == 3U,
|
||||
"TC-CONLOG-097 the counted branch never reads CYCCNT (independent of DWT, hence of any time source)");
|
||||
|
||||
printf("-- TC-CONLOG-098..099 the timeout criterion does not use HAL_GetTick (round 8b motivation) --\n");
|
||||
printf("-- TC-CONLOG-098..099 the timeout criterion does not use HAL_GetTick --\n");
|
||||
reset_all();
|
||||
con_uart_txe_stuck = 1; /* tick 冻结在 0,模拟 TIM7 停摆 */
|
||||
con_dwt_step = SystemCoreClock / 10U; /* 一次判据正好 = 100ms 上界 */
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
为什么要主机侧单测(而不是只用台架):
|
||||
本模块的坑全是"异常时刻 + 节奏":ORE 僵尸通道在软复位后毫秒级重锁、TIM7 停摆让
|
||||
HAL 的 tick 超时永不成立(round 8a 台架:唯一喂狗者死等 TXE -> IWDG 误复位)、
|
||||
HAL 的 tick 超时永不成立(第 8a 轮台架:唯一喂狗者死等 TXE -> IWDG 误复位)、
|
||||
背靠背块超时把主循环拉到 450~500ms 饿死喂狗(台架实测的停喂误复位)。这些在台架上
|
||||
要么一年一遇、要么现场已被复位冲掉;把 100/250/300ms 三条时间线做成显式量之后,
|
||||
299ms 与 300ms 的差别就是一行断言。详见
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""E-02 头阻塞专项台架探针(timeSync 侧,A/B 对照用)。
|
||||
"""头阻塞专项台架探针(timeSync 侧,A/B 对照用)。
|
||||
|
||||
场景(历史审计 E-02 的 timeSync 触发形态,属主文档已于 2026-10-15 删除):
|
||||
场景(历史审计的头阻塞触发形态,属主文档已于 2026-10-15 删除):
|
||||
1. 一个"挂起"的对时客户端连上 :8888 后不发任何数据(空闲/故障客户端,
|
||||
常见工况)——旧固件在此触发 30s 阻塞 net_recv 独占 netTask,
|
||||
全体消费者的队列消息停摆最长 30s;
|
||||
|
||||
@@ -31,7 +31,7 @@ def rd(sock):
|
||||
def raw_login(ip, port, timeout=10.0):
|
||||
s = socket.create_connection((ip, port), timeout=timeout)
|
||||
rd(s) # 220 banner
|
||||
# N-19: anonymous login is rejected (530) -> use the factory default account
|
||||
# FTP auth is mandatory: anonymous login is rejected (530) -> use the factory default account
|
||||
s.sendall(b"USER admin\r\n"); rd(s)
|
||||
s.sendall(b"PASS admin\r\n"); rd(s)
|
||||
s.sendall(b"TYPE I\r\n"); rd(s)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""FTP 鉴权台架回归(N-19,需真机在线)
|
||||
"""FTP 鉴权台架回归(需真机在线)
|
||||
|
||||
验证固件侧 lftpd 鉴权集成的完整行为矩阵(主机单测 test/host/ftp_auth 覆盖
|
||||
不到的"协议接线"部分:命令门、530/331/230/550 响应码、跨会话锁定):
|
||||
@@ -65,7 +65,7 @@ def _try_login(host, port, timeout, user, pwd):
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(description="FTP auth bench matrix (N-19)")
|
||||
ap = argparse.ArgumentParser(description="FTP auth bench matrix")
|
||||
ap.add_argument("--host", default="192.168.1.100")
|
||||
ap.add_argument("--port", type=int, default=21)
|
||||
ap.add_argument("--timeout", type=int, default=15)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 包含 dbg_log.h,也没有别的平台依赖。
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只靠台架):
|
||||
* 1. 这个模块是 **FTP 的门禁**(N-19):pwd_hash 存的是 SHA-256(user‖password),
|
||||
* 1. 这个模块是 **FTP 鉴权的门禁**:pwd_hash 存的是 SHA-256(user‖password),
|
||||
* 判定写成大小写敏感就会在"用户手输大写 hex"时把正确口令当错误;写成敏感于
|
||||
* 默认值大小写则会误报"已改口令",让出厂默认口令的启动告警**静默失效**——
|
||||
* 现场表现是"以为改了口令"。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
工具链探测与判定全部委托给 test/host/common/unit_test_harness.py。
|
||||
|
||||
为什么要主机侧单测(而不是只用台架):
|
||||
本模块是 FTP 的门禁(N-19)。判定写成大小写敏感 → 用户手输大写 hex 时正确口令被拒;
|
||||
本模块是 FTP 鉴权的门禁。判定写成大小写敏感 → 用户手输大写 hex 时正确口令被拒;
|
||||
反过来对出厂值敏感 → "已改口令"误报,出厂默认口令的启动告警**静默失效**(现场以为
|
||||
改了口令)。[ftp] 段刻意不挂变更回调(口令下次登录才生效),有人"顺手补上"就会在
|
||||
poll 重载时去重建网络。write_en 出厂 = 1 也是刻意的(FTP 推 config.ini 是既有维护
|
||||
|
||||
@@ -30,7 +30,7 @@ def main(argv=None):
|
||||
ap.add_argument("--host", default="192.168.1.100", help="device IP")
|
||||
ap.add_argument("--port", type=int, default=21, help="FTP control port")
|
||||
ap.add_argument("--timeout", type=int, default=20, help="socket timeout (s)")
|
||||
# N-19(2026-10-11):FTP 已强制鉴权,匿名登录被 530 拒;默认口令 admin/admin
|
||||
# 2026-10-11 起 FTP 强制鉴权:匿名登录被 530 拒;默认口令 admin/admin
|
||||
# (出厂值,见 ftp_cfg.h),改过 [ftp] pwd_hash 后用这两个参数覆盖
|
||||
ap.add_argument("--user", default="admin", help="FTP user ([ftp] user)")
|
||||
ap.add_argument("--pwd", default="admin", help="FTP password")
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
* TC-IEC104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发)
|
||||
* —— 以下为「数据可信」卡 9/卡 11(道D,2026-10-25;板端默认源 = 真实
|
||||
L2 链 ring→phasor→freq→meas,非桩)——
|
||||
* TC-IEC104-025 板端源读快照(024 号被存量 B7 用例的裸标签占用):未发布
|
||||
* TC-IEC104-025 板端源读快照(024 号已归 B7 用例):未发布
|
||||
* IV|NT / 值=基波有效值 / 削顶 CLIPPED
|
||||
* →线上 IV 清零 / 干净路品质 0 / 频率未新鲜 IV
|
||||
* TC-IEC104-026 0x401D 绑频:收敛后 ±0.02 Hz / freq_valid==0 → IV 清零
|
||||
@@ -440,17 +440,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 round trip on 2000-01-01");
|
||||
CHECK((rc == 0) && (back == e1), "TC-IEC104-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 round trip on the leap day 2024-02-29");
|
||||
CHECK((rc == 0) && (back == e2), "TC-IEC104-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-IEC104-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 a value before 2000 is clamped");
|
||||
CHECK(back == e1, "TC-IEC104-001 a value before 2000 is clamped");
|
||||
}
|
||||
|
||||
static void tc_ioa_float(void)
|
||||
@@ -459,12 +459,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 is 3 bytes little endian");
|
||||
&& (b[2] == 0x12u), "TC-IEC104-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 in IEEE754 little endian");
|
||||
&& (b[3] == 0x3Fu), "TC-IEC104-002 float in IEEE754 little endian");
|
||||
iec104_put_float(b, -12.5f);
|
||||
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float round trip");
|
||||
CHECK(iec104_get_float(b) == -12.5f, "TC-IEC104-002 float round trip");
|
||||
}
|
||||
|
||||
static void tc_apci(void)
|
||||
@@ -476,9 +476,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 a half frame triggers nothing");
|
||||
CHECK(cap_u_count(0x0Bu) == 0, "TC-IEC104-003 a half frame triggers nothing");
|
||||
iec104_link_on_rx(&g_lk, &b[3], 3u, now);
|
||||
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 a half frame is reassembled then con");
|
||||
CHECK(cap_u_count(0x0Bu) == 1, "TC-IEC104-003 a half frame is reassembled then con");
|
||||
}
|
||||
{
|
||||
static const uint8_t b[12] = {
|
||||
@@ -489,21 +489,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 a coalesced frame is handled one by one and STOPDT takes effect");
|
||||
"TC-IEC104-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 a bad start byte -> IDLE");
|
||||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-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 a bad length -> IDLE");
|
||||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-003 a bad length -> IDLE");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,10 +512,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-IEC104-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-IEC104-004 STARTDT -> RUN");
|
||||
}
|
||||
|
||||
static void tc_open_ignore(void)
|
||||
@@ -528,11 +528,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 an I frame in OPEN state is ignored");
|
||||
"TC-IEC104-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 after RUN the same ns is answered normally");
|
||||
CHECK(cap_i_count() == 1, "TC-IEC104-005 after RUN the same ns is answered normally");
|
||||
}
|
||||
|
||||
static void tc_interrogation(void)
|
||||
@@ -551,34 +551,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 is sent right away, COT7");
|
||||
&& (c.cot == IEC104_COT_ACTCON), "TC-IEC104-006 actcon is sent right away, COT7");
|
||||
iec104_link_poll(&g_lk, now);
|
||||
CHECK(cap_i_count() == 5, "TC-006 the first poll uses the whole budget of 4 frames");
|
||||
CHECK(cap_i_count() == 5, "TC-IEC104-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 double-point group, first call COT=4");
|
||||
"TC-IEC104-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 single-point group with 29 points");
|
||||
"TC-IEC104-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 group of 8 points, 40 bytes");
|
||||
&& (c.obj_len == 40u), "TC-IEC104-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 value 111.0 with QDS=0");
|
||||
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 an invalid channel sets QDS=IV");
|
||||
"TC-IEC104-006 YC value 111.0 with QDS=0");
|
||||
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-IEC104-006 an invalid channel sets QDS=IV");
|
||||
}
|
||||
CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u),
|
||||
"TC-006 frequency as a single point with SQ=0");
|
||||
"TC-IEC104-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 closes the sequence");
|
||||
&& (c.cot == IEC104_COT_ACTTERM), "TC-IEC104-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 second call COT=2");
|
||||
"TC-IEC104-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);
|
||||
@@ -586,7 +586,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-IEC104-006 RBT -> COT=4");
|
||||
}
|
||||
|
||||
static void tc_cov_yx(void)
|
||||
@@ -598,33 +598,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 the first poll sends nothing spontaneous");
|
||||
CHECK(cap_i_count() == 0, "TC-IEC104-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 a YX16 change of state sends COT3 with TB");
|
||||
&& (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-IEC104-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 the CP56 time tag decodes");
|
||||
CHECK(back == s_epoch_val, "TC-007 the time tag equals the epoch_ms source");
|
||||
"TC-IEC104-007 the CP56 time tag decodes");
|
||||
CHECK(back == s_epoch_val, "TC-IEC104-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 is reset and changes again");
|
||||
&& (c.ioa == 16u) && (c.obj[0] == 0u), "TC-IEC104-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 an invalid clock sets the CP56 IV bit");
|
||||
"TC-IEC104-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 a change while the IV bit is set triggers nothing");
|
||||
CHECK(cap_i_count() == 3, "TC-IEC104-007 a change while the IV bit is set triggers nothing");
|
||||
}
|
||||
|
||||
static void tc_cov_yc(void)
|
||||
@@ -639,7 +639,7 @@ static void tc_cov_yc(void)
|
||||
s_f_yc[0] = 100.4f; /* 死区 0.5 V 内(卡 6 二次值口径:0.5 %×100 V,
|
||||
* 旧拟定码值死区 10 已由「数据可信」卡 6 重定) */
|
||||
iec104_link_poll(&g_lk, now + 100u);
|
||||
CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded");
|
||||
CHECK(cap_i_count() == 0, "TC-IEC104-008 inside the deadband nothing is uploaded");
|
||||
s_f_yc[0] = 101.25f; /* 101.25-100=1.25 > 0.5,第 1 越点 */
|
||||
iec104_link_poll(&g_lk, now + 200u);
|
||||
CHECK(cap_i_count() == 0, "TC-IEC104-008 first deadband crossing only arms (2-point confirm)");
|
||||
@@ -669,12 +669,12 @@ static void tc_k_window(void)
|
||||
for (i = 0; i < 12; i++) {
|
||||
feed_rd(now);
|
||||
}
|
||||
CHECK(cap_i_count() == 12, "TC-009 the k=12 window caps the outstanding frames");
|
||||
CHECK(cap_i_count() == 12, "TC-IEC104-009 the k=12 window caps the outstanding frames");
|
||||
feed_rd(now);
|
||||
CHECK(cap_i_count() == 12, "TC-009 the 13th frame is suppressed by the k window");
|
||||
CHECK(cap_i_count() == 12, "TC-IEC104-009 the 13th frame is suppressed by the k window");
|
||||
feed_s(12u, now); /* 全确认 → 窗开 */
|
||||
feed_rd(now);
|
||||
CHECK(cap_i_count() == 13, "TC-009 sending resumes after the S acknowledgement");
|
||||
CHECK(cap_i_count() == 13, "TC-IEC104-009 sending resumes after the S acknowledgement");
|
||||
}
|
||||
|
||||
static void tc_w_thresh(void)
|
||||
@@ -688,9 +688,9 @@ static void tc_w_thresh(void)
|
||||
for (i = 0; i < 7; i++) {
|
||||
feed_rd(now);
|
||||
}
|
||||
CHECK(cap_has_s(&nr) == 0, "TC-010 after 7 received frames no S has been sent yet");
|
||||
CHECK(cap_has_s(&nr) == 0, "TC-IEC104-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 with nr=8");
|
||||
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-IEC104-010 w=8 -> S with nr=8");
|
||||
}
|
||||
|
||||
static void tc_t2(void)
|
||||
@@ -702,9 +702,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 no S before t2 expires");
|
||||
CHECK(cap_has_s(&nr) == 0, "TC-IEC104-011 no S before t2 expires");
|
||||
iec104_link_poll(&g_lk, now + 10001u);
|
||||
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 S is sent once t2 expires");
|
||||
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-IEC104-011 S is sent once t2 expires");
|
||||
}
|
||||
|
||||
static void tc_t3_t1(void)
|
||||
@@ -714,19 +714,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 no TESTFR before t3 expires");
|
||||
CHECK(cap_u_count(0x43u) == 0, "TC-IEC104-012 no TESTFR before t3 expires");
|
||||
iec104_link_poll(&g_lk, now + 20001u);
|
||||
CHECK(cap_u_count(0x43u) == 1, "TC-012 TESTFR act is sent once t3 expires");
|
||||
CHECK(cap_u_count(0x43u) == 1, "TC-IEC104-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 an unanswered TESTFR hits t1 -> IDLE");
|
||||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-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 after reconnect t3 fires again");
|
||||
CHECK(cap_u_count(0x43u) == 1, "TC-IEC104-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 once con clears the pending frame t1 does not kill it by mistake");
|
||||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-IEC104-012 once con clears the pending frame t1 does not kill it by mistake");
|
||||
}
|
||||
|
||||
static void tc_dc(void)
|
||||
@@ -743,13 +743,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 the select phase answers actcon");
|
||||
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 the select phase forwards the source");
|
||||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-013 the select phase answers actcon");
|
||||
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-IEC104-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 the execute phase answers actcon");
|
||||
&& (s_f_yk_sel == 0u), "TC-IEC104-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);
|
||||
@@ -758,16 +758,16 @@ 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 a mismatching execute phase returns PN");
|
||||
"TC-IEC104-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 a YK without output hardware returns PN in the select phase");
|
||||
"TC-IEC104-013 a YK without output hardware returns PN in the select phase");
|
||||
}
|
||||
|
||||
/* TC-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */
|
||||
/* TC-IEC104-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */
|
||||
static void tc_dc_yk12(void)
|
||||
{
|
||||
uint32_t now = 1000u;
|
||||
@@ -778,21 +778,21 @@ static void tc_dc_yk12(void)
|
||||
|
||||
reset_all(now);
|
||||
handshake(now);
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 soft plates are engaged by default");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-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 the YK12 select phase answers actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the select phase performs no action");
|
||||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-024 the YK12 select phase answers actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-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 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");
|
||||
"TC-IEC104-024 the YK12 execute phase answers actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-IEC104-024 the execute phase drives the soft plate to the released state");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-IEC104-024 set_sealed seals atomically and selfcheck passes");
|
||||
CHECK(prot_set_dirty_peek() == 1u, "TC-IEC104-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);
|
||||
@@ -800,13 +800,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 the engage command executes with actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the soft plate reaches the engaged state");
|
||||
"TC-IEC104-024 the engage command executes with actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-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 an invalid DPC returns PN in the select phase");
|
||||
"TC-IEC104-024 an invalid DPC returns PN in the select phase");
|
||||
}
|
||||
|
||||
static void tc_se(void)
|
||||
@@ -820,27 +820,27 @@ static void tc_se(void)
|
||||
|
||||
reset_all(now);
|
||||
handshake(now);
|
||||
CHECK(addr == 0x8858u, "TC-014 the OC1 current setting communication address");
|
||||
CHECK(addr == 0x8858u, "TC-IEC104-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 selecting a setting answers actcon");
|
||||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-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 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)");
|
||||
"TC-IEC104-014 executing a setting answers actcon");
|
||||
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-IEC104-014 the setting really was written (real prot_set)");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-IEC104-014 sealing after execute keeps selfcheck passing");
|
||||
CHECK(prot_set_dirty_peek() == 1u, "TC-IEC104-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 an out-of-range select phase returns PN");
|
||||
"TC-IEC104-014 an out-of-range select phase returns PN");
|
||||
}
|
||||
|
||||
static void tc_rd(void)
|
||||
@@ -856,20 +856,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 hits a YC group");
|
||||
&& (iec104_get_float(c.obj) == 222.0f), "TC-IEC104-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 hits a setting");
|
||||
"TC-IEC104-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 an unknown IOA answers COT14 plus EVI");
|
||||
"TC-IEC104-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 a local setting item (comm_addr=0) is not addressable (B7)");
|
||||
"TC-IEC104-015 a local setting item (comm_addr=0) is not addressable (B7)");
|
||||
}
|
||||
|
||||
static void tc_cs(void)
|
||||
@@ -888,18 +888,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 syncs the clock with an exact epoch");
|
||||
"TC-IEC104-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-IEC104-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 a clock_sync failure -> PN");
|
||||
"TC-IEC104-016 a clock_sync failure -> PN");
|
||||
}
|
||||
|
||||
static void tc_err_resp(void)
|
||||
@@ -914,13 +914,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 an unknown type answers COT12 plus EVI");
|
||||
&& ((c.oa & 0x80u) != 0u), "TC-IEC104-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 a wrong common address answers COT13 plus EVI");
|
||||
&& ((c.oa & 0x80u) != 0u), "TC-IEC104-017 a wrong common address answers COT13 plus EVI");
|
||||
}
|
||||
|
||||
static void tc_stopdt(void)
|
||||
@@ -934,13 +934,13 @@ static void tc_stopdt(void)
|
||||
handshake(now);
|
||||
feed_u(0x13u, now); /* STOPDT act */
|
||||
CHECK((cap_u_count(0x23u) == 1)
|
||||
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-018 STOPDT con");
|
||||
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-IEC104-018 STOPDT con");
|
||||
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 in STOPPED state the data service is off");
|
||||
CHECK(cap_i_count() == 0, "TC-IEC104-018 in STOPPED state the data service is off");
|
||||
feed_u(0x43u, now);
|
||||
CHECK(cap_u_count(0x83u) == 1, "TC-018 in STOPPED state TESTFR is still answered");
|
||||
CHECK(cap_u_count(0x83u) == 1, "TC-IEC104-018 in STOPPED state TESTFR is still answered");
|
||||
}
|
||||
|
||||
static void tc_seq_err(void)
|
||||
@@ -954,7 +954,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 a sequence error -> IDLE");
|
||||
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-019 a sequence error -> IDLE");
|
||||
}
|
||||
|
||||
static void tc_pts(void)
|
||||
@@ -965,23 +965,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 table size and IOA layout");
|
||||
&& (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-IEC104-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 a valid YC");
|
||||
"TC-IEC104-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 an invalid channel gives IV");
|
||||
CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-IEC104-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 not ready gives NT");
|
||||
CHECK(q == IEC104_QDS_NT, "TC-IEC104-020 not ready gives NT");
|
||||
iec104_pts_yx_read(0u, &d);
|
||||
CHECK(d == IEC104_DIQ_IV, "TC-020 an invalid telemetry point gives IV");
|
||||
CHECK(d == IEC104_DIQ_IV, "TC-IEC104-020 an invalid telemetry point gives IV");
|
||||
CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u),
|
||||
"TC-020 the is_dp boundary");
|
||||
"TC-IEC104-020 the is_dp boundary");
|
||||
s_f_bits = (1uLL << 15);
|
||||
iec104_pts_yx_read(15u, &d);
|
||||
CHECK(d == 1u, "TC-020 a valid telemetry point gives ON");
|
||||
CHECK(d == 1u, "TC-IEC104-020 a valid telemetry point gives ON");
|
||||
}
|
||||
|
||||
static void tc_ts_rp(void)
|
||||
@@ -998,11 +998,11 @@ static void tc_ts_rp(void)
|
||||
n = mk_asdu(a, IEC104_T_C_TS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(0, &c) == 0) && (c.type == 104u) && (c.cot == IEC104_COT_ACTCON),
|
||||
"TC-021 C_TS actcon");
|
||||
"TC-IEC104-021 C_TS actcon");
|
||||
n = mk_asdu(a, IEC104_T_C_RP_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(1, &c) == 0) && (c.type == 105u) && (c.cot == IEC104_COT_ACTCON),
|
||||
"TC-021 C_RP actcon");
|
||||
"TC-IEC104-021 C_RP actcon");
|
||||
}
|
||||
|
||||
static void tc_periodic(void)
|
||||
@@ -1013,10 +1013,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 the cyclic upload takes 2 frames");
|
||||
CHECK(cap_i_count() == 2, "TC-IEC104-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 the cyclic YC group uses COT=1");
|
||||
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 the cyclic frequency point");
|
||||
&& (c.ioa == 0x4011u), "TC-IEC104-022 the cyclic YC group uses COT=1");
|
||||
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-IEC104-022 the cyclic frequency point");
|
||||
}
|
||||
|
||||
static void tc_cov_kfull(void)
|
||||
@@ -1033,11 +1033,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 with k full nothing spontaneous can go out");
|
||||
CHECK(cap_i_count() == 12, "TC-IEC104-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 once the window opens the backlogged event is sent, none lost");
|
||||
&& (c.cot == IEC104_COT_SPONT), "TC-IEC104-023 once the window opens the backlogged event is sent, none lost");
|
||||
}
|
||||
|
||||
/* ========== 卡 9/卡 11:板端默认源(真实 L2 链:ring→phasor→freq→meas) ========== */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""B6 bench leg: IEC 60870-5-104 master client (GI + COV round-trip).
|
||||
|
||||
Task card: docs/任务规划/采集保护收尾.md (B6 DoD); protocol baseline:
|
||||
Historical bench leg; current acceptance index: docs/任务规划/登记簿.md §7.
|
||||
docs/子系统设计/IEC104上行设计说明.md §3/§4. Constants mirror
|
||||
App/protocol/iec104/iec104_asdu.h / iec104_link.h.
|
||||
|
||||
@@ -33,8 +33,8 @@ Usage: python iec104_master.py [--ip 192.168.1.100] [--port 2404]
|
||||
[--addr 0x8858] [--low 0.05]
|
||||
[--restore 10.0]
|
||||
|
||||
B7 bench leg modes (setpoint persistence + global soft plate, task card
|
||||
docs/任务规划/采集保护收尾.md B7; firmware side App/cfg/prot_set_store.c):
|
||||
B7 bench leg modes (setpoint persistence + global soft plate; historical
|
||||
bench leg, firmware side App/cfg/prot_set_store.c):
|
||||
--b7-pre : before reset: mark setpoint 7.5 (C_SE+C_RD), YK12 OFF (two-step)
|
||||
-> low setpoint 0.05 must NOT trip YX16 (plate OUT disables
|
||||
protection); YK12 ON -> YX16 trips (linkage); restore 10.0;
|
||||
|
||||
@@ -19,7 +19,7 @@ Both the serial log and this JSON carry HOST timestamps -> direct correlation.
|
||||
|
||||
Usage:
|
||||
python latency_peer.py --host 192.168.1.100 --port 8080 --json m2_net.json
|
||||
python latency_peer.py --mode net --ftp-stress ... (E-02 head-of-line repro)
|
||||
python latency_peer.py --mode net --ftp-stress ... (head-of-line repro)
|
||||
|
||||
Pure stdlib (socket/json/time/argparse/threading). ASCII output only.
|
||||
"""
|
||||
@@ -61,7 +61,7 @@ REMO_CMD_KEY = 2
|
||||
REMO_CMD_LCDMEM = 3
|
||||
REMO_MEMSIZE = 3200 # 160x160 1bpp
|
||||
REMO_LCDMEM_FRAME = 4 + 4 + REMO_MEMSIZE + 1 # 3209 (header + offset + bitmap + CRC)
|
||||
REMO_CYCLE_DEADLINE_S = 45.0 # per-cycle recv deadline (E-02 stress: up to 30s)
|
||||
REMO_CYCLE_DEADLINE_S = 45.0 # per-cycle recv deadline (head-of-line stress: up to 30s)
|
||||
|
||||
|
||||
def now_ns():
|
||||
@@ -307,13 +307,13 @@ class RemoPeer:
|
||||
"payload_len": len(f["data"])}
|
||||
return ms
|
||||
|
||||
# --- FTP head-of-line stress (E-02) ---
|
||||
# --- FTP head-of-line stress ---
|
||||
def ftp_stress_worker(self, start_evt, stop_evt, hold_s):
|
||||
"""Wait for the stress window to open, then hold an idle FTP login:
|
||||
lftpd read_line -> blocking net_recv(0) inside netTask (old 30s default
|
||||
budget NET_RECV_TIMEOUT_MS, removed in self-heal phase 1.5; today the wait is
|
||||
an L3 pending recv -> this scenario must NOT freeze the queue anymore)
|
||||
-> every queued message (incl. the LCDMEM response) waits behind it (E-02)."""
|
||||
-> every queued message (incl. the LCDMEM response) waits behind it."""
|
||||
if not start_evt.wait(120.0):
|
||||
sys.stdout.write("[peer] ftp stress: window never opened\n")
|
||||
return
|
||||
@@ -362,7 +362,7 @@ class RemoPeer:
|
||||
|
||||
for i in range(self.samples):
|
||||
if self.stress_from is not None and i == self.stress_from:
|
||||
print("[peer] === FTP stress ON (E-02 head-of-line) at cycle %d ===" % i)
|
||||
print("[peer] === FTP stress ON (head-of-line) at cycle %d ===" % i)
|
||||
stress_start.set()
|
||||
time.sleep(0.5) # let the FTP half-login block netTask
|
||||
|
||||
@@ -568,7 +568,7 @@ class Peer:
|
||||
|
||||
def ftp_stress_worker(self, start_evt, stop_evt):
|
||||
"""Hold an idle FTP login: lftpd read_line -> blocking net_recv(0) in
|
||||
netTask (30s timeout) -> all queued messages wait behind it (E-02)."""
|
||||
netTask (30s timeout) -> all queued messages wait behind it."""
|
||||
try:
|
||||
s = socket.create_connection((self.host, 21), timeout=10.0)
|
||||
s.settimeout(10.0)
|
||||
@@ -790,7 +790,7 @@ def main(argv=None):
|
||||
help="max seconds to retry connect (device boots after flash)")
|
||||
ap.add_argument("--json", default=None, help="write JSON report to this path")
|
||||
ap.add_argument("--ftp-stress", action="store_true",
|
||||
help="hold idle FTP login (E-02 head-of-line repro)")
|
||||
help="hold idle FTP login (head-of-line repro)")
|
||||
ap.add_argument("--samples", type=int, default=50,
|
||||
help="remo mode: LCDMEM frame cycles to measure (default 50)")
|
||||
ap.add_argument("--cadence-ms", type=int, default=100,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# N-30 确定性复现:连续快速 STOR(无间隔),统计 425/超时失败次数
|
||||
# relisten 沉降段原语化受阻的确定性复现:连续快速 STOR(无间隔),统计 425/超时失败次数
|
||||
# 用法: python n30_repro.py [--host IP] [--user U --pwd P] [--n 10] [--size 1024]
|
||||
# 退出码: 0=全部成功 / 1=有失败 / 2=连接错误
|
||||
import argparse
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
* 为什么要主机侧单测(而不是只用板端套件):
|
||||
* 移植层(dhara HAL 六个入口)是**纯地址算术 + 边界判定**,与 SPI 时序无关;
|
||||
* 而它要防的两类缺陷恰恰是真机**极难复现**的:
|
||||
* 1. N-12:is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲
|
||||
* 1. is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲
|
||||
* → dhara 往已编程页再编程 = 静默数据丢失。真机上要恰好撞上那种页形态
|
||||
* 才发作,且发作后只表现为"某次复位后丢数据",无从定位。
|
||||
* 2. N-13:越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页
|
||||
* 2. 越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页
|
||||
* → 静默读写/搬走无辜数据。真机上没有任何一条路径会主动喂进越界号,
|
||||
* 只有被污染的 meta 或将来改动才会触发——主机侧一次构造即钉死。
|
||||
* 假芯片把"双编程"做成**可数事件**(N-12 探针),并记录全部访问次数,
|
||||
* 假芯片把"双编程"做成**可数事件**(is_free 精度回归探针),并记录全部访问次数,
|
||||
* 于是"越界不产生任何芯片访问""越界标坏块次数为 0"这类判据可机械断言
|
||||
* ——这是板端永远拿不到的观测面。
|
||||
*
|
||||
@@ -22,10 +22,10 @@
|
||||
*
|
||||
* 用例前缀:TC-NP-NNN(NP = NAND Port);分组:
|
||||
* TC-NANDPORT-000 假芯片自洽(几何 / 上电态)
|
||||
* TC-NANDPORT-100..108 dhara_nand_is_free 整页扫描(N-12)
|
||||
* TC-NANDPORT-110..112 dhara_nand_is_free 越界守卫(N-13)
|
||||
* TC-NANDPORT-100..108 dhara_nand_is_free 整页扫描
|
||||
* TC-NANDPORT-110..112 dhara_nand_is_free 越界守卫
|
||||
* TC-NANDPORT-200..207 dhara_nand_prog(越界 / NULL / 往返 / 双编程探针 / 失败注入)
|
||||
* TC-NANDPORT-300..307 dhara_nand_read(越界 / 越窗 offset+length / 部分读 / ECC / E-42)
|
||||
* TC-NANDPORT-300..307 dhara_nand_read(越界 / 越窗 offset+length / 部分读 / ECC / 读失败保守判据)
|
||||
* TC-NANDPORT-400..405 dhara_nand_copy(src 与 dst 双端守卫 / 正常搬页)
|
||||
* TC-NANDPORT-500..504 dhara_nand_erase(越界块零标坏块 / 整块回 0xFF / 失败注入)
|
||||
* TC-NANDPORT-600..605 dhara_nand_is_bad / dhara_nand_mark_bad(越界保守判坏、BBT 不变)
|
||||
@@ -61,7 +61,7 @@ int g_nandtest_debug_count;
|
||||
* - 写:gd5f_program_load 只写"数据寄存器",gd5f_program_exec 才落到阵列,
|
||||
* 且落盘是 **NOR 叠加**(new = old & written)——真实 NAND 的位语义;
|
||||
* - 严格探针:exec 时若目标页已有非 0xFF 字节,记一次**双编程违规**
|
||||
* (g_fake_double_program_cnt)。这是抓 N-12 回归的核心:任何
|
||||
* (g_fake_double_program_cnt)。这是抓 is_free 整页精度回归的核心:任何
|
||||
* "把已编程页判成空闲"的实现都会让它立刻非 0;
|
||||
* - 擦除:整块回 0xFF;坏块表是 RAM 位图,mark_bad 记录次数与块号序列。
|
||||
*/
|
||||
@@ -93,7 +93,7 @@ static unsigned long g_fake_is_bad_cnt;
|
||||
static unsigned long g_fake_mark_bad_cnt;
|
||||
static unsigned long g_fake_isfree_scan_cnt; /* 64 B 列读次数(is_free 指纹) */
|
||||
|
||||
/* N-12 探针:往已编程页再编程的次数与页号 */
|
||||
/* 双编程探针:往已编程页再编程的次数与页号 */
|
||||
static unsigned long g_fake_double_program_cnt;
|
||||
static uint32_t g_fake_last_double_program_page;
|
||||
|
||||
@@ -191,7 +191,7 @@ int32_t gd5f_program_exec(uint32_t page_addr) {
|
||||
return GD5F_PROGRAM_FAIL;
|
||||
}
|
||||
|
||||
/* 严格语义探针:目标页已有非 0xFF 字节 = 双编程(N-12 的现场证据) */
|
||||
/* 严格语义探针:目标页已有非 0xFF 字节 = 双编程(is_free 误判的现场证据) */
|
||||
for (i = 0; i < FAKE_PAGE_SIZE; i++) {
|
||||
if (s_chip[page_addr][i] != 0xFFu) {
|
||||
g_fake_double_program_cnt++;
|
||||
@@ -423,9 +423,9 @@ static struct dhara_nand s_ut_nand;
|
||||
#define UT_OOB_PAGE (UT_TOTAL_PAGES) /* 第一个越界页 */
|
||||
#define UT_OOB_BLOCK ((dhara_block_t)s_ut_nand.num_blocks)
|
||||
|
||||
/* ===================== 旧算法对照(N-12 的"牙齿") =====================
|
||||
/* ===================== 旧算法对照(is_free 误判的"牙齿") =====================
|
||||
*
|
||||
* N-12 修复**之前**的 dhara_nand_is_free:只读页首 64 B 就回答"空闲"。
|
||||
* 整页精度修复**之前**的 dhara_nand_is_free:只读页首 64 B 就回答"空闲"。
|
||||
* 本函数按当时的形状内联一份(同样经假芯片的两段式读),用来证明本套件**有牙**:
|
||||
* 对同一页,旧算法答 1(误判空闲)、新实现答 0。将来有人把 is_free 改回只看
|
||||
* 页首,TC-NANDPORT-105 立刻变红——而不是"测试跟着实现一起改回去"。
|
||||
@@ -451,7 +451,7 @@ static int old_is_free_first64(const struct dhara_nand *n, dhara_page_t p) {
|
||||
|
||||
/* ===================== TC-NANDPORT-100..108:dhara_nand_is_free(整页扫描) =====================
|
||||
*
|
||||
* N-12:旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的已编程页误判空闲
|
||||
* 旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的已编程页误判空闲
|
||||
* -> dhara 往已编程页再编程 = 数据丢失。本组把"必须扫完整页"钉死,
|
||||
* 并用 TC-NANDPORT-105 的旧算法对照证明这组断言真的能咬人。
|
||||
*/
|
||||
@@ -465,7 +465,7 @@ static void tc_is_free(void) {
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, 0u) == 1, "erased page -> is_free = 1");
|
||||
CHECK(g_fake_page_read_cnt == 1u, "one page_read only (no per-byte commands)");
|
||||
CHECK(g_fake_read_cache_cnt == (FAKE_PAGE_SIZE / 64u),
|
||||
"erased page scans the whole page = 32 column reads (N-12 evidence)");
|
||||
"erased page scans the whole page = 32 column reads (whole-page scan evidence)");
|
||||
CHECK(g_fake_isfree_scan_cnt == (FAKE_PAGE_SIZE / 64u),
|
||||
"64B column-read fingerprint calibrated (proves scenario really calls is_free)");
|
||||
|
||||
@@ -479,16 +479,16 @@ static void tc_is_free(void) {
|
||||
CHECK(g_fake_read_cache_cnt == 1u,
|
||||
"early exit on first chunk = fast path (same cost as the old impl)");
|
||||
|
||||
/* TC-NANDPORT-102:N-12 回归用例
|
||||
/* TC-NANDPORT-102:is_free 整页精度回归用例
|
||||
* 页首 64 B 全 0xFF、第 1000 字节 = 0x00 -> 必须答"非空"。
|
||||
* 旧实现只看页首 64 B 会误判空闲,dhara 遂往这一页再编程。 */
|
||||
printf("[TC-NANDPORT-102] N-12 regression: hole in first 64B, data at byte 1000\n");
|
||||
printf("[TC-NANDPORT-102] is_free regression: hole in first 64B, data at byte 1000\n");
|
||||
fake_chip_reset();
|
||||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||||
page_buf[1000] = 0x00u;
|
||||
fake_poke_page(2u, page_buf, FAKE_PAGE_SIZE);
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, 2u) == 0,
|
||||
"first 64B all 0xFF, byte 1000 = 0x00 -> is_free = 0 (N-12)");
|
||||
"first 64B all 0xFF, byte 1000 = 0x00 -> is_free = 0 (whole-page scan)");
|
||||
CHECK(g_fake_read_cache_cnt > 1u,
|
||||
"scan continues past the first 64B (old impl stopped here = root cause)");
|
||||
|
||||
@@ -525,7 +525,7 @@ static void tc_is_free(void) {
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, 5u) == 0,
|
||||
"new implementation answers not-free -> 0 (opposite verdict = teeth)");
|
||||
|
||||
/* TC-NANDPORT-106:读失败保守判非空(E-42 同族) */
|
||||
/* TC-NANDPORT-106:读失败保守判非空 */
|
||||
printf("[TC-NANDPORT-106] read failure is answered conservatively\n");
|
||||
fake_chip_reset();
|
||||
s_fail_page_read = 1;
|
||||
@@ -534,7 +534,7 @@ static void tc_is_free(void) {
|
||||
fake_chip_reset();
|
||||
s_fail_read_cache = 1;
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, 7u) == 0,
|
||||
"read_from_cache failure -> not free (E-42 family)");
|
||||
"read_from_cache failure -> not free (conservative)");
|
||||
|
||||
/* TC-NANDPORT-107:NULL / num_blocks = 0 的几何守卫 */
|
||||
printf("[TC-NANDPORT-107] NULL geometry / zero blocks guard\n");
|
||||
@@ -557,9 +557,9 @@ static void tc_is_free(void) {
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, 8u) == 1, "after erase -> free (fake chip sane)");
|
||||
}
|
||||
|
||||
/* ===================== TC-NANDPORT-110..112:is_free 越界守卫(N-13) ===================== */
|
||||
/* ===================== TC-NANDPORT-110..112:is_free 越界守卫 ===================== */
|
||||
static void tc_is_free_oob(void) {
|
||||
printf("[TC-NANDPORT-110] is_free out-of-range pages (N-13)\n");
|
||||
printf("[TC-NANDPORT-110] is_free out-of-range pages\n");
|
||||
|
||||
fake_chip_reset();
|
||||
CHECK(dhara_nand_is_free(&s_ut_nand, DHARA_PAGE_NONE) == 0,
|
||||
@@ -605,7 +605,7 @@ static void tc_prog(void) {
|
||||
/* TC-NANDPORT-201:DHARA_PAGE_NONE -> -1 + CORRUPT_MAP + 零写入
|
||||
* 旧实现会把 0xffffffff 加偏移送进 SPI 命令,被地址位截断成某个真实页
|
||||
* -> 静默写坏无辜数据;现在必须在守卫处就挡住。 */
|
||||
printf("[TC-NANDPORT-201] prog DHARA_PAGE_NONE -> -1, zero write (N-13)\n");
|
||||
printf("[TC-NANDPORT-201] prog DHARA_PAGE_NONE -> -1, zero write\n");
|
||||
fake_chip_reset();
|
||||
err = DHARA_E_NONE;
|
||||
CHECK(dhara_nand_prog(&s_ut_nand, DHARA_PAGE_NONE, page_buf, &err) == -1,
|
||||
@@ -746,8 +746,8 @@ static void tc_read(void) {
|
||||
"check_ecc failure -> -1");
|
||||
CHECK(err == DHARA_E_ECC, "check_ecc failure -> DHARA_E_ECC");
|
||||
|
||||
/* TC-NANDPORT-307:E-42 同族——read_from_cache 失败必须 -1(旧实现返回 0 交垃圾) */
|
||||
printf("[TC-NANDPORT-307] E-42: read_from_cache failure must not report success\n");
|
||||
/* TC-NANDPORT-307:同一保守判据——read_from_cache 失败必须 -1(旧实现返回 0 交垃圾) */
|
||||
printf("[TC-NANDPORT-307] read_from_cache failure must not report success\n");
|
||||
prep_content_page(page_buf, 21u);
|
||||
s_fail_read_cache = 1;
|
||||
err = DHARA_E_NONE;
|
||||
@@ -933,7 +933,7 @@ static void tc_bad_block(void) {
|
||||
* 用真实 dhara_map_* 跑一轮"写入 -> sync -> 重新 resume -> 读回",断言:
|
||||
* - 数据往返一致;
|
||||
* - **全程双编程计数为 0**:is_free 一旦退回"只看页首 64 B",journal 会把
|
||||
* 已编程的检查点页当空闲再编程一次,这个计数立刻非 0(N-12 的场景级证据)。
|
||||
* 已编程的检查点页当空闲再编程一次,这个计数立刻非 0(场景级证据)。
|
||||
*/
|
||||
static void tc_dhara_scenario(void) {
|
||||
static uint8_t wr[FAKE_PAGE_SIZE];
|
||||
@@ -978,7 +978,7 @@ static void tc_dhara_scenario(void) {
|
||||
CHECK(rc == 0, "read sector 1 after resume -> 0");
|
||||
CHECK(memcmp(rd, wr, FAKE_PAGE_SIZE) == 0, "sector 1 data survives the resume");
|
||||
CHECK(g_fake_double_program_cnt == 0u,
|
||||
"write + resume scenario: zero double-program violations (N-12 scenario guard)");
|
||||
"write + resume scenario: zero double-program violations (scenario guard)");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
@@ -999,10 +999,10 @@ int main(void) {
|
||||
CHECK(UT_TOTAL_PAGES == ((dhara_page_t)FAKE_USABLE_BLOCKS * FAKE_PAGES_PER_BLOCK),
|
||||
"TC-NANDPORT-000c guard bound = num_blocks << log2_ppb");
|
||||
|
||||
printf("-- dhara_nand_is_free: whole-page scan (N-12) --\n");
|
||||
printf("-- dhara_nand_is_free: whole-page scan --\n");
|
||||
tc_is_free();
|
||||
|
||||
printf("-- dhara_nand_is_free: out-of-range guard (N-13) --\n");
|
||||
printf("-- dhara_nand_is_free: out-of-range guard --\n");
|
||||
tc_is_free_oob();
|
||||
|
||||
printf("-- dhara_nand_prog --\n");
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
移植层是**纯地址算术 + 边界判定**,芯片侧只经 11 个 SPI 原语打交道——把它换成
|
||||
一块 RAM 假芯片,整层就完全可在 PC 上跑。而它要防的两类缺陷恰恰是真机极难复现的:
|
||||
- N-12:is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 →
|
||||
- is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 →
|
||||
dhara 往已编程页再编程 = 静默数据丢失(真机要恰好撞上那种页形态才发作);
|
||||
- N-13:越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 →
|
||||
- 越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 →
|
||||
静默读写/搬走无辜数据(真机上没有任何路径会主动喂进越界号)。
|
||||
假芯片把"双编程"做成可数事件、并记录全部访问次数,于是"越界不产生任何芯片
|
||||
访问""越界标坏块次数为 0"这类判据可机械断言——这是板端永远拿不到的观测面。
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:原评审报告 N-12/N-13(该报告 2026-10-25 已删除;报告 R-3 建议的
|
||||
设计依据:dhara 移植层两条硬约束(原评审报告 2026-10-25 已删除;其建议的
|
||||
`test/host/dhara/` 移植层单测,落位为 test/host/nand_port/);
|
||||
创建日期:2026-10-15
|
||||
"""
|
||||
@@ -50,7 +50,7 @@ BUILD = TestBuild(
|
||||
src=os.path.join(HERE, "nand_port_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(nand_ftl.c 依赖,2026-10-23 S-5)
|
||||
os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(nand_ftl.c 依赖,2026-10-23)
|
||||
os.path.join(ROOT, "Drivers", "BSP", "GD5F2GQ5UE"),
|
||||
os.path.join(ROOT, "Lib", "dhara"),
|
||||
os.path.join(ROOT, "Lib", "FatFs"),
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
* TC-NETCFG-004 未 load 时 mac = 全零(哨兵 = 不覆盖芯片内置 MAC)
|
||||
* —— 注册与 load(TC-NETCFG-005..011)——
|
||||
* TC-NETCFG-005 首次 load 注册 [network] 段恰好一次(且 load 返回 0)
|
||||
* TC-NETCFG-006 首次 load 一并注册 [ftp] 段(N-19:所有受管段先注册、后 load)
|
||||
* TC-NETCFG-006 首次 load 一并注册 [ftp] 段(FTP 鉴权:所有受管段先注册、后 load)
|
||||
* TC-NETCFG-007 重复 load 不重复注册(幂等;否则引擎按"段名重复"拒登 → 静默失配)
|
||||
* TC-NETCFG-008 注册描述符内容:段名/尺寸/键数/默认值指针/变更回调/数据指针 = RAM 副本
|
||||
* TC-NETCFG-009 load 成功:返回 0 且留一条 INFO、不误报 ERROR
|
||||
|
||||
@@ -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 infinite wait wakes on a peer event (round 3)");
|
||||
"TC-NETSEL-006 infinite wait wakes on a peer event");
|
||||
|
||||
reset_all();
|
||||
make_sock(0, NET_SOCK_STATE_ESTABLISHED, 0u, 0u);
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
* alloc_socket/net_get_sock/net_sock_raw 的边界、net_socket() 的"新 fd 不得继承"、
|
||||
* net_bind 的动态端口与回绕、getsockname/getpeername、setblocking/set_event_cb/
|
||||
* sock_state、net_poll 未初始化短路。
|
||||
* 分片 2 每任务错误码槽(E-03)+ 队列句柄绑定(adopt 幂等):内核未跑→[0] 槽退化、
|
||||
* 分片 2 每任务错误码槽 + 队列句柄绑定(adopt 幂等):内核未跑→[0] 槽退化、
|
||||
* 不同任务不同槽位互不覆写、槽位用尽回退 [0]、临界区成对且不嵌套泄漏。
|
||||
* 分片 3 跨上下文消息面:八个 msg.type 全分派 + 结果经**任务通知值**回传 +
|
||||
* "登记挂起即不发即时回执"(do_notify=0)+ 失败时把 netTask 自己槽位的精确
|
||||
* 错误码搭通知值带回来 + 调用方在自己的槽位里恢复 errno。
|
||||
* 分片 4 状态机与闸门:监听门控 net_listener_armed 三支放行/其余挡下、net_can_read、
|
||||
* net_poll 的"全局位命中才读本 Socket SINT、永不读全局"(B3/F-NET-7)、
|
||||
* 事件计数、每轮全量刷新驱动缓存 + sock_reset 步进(I1/E-02)。
|
||||
* 事件计数、每轮全量刷新驱动缓存 + sock_reset 步进(I1/头阻塞)。
|
||||
* 分片 5 读写错误分支:send 分块上限/短写/挂起登记/末块不登记(NET_PENDING 拒收边界)、
|
||||
* recv 快路径与 EOF vs WOULDBLOCK、connect 命令被拒的回滚、accept 的
|
||||
* closed-with-readable 排空接受、close 的先结算再关芯片。
|
||||
@@ -291,7 +291,7 @@ static void tc_104_socket_no_inherit(void)
|
||||
/* 三条"新 fd 不得继承"的定案,逐条钉(都是真机上静默出错的形态) */
|
||||
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(p->session_dead == 0U, "TC-NETSOCK-104g must not inherit the previous session-dead level (listener lifecycle P1)");
|
||||
CHECK(g_peers.listener_clear_calls == 1 && g_peers.pending_clear_calls == 1 &&
|
||||
g_peers.send_clear_calls == 1,
|
||||
"TC-NETSOCK-104h new fd clears listener retry and both pending tables (once each)");
|
||||
@@ -581,7 +581,7 @@ static void tc_110_queue_adopt(void)
|
||||
reset_all();
|
||||
}
|
||||
|
||||
/* ===================== 分片 2 每任务错误码槽(E-03) ===================== */
|
||||
/* ===================== 分片 2 每任务错误码槽 ===================== */
|
||||
|
||||
static void tc_120_errno_slot_per_task(void)
|
||||
{
|
||||
@@ -605,7 +605,7 @@ 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 another task cannot read the caller code (the positive wording of E-03)");
|
||||
"TC-NETSOCK-120c another task cannot read the caller code (positive wording)");
|
||||
(void)net_set_errno(NET_ERR_WOULDBLOCK);
|
||||
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;
|
||||
|
||||
@@ -224,7 +224,7 @@ static void tc_213_send_error_code_roundtrip(void)
|
||||
rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */
|
||||
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 caller reads the exact code from its own errno slot (E-03 plus notify value together)");
|
||||
"TC-NETSOCK-213d caller reads the exact code from its own errno slot (errno slot plus notify value together)");
|
||||
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 still EOF after relisten moved it back to LISTENING (N-32 P1 level, else EOF vanishes forever)");
|
||||
"TC-NETSOCK-222d still EOF after relisten moved it back to LISTENING (listener lifecycle P1 level, else EOF vanishes forever)");
|
||||
|
||||
/* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */
|
||||
reset_all();
|
||||
@@ -553,7 +553,7 @@ static void tc_240_drop_conn_branches(void)
|
||||
CHECK(g_peers.listener_settle_calls == 1 &&
|
||||
g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE &&
|
||||
g_fxchip.rst_begin_calls == 0,
|
||||
"TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (N-17)");
|
||||
"TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (heartbeat gap fix)");
|
||||
CHECK(g_peers.listener_req_calls == 1 &&
|
||||
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
|
||||
"TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)");
|
||||
|
||||
@@ -275,7 +275,7 @@ static void tc_320_close_two_paths(void)
|
||||
}
|
||||
|
||||
/* TC-NETSOCK-321 drop_conn:监听型 = 登记式(settle QUARANTINE + REQ_DROP,
|
||||
* N-17 起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */
|
||||
* 探针窗口心跳缺口修复起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */
|
||||
static void tc_321_drop_conn_settle(void)
|
||||
{
|
||||
int fd;
|
||||
@@ -292,9 +292,9 @@ static void tc_321_drop_conn_settle(void)
|
||||
g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE,
|
||||
"TC-NETSOCK-321b listener fd: abnormal-release isolation REGISTERED for the tick machine (TRAP-NET-18 QUARANTINE semantics survive, executed by net_listener_tick)");
|
||||
CHECK(g_fxchip.rst_begin_calls == 0 && g_ns_error_count == 0,
|
||||
"TC-NETSOCK-321b2 N-17: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat");
|
||||
"TC-NETSOCK-321b2 heartbeat gap fix: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat");
|
||||
CHECK(s_net_socks[fd].lost_conn_seen == 0U,
|
||||
"TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)");
|
||||
"TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (relisten settle)");
|
||||
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 port, listener attribute and fd all survive: this is drop-the-connection-not-the-listener");
|
||||
@@ -305,7 +305,7 @@ static void tc_321_drop_conn_settle(void)
|
||||
"TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED");
|
||||
reset_all();
|
||||
|
||||
/* 非监听 client fd:无监听可保,同步排渣形态原样保留(N-17 只改监听型) */
|
||||
/* 非监听 client fd:无监听可保,同步排渣形态原样保留(心跳缺口修复只改监听型) */
|
||||
bring_up();
|
||||
fd = mk_established(NET_SOCK_STREAM);
|
||||
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
|
||||
|
||||
@@ -255,7 +255,7 @@ 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 the probe moved the listener to ESTABLISHED -> accept succeeds at once (N-30 root fix)");
|
||||
"TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (relisten settle root fix)");
|
||||
CHECK(g_peers.lost_probe_calls == 1,
|
||||
"TC-NETSOCK-420e only LISTENING calls the probe (once per accept round, with debounce)");
|
||||
|
||||
@@ -281,7 +281,7 @@ 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: accept this connection to drain it (N-30, reversed yields 425)");
|
||||
"TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (relisten settle, reversed yields 425)");
|
||||
CHECK(g_peers.listener_req_calls == 0,
|
||||
"TC-NETSOCK-420i with leftover data present, do NOT register a rebuild (relisting first destroys arrived data)");
|
||||
CHECK(g_chip.get_status_calls == 0,
|
||||
@@ -299,7 +299,7 @@ static void tc_420_accept_shapes(void)
|
||||
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 only a consumer hitting empty registers an immediate rebuild (N-32 P2: removing it widens the CLOSED window)");
|
||||
"TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (listener lifecycle P2: removing it widens the CLOSED window)");
|
||||
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
|
||||
"TC-NETSOCK-420m the registration path costs zero chip transactions (rebuild lives only in net_listener_tick)");
|
||||
|
||||
@@ -554,7 +554,7 @@ static void tc_460_reset_sync_timeout_bound(void)
|
||||
"TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)");
|
||||
reset_all();
|
||||
|
||||
/* N-17:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回
|
||||
/* 探针窗口心跳缺口修复:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回
|
||||
* CLOSED 也与本调用无关(排渣与等待归 net_listener_tick 的隔离机) */
|
||||
bring_up();
|
||||
fd = mk_established(NET_SOCK_STREAM);
|
||||
@@ -564,7 +564,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) <= 20U &&
|
||||
g_ns_error_count == 0 && g_peers.listener_settle_calls == 1,
|
||||
"TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (N-17)");
|
||||
"TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (heartbeat gap fix)");
|
||||
reset_all();
|
||||
|
||||
/* 非监听 client fd 保留旧超时线(同步、600ms 封顶、留痕) */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
用本机 MSVC 编译 test/host/net_socket/net_socket_test.c(以 #include 纳入生产源
|
||||
Drivers/BSP/NET/net_socket.c + 共享假芯片 test/host/common/ch395f_fake.c +
|
||||
本目录的假内核 ns_rtos.c 与兄弟模块桩 ns_peers.c),在 PC 上离线验证:
|
||||
跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽(E-03)、
|
||||
跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽、
|
||||
监听门控与 closed-with-readable、net_poll 的中断分派与"只读自己 SINT",
|
||||
以及读写路径的短写 / 挂起登记 / EOF / 命令被拒回滚。
|
||||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||||
@@ -20,7 +20,7 @@
|
||||
(net_msg_t 是按值入队的,netTask 改自己那份副本,调用方永远看不到——AGENTS
|
||||
模块约定明写"勿给 net_msg_t 加回传字段")。这条链的正确性取决于两个任务的
|
||||
交错时序,真机上"通知丢了"表现为永久挂死并被看门狗复位,日志里只剩一条
|
||||
[ERR];而错误码本身还按任务隔离(E-03),跨任务覆写只会让排障读到别人的码。
|
||||
[ERR];而错误码本身还按任务隔离,跨任务覆写只会让排障读到别人的码。
|
||||
把队列/通知/时基做成显式量后,"登记了挂起却仍发即时回执"这种缺陷在 PC 上
|
||||
一次就能钉死。
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||
|
||||
为什么要主机侧单测(而不是只用板端):
|
||||
sha256 是 FTP pwd_hash 校验(N-19)的地基——哈希算错是"正确口令被拒 /
|
||||
sha256 是 FTP 鉴权的 pwd_hash 校验地基——哈希算错是"正确口令被拒 /
|
||||
错误口令放行"的静默语义错误,板端只见 530/230 看不出根因;标准向量
|
||||
(空串/abc/56B 补位跨界/1MB 长度进第二分组)主机侧一次钉死。
|
||||
本模块零平台依赖,无需任何桩。
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 保证测的是生产代码而非副本;本模块零平台依赖,无需任何桩)。
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只靠台架):
|
||||
* sha256 是 FTP pwd_hash 校验(N-19)的地基——哈希算错的后果是
|
||||
* sha256 是 FTP 鉴权的 pwd_hash 校验地基——哈希算错的后果是
|
||||
* "正确口令被拒 / 错误口令放行"这类静默语义错误,板端只看到 530/230,
|
||||
* 看不出哪一步坏;标准向量在主机侧毫秒级钉死。
|
||||
*
|
||||
|
||||
+32
-32
@@ -47,15 +47,15 @@ static void tc_roundtrip(void)
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 2u, 0u, 12345u,
|
||||
1760234567u, 0xDEADBEEFu);
|
||||
rc = soe_rec_parse(rec, &out);
|
||||
CHECK(rc == 0, "TC-001 parse ok");
|
||||
CHECK(out.ver == SOE_REC_VER, "TC-001 ver");
|
||||
CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-001 type");
|
||||
CHECK(out.elm_id == 2u, "TC-001 elm_id");
|
||||
CHECK(out.flags == 0u, "TC-001 flags");
|
||||
CHECK(out.seq == 12345u, "TC-001 seq");
|
||||
CHECK(out.epoch_s == 1760234567u, "TC-001 epoch");
|
||||
CHECK(out.value == 0xDEADBEEFu, "TC-001 value");
|
||||
CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-001 cp56 year=2025");
|
||||
CHECK(rc == 0, "TC-SOE-001 parse ok");
|
||||
CHECK(out.ver == SOE_REC_VER, "TC-SOE-001 ver");
|
||||
CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-SOE-001 type");
|
||||
CHECK(out.elm_id == 2u, "TC-SOE-001 elm_id");
|
||||
CHECK(out.flags == 0u, "TC-SOE-001 flags");
|
||||
CHECK(out.seq == 12345u, "TC-SOE-001 seq");
|
||||
CHECK(out.epoch_s == 1760234567u, "TC-SOE-001 epoch");
|
||||
CHECK(out.value == 0xDEADBEEFu, "TC-SOE-001 value");
|
||||
CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-SOE-001 cp56 year=2025");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-002 校验和/版本硬门槛 ---------------- */
|
||||
@@ -76,14 +76,14 @@ static void tc_gates(void)
|
||||
}
|
||||
rec[i] = save;
|
||||
}
|
||||
CHECK(bad == 0, "TC-002 any byte flip rejected");
|
||||
CHECK(soe_rec_parse(rec, &out) == 0, "TC-002 intact parses ok");
|
||||
CHECK(bad == 0, "TC-SOE-002 any byte flip rejected");
|
||||
CHECK(soe_rec_parse(rec, &out) == 0, "TC-SOE-002 intact parses ok");
|
||||
|
||||
rec[SOE_OFF_VER] = (uint8_t)(SOE_REC_VER + 1u); /* 版本篡改:sum 仍按新
|
||||
* 字节算会自洽,但版本
|
||||
* 门槛先拒 */
|
||||
CHECK(soe_rec_parse(rec, &out) == -2, "TC-002 bad version -> -2");
|
||||
CHECK(soe_rec_parse(NULL, &out) == -1, "TC-002 null args -> -1");
|
||||
CHECK(soe_rec_parse(rec, &out) == -2, "TC-SOE-002 bad version -> -2");
|
||||
CHECK(soe_rec_parse(NULL, &out) == -1, "TC-SOE-002 null args -> -1");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-003 CP56 正确性 + 交叉往返 ---------------- */
|
||||
@@ -101,24 +101,24 @@ static void tc_cp56(void)
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
/* 显式字段:12:34:56 → ms 域 = 56*1000(秒域时基如实编码,亚秒 0) */
|
||||
CHECK(((uint32_t)out.cp56[0] | ((uint32_t)out.cp56[1] << 8)) == 56000u,
|
||||
"TC-003 ms field = sec*1000 (56000)");
|
||||
"TC-SOE-003 ms field = sec*1000 (56000)");
|
||||
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
|
||||
CHECK(rc == 0, "TC-003 cross decode ok");
|
||||
CHECK(back == 1791808496000ULL, "TC-003 cross roundtrip exact");
|
||||
CHECK(rc == 0, "TC-SOE-003 cross decode ok");
|
||||
CHECK(back == 1791808496000ULL, "TC-SOE-003 cross roundtrip exact");
|
||||
|
||||
/* 闰日 2024-02-29 00:00:00 UTC = 1709164800 */
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_BLOCK, 0u, 0u, 2u, 1709164800u, 0u);
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
CHECK(out.cp56[4] == 29u && out.cp56[5] == 2u && out.cp56[6] == 24u,
|
||||
"TC-003 leap day fields");
|
||||
"TC-SOE-003 leap day fields");
|
||||
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
|
||||
CHECK((rc == 0) && (back == 1709164800000ULL), "TC-003 leap roundtrip");
|
||||
CHECK((rc == 0) && (back == 1709164800000ULL), "TC-SOE-003 leap roundtrip");
|
||||
|
||||
/* 2000-01-01 00:00:00 边界 */
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_PLATE_IN, 0u, 0u, 3u, 946684800u, 0u);
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
|
||||
CHECK((rc == 0) && (back == 946684800000ULL), "TC-003 year-2000 boundary");
|
||||
CHECK((rc == 0) && (back == 946684800000ULL), "TC-SOE-003 year-2000 boundary");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-004 IV 语义 ---------------- */
|
||||
@@ -130,10 +130,10 @@ static void tc_iv(void)
|
||||
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 1u, SOE_FLG_IV, 9u, 0u, 1u);
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
CHECK((out.cp56[2] & 0x80u) != 0u, "TC-004 cp56 IV bit set");
|
||||
CHECK(out.epoch_s == 0u, "TC-004 epoch field zeroed");
|
||||
CHECK((out.cp56[2] & 0x80u) != 0u, "TC-SOE-004 cp56 IV bit set");
|
||||
CHECK(out.epoch_s == 0u, "TC-SOE-004 epoch field zeroed");
|
||||
CHECK(iec104_cp56_to_epoch_ms(out.cp56, &back) == -2,
|
||||
"TC-004 cross decoder rejects IV");
|
||||
"TC-SOE-004 cross decoder rejects IV");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-005 钳位 ---------------- */
|
||||
@@ -146,17 +146,17 @@ static void tc_clamp(void)
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 4u, 100u, 0u);
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
CHECK((out.cp56[4] == 1u) && (out.cp56[5] == 1u) && (out.cp56[6] == 0u),
|
||||
"TC-005 clamp low -> 2000-01-01");
|
||||
"TC-SOE-005 clamp low -> 2000-01-01");
|
||||
(void)iec104_cp56_to_epoch_ms(out.cp56, &back);
|
||||
CHECK(back == 946684800000ULL, "TC-005 clamp low exact");
|
||||
CHECK(back == 946684800000ULL, "TC-SOE-005 clamp low exact");
|
||||
|
||||
/* epoch_s 为 uint32 → 最大 2106-02-07(0xFFFFFFFF),恒在 CP56 域内:
|
||||
* 上界钳位是防御性死分支(epoch 域扩展时生效),此处断言精确往返 */
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 5u, 0xFFFFFFFFu, 0u);
|
||||
(void)soe_rec_parse(rec, &out);
|
||||
CHECK(out.cp56[6] == 106u, "TC-005 uint32 max -> year 2106 (no clamp needed)");
|
||||
CHECK(out.cp56[6] == 106u, "TC-SOE-005 uint32 max -> year 2106 (no clamp needed)");
|
||||
(void)iec104_cp56_to_epoch_ms(out.cp56, &back);
|
||||
CHECK(back == 4294967295000ULL, "TC-005 uint32 max exact roundtrip");
|
||||
CHECK(back == 4294967295000ULL, "TC-SOE-005 uint32 max exact roundtrip");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-006 结构不变量 ---------------- */
|
||||
@@ -167,7 +167,7 @@ static void tc_layout(void)
|
||||
uint8_t s = 0u;
|
||||
int zero_ok = 1;
|
||||
|
||||
CHECK(SOE_REC_SIZE == 64u, "TC-006 record size 64");
|
||||
CHECK(SOE_REC_SIZE == 64u, "TC-SOE-006 record size 64");
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_RESUME, SOE_ELM_NONE, 0u,
|
||||
0u, 946684800u, 0u);
|
||||
for (i = 23u; i <= 62u; i++) {
|
||||
@@ -175,12 +175,12 @@ static void tc_layout(void)
|
||||
zero_ok = 0;
|
||||
}
|
||||
}
|
||||
CHECK(zero_ok, "TC-006 reserved [23..62] zero");
|
||||
CHECK(zero_ok, "TC-SOE-006 reserved [23..62] zero");
|
||||
for (i = 0u; i < SOE_OFF_SUM; i++) {
|
||||
s = (uint8_t)(s + rec[i]);
|
||||
}
|
||||
CHECK(rec[SOE_OFF_SUM] == s, "TC-006 sum8 covers [0..62]");
|
||||
CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-006 elm none");
|
||||
CHECK(rec[SOE_OFF_SUM] == s, "TC-SOE-006 sum8 covers [0..62]");
|
||||
CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-SOE-006 elm none");
|
||||
}
|
||||
|
||||
/* ---------------- TC-SOE-007 序号边界 ---------------- */
|
||||
@@ -190,11 +190,11 @@ static void tc_seq_edges(void)
|
||||
soe_rec_t out;
|
||||
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0u, 946684800u, 0u);
|
||||
CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0u), "TC-007 seq 0");
|
||||
CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0u), "TC-SOE-007 seq 0");
|
||||
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0xFFFFFFFFu,
|
||||
946684800u, 0u);
|
||||
CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0xFFFFFFFFu),
|
||||
"TC-007 seq 0xFFFFFFFF");
|
||||
"TC-SOE-007 seq 0xFFFFFFFF");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
* TC-SOESTORE-011 打开模式必须是 FA_WRITE|FA_OPEN_APPEND;对照保真:只给
|
||||
* FA_OPEN_APPEND 的句柄 f_write 恒返 FR_DENIED(陷阱一,本仓实证根因)
|
||||
* TC-SOESTORE-012 多条落盘:一条一次 open/write/close(本模块**没有**批量合并——
|
||||
* 与 sys_log 的 N-24 不同),FIFO 顺序、seq 递增
|
||||
* 与 sys_log 的日志读写放大收口不同),FIFO 顺序、seq 递增
|
||||
* TC-SOESTORE-013 open 与 close 严格配对(脏句柄不跨调用残留)
|
||||
* —— drain 侧:故障注入 ——
|
||||
* TC-SOESTORE-014 短写 63B → 判失败 → unlink 重建 → 重试成功 → 最终恰好 64 字节
|
||||
|
||||
@@ -46,7 +46,7 @@ BUILD = TestBuild(
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "util"),
|
||||
os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(sd2506.h 依赖,2026-10-23 S-5)
|
||||
os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(sd2506.h 依赖,2026-10-23)
|
||||
os.path.join(ROOT, "Drivers", "BSP", "SD2506"),
|
||||
HERE,
|
||||
],
|
||||
|
||||
@@ -6,7 +6,7 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运
|
||||
用本机 MSVC 编译 test/host/sys_log_mcu/sys_log_mcu_test.c(以 #include 纳入**生产源**
|
||||
App/log/sys_log_mcu.c,并纳入共享假卷 test/host/common/fatfs_fake.c),在 PC 上离线
|
||||
跑 64B 定长记录格式、单条/批量两条写路径、syslog_drain 的 preboot+队列排空与
|
||||
"失败回投不丢条目"、N-24 的 8 条滑动读窗口 + 写者代数失效、挂载幂等与
|
||||
"失败回投不丢条目"、日志读写放大收口的 8 条滑动读窗口 + 写者代数失效、挂载幂等与
|
||||
上电/重启分类、sys_log_evidence 的 wdev.log 取证通道,失败即非 0 退出。
|
||||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||||
|
||||
@@ -20,7 +20,7 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运
|
||||
3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检
|
||||
全部滞留),改成幂等 + 重试后又必须防"每 2s 刷一条同样错误" → 要注入 mkdir
|
||||
失败才测得到,且必须能数出"到底留痕几次"。
|
||||
4. **读窗口 try 语义 / N-24 读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面,
|
||||
4. **读窗口 try 语义 / 日志读写放大收口的读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面,
|
||||
只做前者会把陈旧读当正确、只做后者会退回逐条 open/seek/close 的旧实现。
|
||||
|
||||
桩的边界:ff.h / sys_log.h / soe_store.h / sys_clock.h / vol_lock.h 用**真实头**;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
* 模块名称:运行/自检记录落盘后端(sys_log_mcu)主机侧单元测试(L0.5)
|
||||
* 模块功能:在 PC 上离线验证 App/log/sys_log_mcu.c 的四族契约——
|
||||
* A 64B 定长记录格式(时间戳 / epoch ASCII / 描述三段 + 截断 + 占位)
|
||||
* B 写侧(单条 s_append_rec、批量 s_append_many、N-24 的"一批一次
|
||||
* B 写侧(单条 s_append_rec、批量 s_append_many、日志读写放大收口的"一批一次
|
||||
* open/write/close"、容量轮转、L1 自愈、L2 留痕去重、卷锁 try)
|
||||
* C 排空(syslog_drain 的 preboot + 队列两条来源、换文件分批、
|
||||
* 失败回投不丢条目、-3 退回慢路径)
|
||||
* D 读侧(N-24 的 8 条滑动窗口 + 写者代数失效 + 越界/忙/短读处置)
|
||||
* D 读侧(日志读写放大收口的 8 条滑动窗口 + 写者代数失效 + 越界/忙/短读处置)
|
||||
* 外加 syslog_mcu_mounted 的 挂载幂等与上电/重启分类、sys_log_evidence
|
||||
* 的 wdev.log 取证通道(8KB 护栏 + 第二条写必须在释放卷锁之后)。
|
||||
*
|
||||
@@ -25,7 +25,7 @@
|
||||
* 2. **f_write 簇分配失败 = FR_OK + 短写**:只判 fr != FR_OK 就等于把丢记录当成功。
|
||||
* 3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检
|
||||
* 全部滞留),且 2s 重试会刷屏 → 幂等 + 留痕去重都要注入 mkdir 失败才测得到。
|
||||
* 4. **读窗口 try 语义 / N-24 读侧**:读窗口"命中即零 I/O"与"写者一落地就必须失效重填"是同一
|
||||
* 4. **读窗口 try 语义 / 日志读写放大收口的读侧**:读窗口"命中即零 I/O"与"写者一落地就必须失效重填"是同一
|
||||
* 枚硬币的两面——只做前者会把陈旧读当正确,只做后者会退化成逐条 open/seek/close
|
||||
* 的旧实现(UI 翻 20 条 = 20 次 FAT 往返)。两者都靠假卷的调用计数才断言得了。
|
||||
*
|
||||
@@ -36,7 +36,7 @@
|
||||
* TC-SYSLOG-002 描述超长截断到 SYSLOG_DESC_MAX,且不越 64B 边界
|
||||
* TC-SYSLOG-003 墙钟未建立:get_str 返 NULL -> 占位 "1970-01-01 08:00:00";
|
||||
* get() 返 UINT32_MAX -> epoch 字段落 "0000000000"
|
||||
* TC-SYSLOG-004 单条路径与批量路径产出的同内容记录**逐字节相同**(N-24 抽出
|
||||
* TC-SYSLOG-004 单条路径与批量路径产出的同内容记录**逐字节相同**(日志读写放大收口抽出
|
||||
* s_format_rec 的全部理由:UI 按定长字段偏移解析)
|
||||
* —— B 写侧 ——
|
||||
* TC-SYSLOG-010 单条落盘成功 + 打开模式必须 FA_WRITE|FA_OPEN_APPEND;close 配对
|
||||
@@ -175,7 +175,7 @@
|
||||
* - 真跨任务并发:读窗口用"对齐 32 位读写在 ARM 上原子"免锁,主机 x64 + 单线程
|
||||
* 编不出真竞态;本套只断言单写者语义与代数失效逻辑本身。
|
||||
* - f_close 内部 sync_fs -> disk_ioctl(CTRL_SYNC) -> ftl_flush_cache() 的掉电落盘
|
||||
* 链:属 nand_port/GD5F 层,假卷里没有 FTL,"关文件即落盘"要靠真机 + N-22 台账。
|
||||
* 链:属 nand_port/GD5F 层,假卷里没有 FTL,"关文件即落盘"要靠真机 + 写回缓存空闲 flush 台账。
|
||||
* - snprintf 失败(n<=0)分支:宿主 snprintf 不会失败,且假件不提供注入——
|
||||
* 为了刷覆盖率去伪造返回值等于把断言写在假件规则上,宁可留空。
|
||||
* - DBG 文本内容:桩只计数(真实宏展开成 snprintf + 192B 行缓冲)。
|
||||
@@ -741,7 +741,7 @@ static void tc_write_many(void)
|
||||
uint32_t i;
|
||||
int ok;
|
||||
|
||||
printf("-- TC-SYSLOG-020..024 batch write path (N-24) --\n");
|
||||
printf("-- TC-SYSLOG-020..024 batch write path (log I/O amplification wrap-up) --\n");
|
||||
|
||||
/* 020 8 条一次 I/O */
|
||||
reset_all();
|
||||
@@ -1139,7 +1139,7 @@ static void tc_read(void)
|
||||
uint32_t i;
|
||||
int ok;
|
||||
|
||||
printf("-- TC-SYSLOG-050..058 read side (N-24 sliding window) --\n");
|
||||
printf("-- TC-SYSLOG-050..058 read side (log I/O amplification wrap-up sliding window) --\n");
|
||||
|
||||
/* 050 count */
|
||||
reset_all();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
本模块是登记簿 N-17 的当事方——"监听重建期间心跳缺口 800ms~1s,距 IWDG 最坏
|
||||
本模块是探针窗口心跳缺口那件事的当事方——"监听重建期间心跳缺口 800ms~1s,距 IWDG 最坏
|
||||
超时 1701ms 余量仅 ~0.7s"。缺口成因全是本模块的**纯逻辑**(分片延时的喂狗
|
||||
节奏、500/2000ms 监听退避、200ms accept 退避、50ms 收尾、屏障等待态喂心跳)。
|
||||
真机上只能"跑起来看复位不复位",主机侧可**逐片断言**。时间包重组状态机
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:App/sync/time_sync.c 文件头注释 + docs/任务规划/登记簿.md N-17;
|
||||
设计依据:App/sync/time_sync.c 文件头注释 + docs/网络/网络自愈架构设计.md §4.6;
|
||||
创建日期:2026-10-10
|
||||
"""
|
||||
import os
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* - dbg_log.h -> DBG_ERROR/DBG_INFO 计数
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用板端套件):
|
||||
* 1. **本模块是 N-17 的当事方**:登记簿 N-17 记录"time_sync 监听重建期间心跳缺口
|
||||
* 1. **本模块是探针窗口心跳缺口的当事方**:当时的记录写着"time_sync 监听重建期间心跳缺口
|
||||
* 800ms~1s,距 IWDG 最坏超时 1701ms 余量仅 ~0.7s"。缺口的成因全部是本模块的
|
||||
* **纯逻辑**——分片延时(delay_fed)的心跳节奏、监听重试退避(500/2000ms)、
|
||||
* accept 轮询退避(200ms)、收尾延时(50ms)、以及"屏障等待态也要喂心跳"。
|
||||
@@ -235,7 +235,7 @@ int net_close(int sockfd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- net_drop_conn(E-02 收口):计数 + 记录 fd ---- */
|
||||
/* ---- net_drop_conn(头阻塞收口):计数 + 记录 fd ---- */
|
||||
static int s_drop_calls;
|
||||
static int s_drop_socks[32];
|
||||
|
||||
@@ -605,7 +605,7 @@ static void tc_handle_conn(void) {
|
||||
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)");
|
||||
CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d budget exhausted drops the session on purpose (head-of-line wrap-up: net_drop_conn kills the connection, keeps the listener)");
|
||||
|
||||
/* 9) 剩余预算不足一片 */
|
||||
fakes_reset();
|
||||
@@ -653,7 +653,7 @@ static void tc_task(void) {
|
||||
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_calls == 13, "TC-TIMESYNC-040h beat count = main loop 1 + backoff 8 + accepted connection 1 + wrap-up 1 + next cycle 1");
|
||||
CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-040i every beat sets the TIMESYNC bit");
|
||||
|
||||
/* B) 就绪屏障:2 次未就绪 + 1 次就绪 */
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
失败即非 0 退出。零硬件、零烧录、毫秒级。
|
||||
|
||||
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明,2026-10-01 T-3 抽取)。
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明,2026-10-01 抽取)。
|
||||
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
task_watchdog.c 的判定逻辑(位图取走清零 / 掩码 / miss_streak 累计 / 节流)
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:docs/测试说明/看门狗单元测试说明.md;docs/任务规划/登记簿.md T-3
|
||||
设计依据:docs/测试说明/看门狗单元测试说明.md
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
import os
|
||||
|
||||
+11
-11
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""run_all_suites.py - 多套件回归编排(纯标准库)。
|
||||
|
||||
做什么(2026-10-01 测试架构评审 T-5):
|
||||
做什么(2026-10-01 测试架构评审):
|
||||
套件互斥(`test_config.h` 的 `#error`)+ 编译期开关 → 跑 4 个套件的回归
|
||||
本来要手工循环 4 轮(改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本把它编排成一条命令:
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
1. 改 test_config.h 启用该套件(其它全注释)
|
||||
2. @build(0 错 0 警,否则跳过该套件)
|
||||
3. 烧录(stlink-flash skill:stlink_flash.py flash,Reset & Run)
|
||||
4. @test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验 T-2)
|
||||
4. @test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验)
|
||||
结束后:
|
||||
5. **无条件还原 test_config.h**(try/finally,含异常/KeyboardInterrupt 路径)
|
||||
|
||||
为什么要还原(这是本脚本存在的核心理由):
|
||||
跑完一轮忘了还原 = **测试固件进库**(与 T-1 同源风险)。T-1 是"提交期门禁"兜底,
|
||||
跑完一轮忘了还原 = **测试固件进库**(与提交期门禁同源风险)。提交期门禁是兜底,
|
||||
本脚本是"流程自动化"——两条防线各自独立。
|
||||
|
||||
⚠️ 代价提醒:
|
||||
@@ -31,16 +31,16 @@
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --dry-run # 只打印计划
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6,无需 MCU)
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(无需 MCU)
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --keep-config # 不还原(调试用,慎)
|
||||
|
||||
--check-build(S6 / 上游 P1-10):
|
||||
--check-build:
|
||||
只做"改宏 + @build",不烧录不采集,故**不需要 MCU / 串口 / ST-LINK**。
|
||||
用途:① 改动测试固件或 test_config.h 后自检 4 套件仍可编译;
|
||||
② 防止"某套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。
|
||||
注意 RAM 溢出(P1-9)会自然暴露为链接错误 L6406E——这正是想要的。
|
||||
注意 RAM 溢出会自然暴露为链接错误 L6406E——这正是想要的。
|
||||
|
||||
设计依据:docs/任务规划/登记簿.md T-5;docs/测试说明/测试开发标准流程.md §3.1
|
||||
设计依据:docs/测试说明/测试开发标准流程.md §3.1
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
|
||||
@@ -144,14 +144,14 @@ def main(argv=None):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ap = argparse.ArgumentParser(description="多套件回归编排(T-5,纯标准库)")
|
||||
ap = argparse.ArgumentParser(description="多套件回归编排(纯标准库)")
|
||||
ap.add_argument("--suites", default=",".join(SUITES.keys()),
|
||||
help="要跑的套件,逗号分隔(默认全部:{})".format(",".join(SUITES)))
|
||||
ap.add_argument("--build-timeout", type=int, default=300)
|
||||
ap.add_argument("--flash-timeout", type=int, default=300)
|
||||
ap.add_argument("--dry-run", action="store_true", help="只打印计划,不改配置不构建不烧录")
|
||||
ap.add_argument("--check-build", action="store_true",
|
||||
help="只编译不烧录(S6/P1-10):逐套件改宏 + @build,验证 4 套件可编译性;"
|
||||
help="只编译不烧录:逐套件改宏 + @build,验证 4 套件可编译性;"
|
||||
"不需要 MCU / 串口 / ST-LINK,可用于 CI 或改动测试固件后自检")
|
||||
ap.add_argument("--keep-config", action="store_true",
|
||||
help="结束后不还原 test_config.h(调试用;⚠️ 会留下测试固件配置)")
|
||||
@@ -212,7 +212,7 @@ def main(argv=None):
|
||||
results[suite] = "BUILD_FAIL"
|
||||
continue
|
||||
|
||||
# --check-build:编译通过即达标,不烧录不采集(S6)
|
||||
# --check-build:编译通过即达标,不烧录不采集
|
||||
if args.check_build:
|
||||
results[suite] = "PASS"
|
||||
print("[{}] BUILD_OK -> PASS(--check-build,未烧录)".format(suite))
|
||||
@@ -227,7 +227,7 @@ def main(argv=None):
|
||||
# 烧录失败不直接判死:板子可能已复位,交由采集+判读决定(与 @test 一致)
|
||||
print("[WARN] 烧录 rc={}(继续:板子可能已复位)".format(frc))
|
||||
|
||||
# 3) 采集 + 判读(含套件汇总行校验 T-2)
|
||||
# 3) 采集 + 判读(含套件汇总行校验)
|
||||
# 烧录统一走 stlink-flash skill(与 .serialtest/config.json 的 flash_cmd 同源)
|
||||
flash_cmd = '"{}" "{}" flash'.format(py, STLINK_FLASH)
|
||||
cmd = [py, str(RUN_BOOT), "--flash-cmd", flash_cmd]
|
||||
|
||||
Reference in New Issue
Block a user