/* * 模块名称:网络配置(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 里直接重定义。) * - [sntp] 段:描述符由 net_cfg.c 的 s_sntp_register() 组装(分层 * 门禁后 sntp_cfg.c 不再自注册),所以本测试直接**编译真实的 * sntp_cfg.c 数据本体**(纯 stdint/string,无引擎依赖,改名后的 * s_sntp_cfg 与 net_cfg.c 的 s_cfg 不再同名冲突),并用假引擎 * 截获注册内容——段键表/偏移/默认值的机器钉在这里打。 * ⚠ 引擎的解析/写盘/沉降契约已由 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.31.100 * TC-NETCFG-002 未 load 时 mask = 255.255.255.0 * TC-NETCFG-003 未 load 时 gw = 192.168.31.1 * TC-NETCFG-004 未 load 时 mac = 全零(哨兵 = 不覆盖芯片内置 MAC) * —— 注册与 load(TC-NETCFG-005..011)—— * TC-NETCFG-005 首次 load 注册 [network]+[sntp] 各恰好一次(且 load 返回 0) * TC-NETCFG-006 首次 load 一并注册 [ftp] 段(FTP 鉴权:所有受管段先注册、后 load) * TC-NETCFG-006b [sntp] 描述符内容:段名/键表(enable/server1/server2/poll_min * 偏移 0/1/5/10 长 1/4/4/2)/默认值(关 + 60 分钟)/无变更回调 * 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 #include /* 真实引擎头(接口原汁)+ 被测生产源(原样纳入);dbg_log.h 命中本目录 stub */ #include "cfg_file.h" #include "net_cfg.h" #include "sntp_cfg.h" #include "net_cfg.c" #include "sntp_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; const cfg_key_def_t *p_keys; /* 键表是生产文件里的 static const,指针长期有效,不必深拷贝 */ 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_keys = p_reg->p_keys; 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, 31u, 100u }; static const uint8_t def_mask[4] = { 255u, 255u, 255u, 0u }; static const uint8_t def_gw[4] = { 192u, 168u, 31u, 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.31.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.31.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 == 2) && (strcmp(g_regs[0].name, "network") == 0), "TC-NETCFG-005 first load registers [network] + [sntp] exactly once each"); CHECK((g_ftp_reg_calls == 1) && (g_load_calls == 1), "TC-NETCFG-006 also registers [ftp], registered before load"); { const fake_reg_t *rs = (g_reg_n == 2) ? &g_regs[1] : NULL; const sntp_cfg_t *d = (const sntp_cfg_t *)(rs != NULL ? rs->p_defaults : NULL); int keys_ok = (rs != NULL) && (rs->key_count == 4u) && (rs->p_keys != NULL); CHECK((rs != NULL) && (strcmp(rs->name, "sntp") == 0) && keys_ok && (rs->data_size == sizeof(sntp_cfg_t)) && (rs->p_data == (const void *)sntp_cfg_data()) && (rs->p_cb == NULL), "TC-NETCFG-006b [sntp] descriptor: name/4 keys/size/RAM copy/no cb"); CHECK(keys_ok && (strcmp(rs->p_keys[0].name, "enable") == 0) && (rs->p_keys[0].type == CFG_VAL_UINT) && (rs->p_keys[0].offset == 0u) && (rs->p_keys[0].len == 1u) && (strcmp(rs->p_keys[1].name, "server1") == 0) && (rs->p_keys[1].type == CFG_VAL_IP4) && (rs->p_keys[1].offset == 1u) && (rs->p_keys[1].len == 4u) && (strcmp(rs->p_keys[2].name, "server2") == 0) && (rs->p_keys[2].type == CFG_VAL_IP4) && (rs->p_keys[2].offset == 5u) && (rs->p_keys[2].len == 4u) && (strcmp(rs->p_keys[3].name, "poll_min") == 0) && (rs->p_keys[3].type == CFG_VAL_UINT) && (rs->p_keys[3].offset == 10u) && (rs->p_keys[3].len == 2u), "TC-NETCFG-006c [sntp] key table: enable/server1/server2/poll_min at 0/1/5/10"); CHECK((d != NULL) && (d->enable == 0u) && (d->poll_min == 60u) && (d->server_pri[0] == 0u) && (d->server_bak[0] == 0u), "TC-NETCFG-006d [sntp] factory defaults: off + empty servers + 60 min"); } rc = net_cfg_load(); CHECK((rc == 0) && (g_reg_n == 2) && (g_ftp_reg_calls == 2), "TC-NETCFG-007 repeated load does not re-register [network]/[sntp]"); 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; }