924ac3d428
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII (无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。 四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、 console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、 11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行 (9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。 两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径 (状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。 差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、 common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于 把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改 注释、要么留下「这行为什么没改」的悬案。 Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号 集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓 整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区 非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。 另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量; 各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log 199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100) → 生产码零改动,基线不需重登。 闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮 已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不 等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动 验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的 守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式 只覆盖自己想到的维度时不能替代真闸。 Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown 反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。 文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照 表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、 三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。 后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定 test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry, 不能顺手做);替换器加「编号形态白名单」校验。 gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers + mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批 增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash 增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。 id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿; cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。 按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
550 lines
22 KiB
C
550 lines
22 KiB
C
/*
|
||
* 模块名称:网络配置(NETCFG)主机侧单元测试
|
||
* 模块功能:在 PC 上离线验证 Drivers/BSP/NETCFG/net_cfg.c 的**编排与状态机**:
|
||
* 出厂默认有效性、注册幂等与注册内容、load 失败仍返回 0 的"RAM 恒有效"
|
||
* 契约、跨任务保存请求交接(IDLE/WAIT/OK/FAIL)、写盘期间新请求不丢、
|
||
* 写盘失败不得触发网络重建、MAC 三态优先级、IPv4 定长格式化。
|
||
*
|
||
* 被测对象:真实源文件 ../../../Drivers/BSP/NETCFG/net_cfg.c(#include 原样纳入,
|
||
* 保证测的是生产代码而非副本)。桩只有三处,都写在下面:
|
||
* - stub/dbg_log.h:日志宏 → 计数(本目录);
|
||
* - 配置文件引擎:用**真实头** App/cfg/cfg_file.h(接口契约原汁,引擎
|
||
* 一改签名本测试立刻编译期报错),被调的三个函数
|
||
* cfg_file_register / cfg_file_load / cfg_file_save 由本文件给出
|
||
* **可控实现**(同 test/host/nand_port/ 的路子);
|
||
* - ftp_cfg_register:只计数不实现——[ftp] 段自身的解析/默认值/默认
|
||
* 口令判定归 test/host/ftp_cfg/ 模块,两边不重复也不互相顶包。
|
||
* (不能把 ftp_cfg.c 也 include 进来:两个 .c 都有 static s_cfg,
|
||
* 同一个 TU 里直接重定义。)
|
||
* ⚠ 引擎的解析/写盘/沉降契约已由 test/host/cfg_file/(103 条)覆盖,
|
||
* 本模块刻意不重测引擎,只测 net_cfg.c 这一层的编排与交接。
|
||
*
|
||
* 为什么要主机侧单测(而不是只靠台架):
|
||
* 1. 保存链是**跨任务交接**(uiTask 投递 net_cfg_save_request → defaultTask 执行
|
||
* net_cfg_save_run → 成功才 net_cfg_request_apply → netTask 按网络代次重建监听)。
|
||
* "写盘期间 UI 又按了一次 ENTER"与"写盘失败却去重建网络"这两条,真机上要靠
|
||
* 手速 + 故障注入才碰得到;错了就是"改了 IP 不生效"或"保存失败网络被无端重建"
|
||
* 这类现场极难定位的问题——主机侧用假引擎的写盘钩子直接构造时序。
|
||
* 2. MAC 三态优先级(存储非零 > 芯片回写影子 > 全零)决定 UI 显示值和下发给
|
||
* CH395F 的 MAC;写错会**静默用错 MAC**(同网段 MAC 冲突,症状是偶发丢包)。
|
||
* 3. net_cfg_fmt_ipv4 的**定长 15 字符(每段 %03u 补零)**是 UI 编辑光标按固定偏移定位
|
||
* 字节的前提;哪天被"优化"成不补零,光标就错位——纯字符串行为,一次钉死。
|
||
*
|
||
* 覆盖点:
|
||
* —— 出厂默认(load 之前也必须可用,TC-NETCFG-001..004)——
|
||
* TC-NETCFG-001 未 load 时 ip = 工厂默认 192.168.1.100
|
||
* TC-NETCFG-002 未 load 时 mask = 255.255.255.0
|
||
* TC-NETCFG-003 未 load 时 gw = 192.168.1.1
|
||
* 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-007 重复 load 不重复注册(幂等;否则引擎按"段名重复"拒登 → 静默失配)
|
||
* TC-NETCFG-008 注册描述符内容:段名/尺寸/键数/默认值指针/变更回调/数据指针 = RAM 副本
|
||
* TC-NETCFG-009 load 成功:返回 0 且留一条 INFO、不误报 ERROR
|
||
* TC-NETCFG-010 **引擎报 -1 时 net_cfg_load 仍返回 0**(RAM 恒有效,不带崩网络启动)
|
||
* TC-NETCFG-011 load 失败必须留 ERROR(不许静默回落默认值)
|
||
* —— RAM 副本读写(TC-NETCFG-012..013)——
|
||
* TC-NETCFG-012 net_cfg_set 写入被 net_cfg_get 读回
|
||
* TC-NETCFG-013 net_cfg_set(NULL) 不改写(UI 侧误传不得毁掉配置)
|
||
* —— 直接保存(TC-NETCFG-014..015)——
|
||
* TC-NETCFG-014 引擎写盘成功 → net_cfg_save 返回 0
|
||
* TC-NETCFG-015 引擎写盘失败 → net_cfg_save 返回 -1 并留痕(错误码没被吞成 0)
|
||
* —— 跨任务保存交接状态机(TC-NETCFG-020..033,UI 保存行显示的就是它)——
|
||
* TC-NETCFG-020 初始态 = IDLE
|
||
* TC-NETCFG-021 save_request 后置 WAIT
|
||
* TC-NETCFG-022 save_request 经已注册回调唤醒属主任务(计数 +1)
|
||
* TC-NETCFG-023 未注册回调时 save_request 不崩、请求照样挂起
|
||
* TC-NETCFG-024a 有请求时 save_run 确实写盘一次(024 的前置)
|
||
* TC-NETCFG-024 无请求时空醒 → 返回 0 且**不写盘**(2s 周期轮询不刷 Flash)
|
||
* TC-NETCFG-025 save_run 成功后 pending 清零
|
||
* TC-NETCFG-026 save_run 成功后状态 = OK
|
||
* TC-NETCFG-027 save_run 成功后触发一次"立即应用"
|
||
* TC-NETCFG-028 **写盘进行中**状态仍是 WAIT(写盘前先清 pending,靠 busy 撑住;
|
||
* 否则这段窗口会退回上一轮的 OK/IDLE,用户看不到反馈只会再按一次)
|
||
* TC-NETCFG-029a **写盘期间投递的新请求不丢**:本轮结束后仍为 WAIT
|
||
* TC-NETCFG-029b 下一轮真的再写一次(不是只把状态挂在那里)
|
||
* TC-NETCFG-030 写盘失败 → 状态 = FAIL
|
||
* TC-NETCFG-031 **写盘失败不得触发"立即应用"**(内存值还没落盘,不能当已生效)
|
||
* TC-NETCFG-032 未注册应用回调时 request_apply 是空操作(模拟器就是这条路)
|
||
* TC-NETCFG-033a 应用回调注册后可被触发
|
||
* TC-NETCFG-033b 传 NULL 注销后不再被触发
|
||
* —— MAC 三态优先级(TC-NETCFG-040..045)——
|
||
* TC-NETCFG-040 存储 MAC 非零 → 用存储值(即使芯片已回写影子)
|
||
* TC-NETCFG-041 存储全零 + 芯片已回写 → 用影子值
|
||
* TC-NETCFG-042 存储全零 + 未回写 → 全零
|
||
* TC-NETCFG-043 set_active_mac(NULL) 不得把影子置为有效
|
||
* TC-NETCFG-044 set_active_mac 正常回写后影子生效
|
||
* TC-NETCFG-045 get_mac(NULL) 安全返回
|
||
* —— IPv4 定长格式化(TC-NETCFG-050..056,UI 光标定位依赖)——
|
||
* TC-NETCFG-050 常规地址渲染为定长 15 字符 "192.168.001.100"
|
||
* TC-NETCFG-051 全零地址补零为 "000.000.000.000"(仍是 15 字符,偏移不变)
|
||
* TC-NETCFG-052 255 边界 "255.255.255.255"
|
||
* TC-NETCFG-053 p_ip 为 NULL:不写缓冲
|
||
* TC-NETCFG-054 p_buf 为 NULL:不崩
|
||
* TC-NETCFG-055 len 为 0:不写缓冲
|
||
* TC-NETCFG-056 len 不足:截断但**必须 NUL 结尾**且不越界(UI 缓冲防线)
|
||
*
|
||
* 用例顺序有依赖(模块静态状态无法整体复位):出厂默认组必须最先跑(任何 load 之前);
|
||
* IDLE 组在任何 save_request 之前;需要干净初态处显式复位白盒量并注明原因。
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-23
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
/* 真实引擎头(接口原汁)+ 被测生产源(原样纳入);dbg_log.h 命中本目录 stub */
|
||
#include "cfg_file.h"
|
||
#include "net_cfg.h"
|
||
|
||
#include "net_cfg.c"
|
||
|
||
/* ===================== 断言框架 ===================== */
|
||
|
||
static int s_total;
|
||
static int s_passed;
|
||
static int s_failed;
|
||
|
||
#define CHECK(cond, name) do { \
|
||
s_total++; \
|
||
if (cond) { s_passed++; printf(" PASS %s\n", name); } \
|
||
else { s_failed++; printf(" FAIL %s (line %d)\n", name, __LINE__); } \
|
||
} while (0)
|
||
|
||
/* ===================== dbg_log 桩的计数定义 ===================== */
|
||
|
||
int g_netcfg_info_count;
|
||
int g_netcfg_error_count;
|
||
int g_netcfg_debug_count;
|
||
|
||
static void logs_clear(void)
|
||
{
|
||
g_netcfg_info_count = 0;
|
||
g_netcfg_error_count = 0;
|
||
g_netcfg_debug_count = 0;
|
||
}
|
||
|
||
/* ===================== 假引擎(可控实现;原型来自真实 cfg_file.h) ========== */
|
||
|
||
/* 注册描述符在 net_cfg.c 里是栈上临时量,必须**按值留档**后才可比对 */
|
||
typedef struct {
|
||
char name[CFG_FILE_MAX_NAME_LEN + 1u];
|
||
const void *p_data;
|
||
const void *p_defaults;
|
||
size_t data_size;
|
||
uint8_t key_count;
|
||
void (*p_cb)(void);
|
||
} fake_reg_t;
|
||
|
||
static fake_reg_t g_regs[CFG_FILE_MAX_SECTIONS];
|
||
static int g_reg_n;
|
||
static int g_ftp_reg_calls; /* ftp_cfg_register 被 net_cfg_load 调了几次 */
|
||
static int g_load_calls;
|
||
static int g_save_calls;
|
||
static int g_apply_calls;
|
||
static int g_req_cb_calls;
|
||
static int g_load_rc; /* 注入:cfg_file_load 返回值 */
|
||
static int g_save_rc; /* 注入:cfg_file_save 返回值 */
|
||
static int g_state_during_save = -1;/* 写盘钩子里读到的交接状态 */
|
||
static int g_requeue_during_save; /* 1 = 写盘中再投递一次请求 */
|
||
static void (*g_save_hook)(void); /* 写盘时刻回调(构造真机难复现的时序) */
|
||
|
||
static void fake_copy_name(char *dst, const char *src, size_t cap)
|
||
{
|
||
size_t i;
|
||
|
||
for (i = 0u; (i + 1u < cap) && (src[i] != '\0'); i++) {
|
||
dst[i] = src[i];
|
||
}
|
||
dst[i] = '\0';
|
||
}
|
||
|
||
int cfg_file_register(const cfg_section_reg_t *p_reg)
|
||
{
|
||
fake_reg_t *rec;
|
||
|
||
if ((p_reg == NULL) || (p_reg->name == NULL)) {
|
||
return -1;
|
||
}
|
||
if ((size_t)g_reg_n >= (size_t)CFG_FILE_MAX_SECTIONS) {
|
||
return -1;
|
||
}
|
||
rec = &g_regs[g_reg_n];
|
||
fake_copy_name(rec->name, p_reg->name, sizeof(rec->name));
|
||
rec->p_data = p_reg->p_data;
|
||
rec->p_defaults = p_reg->p_defaults;
|
||
rec->data_size = p_reg->data_size;
|
||
rec->key_count = p_reg->key_count;
|
||
rec->p_cb = p_reg->p_cb;
|
||
g_reg_n++;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_file_load(void)
|
||
{
|
||
g_load_calls++;
|
||
return g_load_rc;
|
||
}
|
||
|
||
int cfg_file_save(void)
|
||
{
|
||
g_save_calls++;
|
||
if (g_save_hook != NULL) {
|
||
g_save_hook();
|
||
}
|
||
return g_save_rc;
|
||
}
|
||
|
||
/* [ftp] 段归 test/host/ftp_cfg/ 模块测;这里只验证"net_cfg_load 确实带上了它" */
|
||
void ftp_cfg_register(void)
|
||
{
|
||
g_ftp_reg_calls++;
|
||
}
|
||
|
||
static void fake_reset(void)
|
||
{
|
||
g_reg_n = 0;
|
||
g_ftp_reg_calls = 0;
|
||
g_load_calls = 0;
|
||
g_save_calls = 0;
|
||
g_apply_calls = 0;
|
||
g_req_cb_calls = 0;
|
||
g_load_rc = 0;
|
||
g_save_rc = 0;
|
||
g_state_during_save = -1;
|
||
g_requeue_during_save = 0;
|
||
g_save_hook = NULL;
|
||
logs_clear();
|
||
}
|
||
|
||
static int mac_eq(const uint8_t *p_a, const uint8_t *p_b)
|
||
{
|
||
return memcmp(p_a, p_b, 6u) == 0;
|
||
}
|
||
|
||
static int bytes_eq(const uint8_t *p_a, const uint8_t *p_b, size_t n)
|
||
{
|
||
return memcmp(p_a, p_b, n) == 0;
|
||
}
|
||
|
||
/* ===================== 回调 ===================== */
|
||
|
||
static void apply_cb(void)
|
||
{
|
||
g_apply_calls++;
|
||
}
|
||
|
||
static void req_cb(void)
|
||
{
|
||
g_req_cb_calls++;
|
||
}
|
||
|
||
/* 写盘时刻钩子:记录当时的交接状态,并按开关模拟"写盘期间 UI 又按了一次 ENTER" */
|
||
static void save_hook_probe(void)
|
||
{
|
||
g_state_during_save = net_cfg_save_state();
|
||
if (g_requeue_during_save != 0) {
|
||
(void)net_cfg_save_request();
|
||
}
|
||
}
|
||
|
||
/* ===================== 出厂默认(必须在任何 load 之前跑) ===================== */
|
||
|
||
static void tc_defaults(void)
|
||
{
|
||
const net_cfg_t *p = net_cfg_get();
|
||
static const uint8_t def_ip[4] = { 192u, 168u, 1u, 100u };
|
||
static const uint8_t def_mask[4] = { 255u, 255u, 255u, 0u };
|
||
static const uint8_t def_gw[4] = { 192u, 168u, 1u, 1u };
|
||
static const uint8_t zero[6] = { 0u, 0u, 0u, 0u, 0u, 0u };
|
||
|
||
printf("-- TC-NETCFG-001..004 defaults before load --\n");
|
||
CHECK(bytes_eq(p->ip, def_ip, 4u), "TC-NETCFG-001 ip = 192.168.1.100");
|
||
CHECK(bytes_eq(p->mask, def_mask, 4u), "TC-NETCFG-002 mask = 255.255.255.0");
|
||
CHECK(bytes_eq(p->gw, def_gw, 4u), "TC-NETCFG-003 gw = 192.168.1.1");
|
||
CHECK(mac_eq(p->mac, zero), "TC-NETCFG-004 mac = all zero (chip built-in sentinel)");
|
||
}
|
||
|
||
/* ===================== 注册与 load 契约 ===================== */
|
||
|
||
static void tc_register_load(void)
|
||
{
|
||
int rc;
|
||
|
||
printf("-- TC-NETCFG-005..011 register + load --\n");
|
||
fake_reset();
|
||
rc = net_cfg_load();
|
||
CHECK((rc == 0) && (g_reg_n == 1) && (strcmp(g_regs[0].name, "network") == 0),
|
||
"TC-NETCFG-005 first load registers [network] exactly once");
|
||
CHECK((g_ftp_reg_calls == 1) && (g_load_calls == 1),
|
||
"TC-NETCFG-006 also registers [ftp], registered before load");
|
||
|
||
rc = net_cfg_load();
|
||
CHECK((rc == 0) && (g_reg_n == 1) && (g_ftp_reg_calls == 2),
|
||
"TC-NETCFG-007 repeated load does not re-register [network]");
|
||
|
||
CHECK((strcmp(g_regs[0].name, "network") == 0) &&
|
||
(g_regs[0].data_size == sizeof(net_cfg_t)) &&
|
||
(g_regs[0].key_count == 4u) &&
|
||
(g_regs[0].p_defaults != NULL) &&
|
||
(g_regs[0].p_cb != NULL) &&
|
||
(g_regs[0].p_data == (const void *)&s_cfg),
|
||
"TC-NETCFG-008 registration content: size/key count/defaults/callback/data pointer = RAM copy");
|
||
|
||
fake_reset();
|
||
rc = net_cfg_load();
|
||
CHECK((rc == 0) && (g_netcfg_info_count == 1) && (g_netcfg_error_count == 0),
|
||
"TC-NETCFG-009 load success: returns 0 + one INFO line");
|
||
|
||
fake_reset();
|
||
g_load_rc = -1;
|
||
rc = net_cfg_load();
|
||
CHECK(rc == 0, "TC-NETCFG-010 engine -1 still returns 0 (RAM copy is always valid, by contract)");
|
||
CHECK(g_netcfg_error_count == 1, "TC-NETCFG-011 load failure must leave an ERROR line");
|
||
}
|
||
|
||
/* ===================== RAM 副本读写 ===================== */
|
||
|
||
static void tc_set_get(void)
|
||
{
|
||
net_cfg_t c;
|
||
static const uint8_t ip[4] = { 10u, 0u, 0u, 7u };
|
||
static const uint8_t mac[6] = { 0x02u, 0x11u, 0x22u, 0x33u, 0x44u, 0x55u };
|
||
|
||
printf("-- TC-NETCFG-012..013 set/get --\n");
|
||
c = *net_cfg_get();
|
||
memcpy(c.ip, ip, 4u);
|
||
memcpy(c.mac, mac, 6u);
|
||
net_cfg_set(&c);
|
||
CHECK(bytes_eq(net_cfg_get()->ip, ip, 4u) &&
|
||
mac_eq(net_cfg_get()->mac, mac), "TC-NETCFG-012 set is read back by get");
|
||
|
||
net_cfg_set(NULL);
|
||
CHECK(bytes_eq(net_cfg_get()->ip, ip, 4u), "TC-NETCFG-013 set(NULL) rewrites nothing");
|
||
}
|
||
|
||
/* ===================== 直接保存 ===================== */
|
||
|
||
static void tc_save_direct(void)
|
||
{
|
||
int rc;
|
||
|
||
printf("-- TC-NETCFG-014..015 save --\n");
|
||
fake_reset();
|
||
rc = net_cfg_save();
|
||
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-014 write success returns 0");
|
||
|
||
fake_reset();
|
||
g_save_rc = -1;
|
||
rc = net_cfg_save();
|
||
CHECK((rc == -1) && (g_netcfg_error_count == 1),
|
||
"TC-NETCFG-015 write failure returns -1 and leaves a trace");
|
||
}
|
||
|
||
/* ===================== 跨任务保存交接状态机 ===================== */
|
||
|
||
static void tc_save_handoff(void)
|
||
{
|
||
int rc;
|
||
|
||
printf("-- TC-NETCFG-020..033 save handoff state machine --\n");
|
||
fake_reset();
|
||
net_cfg_set_apply_cb(NULL);
|
||
net_cfg_set_save_req_cb(NULL);
|
||
s_save_pending = 0u; /* 白盒复位:上一组的 save() 不碰 pending/result,此处 */
|
||
s_save_result = 0u; /* 只是防止将来有人调整后漏出脏态 */
|
||
CHECK(net_cfg_save_state() == NET_CFG_SAVE_ST_IDLE, "TC-NETCFG-020 initial state IDLE");
|
||
|
||
CHECK((net_cfg_save_request() == 0) &&
|
||
(net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT), "TC-NETCFG-021 WAIT after posting");
|
||
|
||
net_cfg_set_save_req_cb(req_cb);
|
||
g_req_cb_calls = 0;
|
||
(void)net_cfg_save_request();
|
||
CHECK(g_req_cb_calls == 1, "TC-NETCFG-022 posting wakes the owner task through the callback");
|
||
|
||
net_cfg_set_save_req_cb(NULL);
|
||
(void)net_cfg_save_request();
|
||
CHECK((g_req_cb_calls == 1) &&
|
||
(net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT),
|
||
"TC-NETCFG-023 no callback registered -> no-op, request still pending");
|
||
|
||
g_save_rc = 0;
|
||
g_save_calls = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-024a with a pending request it really writes once");
|
||
g_save_calls = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (g_save_calls == 0),
|
||
"TC-NETCFG-024 no request: empty wake does not write (2 s polling never flushes Flash)");
|
||
|
||
(void)net_cfg_save_request();
|
||
net_cfg_set_apply_cb(apply_cb);
|
||
g_apply_calls = 0;
|
||
g_save_rc = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (s_save_pending == 0u), "TC-NETCFG-025 pending cleared after success");
|
||
CHECK((net_cfg_save_state() == NET_CFG_SAVE_ST_OK) && (s_save_busy == 0u),
|
||
"TC-NETCFG-026 state OK after success and the write flag is cleared");
|
||
CHECK(g_apply_calls == 1, "TC-NETCFG-027 success triggers one immediate apply");
|
||
|
||
(void)net_cfg_save_request();
|
||
g_save_hook = save_hook_probe;
|
||
g_state_during_save = -1;
|
||
g_apply_calls = 0;
|
||
g_save_rc = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (g_state_during_save == (int)NET_CFG_SAVE_ST_WAIT),
|
||
"TC-NETCFG-028 state stays WAIT while the write is in progress");
|
||
|
||
/* 写盘期间再投递一次:本轮不得把它当已处理,下轮必须真的再写 */
|
||
g_requeue_during_save = 1;
|
||
(void)net_cfg_save_request();
|
||
g_save_calls = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (net_cfg_save_state() == NET_CFG_SAVE_ST_WAIT),
|
||
"TC-NETCFG-029a a new request during the write is not lost");
|
||
g_requeue_during_save = 0;
|
||
g_save_hook = NULL;
|
||
g_save_calls = 0;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == 0) && (g_save_calls == 1), "TC-NETCFG-029b the next round really writes once more");
|
||
|
||
/* 写盘失败:置 FAIL 且**不得**触发应用(内存值没落盘,网络不能重建) */
|
||
s_save_pending = 0u;
|
||
s_save_result = 0u;
|
||
g_apply_calls = 0;
|
||
(void)net_cfg_save_request();
|
||
g_save_rc = -1;
|
||
rc = net_cfg_save_run();
|
||
CHECK((rc == -1) && (net_cfg_save_state() == NET_CFG_SAVE_ST_FAIL) &&
|
||
(s_save_busy == 0u),
|
||
"TC-NETCFG-030 failure sets FAIL and clears the write flag (else UI sticks in WAIT forever)");
|
||
CHECK(g_apply_calls == 0, "TC-NETCFG-031 write failure must not trigger an immediate apply");
|
||
|
||
net_cfg_set_apply_cb(NULL);
|
||
g_apply_calls = 0;
|
||
net_cfg_request_apply();
|
||
CHECK(g_apply_calls == 0, "TC-NETCFG-032 apply is a no-op while no callback is registered");
|
||
|
||
net_cfg_set_apply_cb(apply_cb);
|
||
net_cfg_request_apply();
|
||
CHECK(g_apply_calls == 1, "TC-NETCFG-033a after registering it can be triggered");
|
||
net_cfg_set_apply_cb(NULL);
|
||
net_cfg_request_apply();
|
||
CHECK(g_apply_calls == 1, "TC-NETCFG-033b after unregistering it is no longer triggered");
|
||
}
|
||
|
||
/* ===================== MAC 三态优先级 ===================== */
|
||
|
||
static void tc_mac_priority(void)
|
||
{
|
||
net_cfg_t c;
|
||
uint8_t out[6];
|
||
static const uint8_t stored[6] = { 0xAAu, 0xBBu, 0xCCu, 0x11u, 0x22u, 0x33u };
|
||
static const uint8_t shadow[6] = { 0x02u, 0x00u, 0x00u, 0x01u, 0x02u, 0x03u };
|
||
static const uint8_t zero[6] = { 0u, 0u, 0u, 0u, 0u, 0u };
|
||
|
||
printf("-- TC-NETCFG-040..045 mac display priority --\n");
|
||
|
||
c = *net_cfg_get();
|
||
memcpy(c.mac, stored, 6u);
|
||
net_cfg_set(&c);
|
||
net_cfg_set_active_mac(shadow);
|
||
memset(out, 0xFF, sizeof(out));
|
||
net_cfg_get_mac(out);
|
||
CHECK(mac_eq(out, stored), "TC-NETCFG-040 stored non-zero takes precedence over the chip shadow");
|
||
|
||
c = *net_cfg_get();
|
||
memcpy(c.mac, zero, 6u);
|
||
net_cfg_set(&c);
|
||
memset(out, 0xFF, sizeof(out));
|
||
net_cfg_get_mac(out);
|
||
CHECK(mac_eq(out, shadow), "TC-NETCFG-041 stored all zero -> use the chip shadow write-back");
|
||
|
||
s_active_valid = 0u; /* 白盒:回到"芯片还没回写"的上电初态 */
|
||
memset(out, 0xFF, sizeof(out));
|
||
net_cfg_get_mac(out);
|
||
CHECK(mac_eq(out, zero), "TC-NETCFG-042 neither available -> all zero");
|
||
|
||
net_cfg_set_active_mac(NULL);
|
||
CHECK(s_active_valid == 0u, "TC-NETCFG-043 NULL write-back must not mark the shadow valid");
|
||
|
||
net_cfg_set_active_mac(shadow);
|
||
memset(out, 0xFF, sizeof(out));
|
||
net_cfg_get_mac(out);
|
||
CHECK(mac_eq(out, shadow), "TC-NETCFG-044 shadow becomes active after a normal write-back");
|
||
|
||
net_cfg_get_mac(NULL);
|
||
CHECK(1, "TC-NETCFG-045 get_mac(NULL) returns safely");
|
||
}
|
||
|
||
/* ===================== IPv4 定长格式化 ===================== */
|
||
|
||
static void tc_fmt_ipv4(void)
|
||
{
|
||
static const uint8_t ip1[4] = { 192u, 168u, 1u, 100u };
|
||
static const uint8_t ip0[4] = { 0u, 0u, 0u, 0u };
|
||
static const uint8_t ipm[4] = { 255u, 255u, 255u, 255u };
|
||
char buf[24];
|
||
char guard[24];
|
||
|
||
printf("-- TC-NETCFG-050..056 fixed-width ipv4 format --\n");
|
||
|
||
memset(buf, 0x7A, sizeof(buf));
|
||
net_cfg_fmt_ipv4(ip1, buf, sizeof(buf));
|
||
/* 定长 = 4 段 × 3 位 + 3 个点 = 15(曾有误记为 11 的注释,已同日更正)
|
||
* 钉死这个长度是 UI 光标偏移(段 i 起点 4i、点号 3/7/11)的前提 */
|
||
CHECK((strcmp(buf, "192.168.001.100") == 0) && (strlen(buf) == 15u),
|
||
"TC-NETCFG-050 fixed 15 chars with zero padding in every segment");
|
||
|
||
memset(buf, 0x7A, sizeof(buf));
|
||
net_cfg_fmt_ipv4(ip0, buf, sizeof(buf));
|
||
CHECK(strcmp(buf, "000.000.000.000") == 0, "TC-NETCFG-051 all zero still fills 15 chars");
|
||
|
||
memset(buf, 0x7A, sizeof(buf));
|
||
net_cfg_fmt_ipv4(ipm, buf, sizeof(buf));
|
||
CHECK(strcmp(buf, "255.255.255.255") == 0, "TC-NETCFG-052 255 boundary");
|
||
|
||
memset(guard, 0x7A, sizeof(guard));
|
||
memcpy(buf, guard, sizeof(buf));
|
||
net_cfg_fmt_ipv4(NULL, buf, sizeof(buf));
|
||
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-053 p_ip NULL does not touch the buffer");
|
||
|
||
net_cfg_fmt_ipv4(ip1, NULL, 16u);
|
||
CHECK(1, "TC-NETCFG-054 p_buf NULL does not crash");
|
||
|
||
memset(guard, 0x7A, sizeof(guard));
|
||
memcpy(buf, guard, sizeof(buf));
|
||
net_cfg_fmt_ipv4(ip1, buf, 0u);
|
||
CHECK(memcmp(buf, guard, sizeof(buf)) == 0, "TC-NETCFG-055 len 0 does not touch the buffer");
|
||
|
||
memset(buf, 0x7A, sizeof(buf));
|
||
net_cfg_fmt_ipv4(ip1, buf, 5u);
|
||
CHECK((strlen(buf) == 4u) && (buf[4] == '\0') && (buf[5] == (char)0x7A),
|
||
"TC-NETCFG-056 short len: truncate but must stay NUL-terminated and in bounds");
|
||
}
|
||
|
||
/* ===================== 入口 ===================== */
|
||
|
||
int main(void)
|
||
{
|
||
printf("=== NET Config Tests ===\n");
|
||
|
||
tc_defaults(); /* 必须最先:任何 load 之前 */
|
||
tc_register_load();
|
||
tc_set_get();
|
||
tc_save_direct();
|
||
tc_save_handoff();
|
||
tc_mac_priority();
|
||
tc_fmt_ipv4();
|
||
|
||
printf("\n=== NET Config Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|