7efa58d9a1
分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。
一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*
二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
(DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
实践)、文档正文 IP(那是文档内容本身)
顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红
验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
1082 lines
43 KiB
C
1082 lines
43 KiB
C
/*
|
||
* 模块名称:cfg_file 主机侧单元测试
|
||
* 模块功能:在 PC 上对 App/cfg/cfg_file.c(通用配置引擎核心)做穷举验证。
|
||
*
|
||
* 被测对象:真实源文件 ../../../App/cfg/cfg_file.c(以 #include 原样纳入,
|
||
* 保证测的是生产代码而非副本)。平台文件操作由本文件提供的
|
||
* **内存虚拟文件系统**桩实现(cfg_store_* 系列强符号)。
|
||
*
|
||
* 为什么要主机侧单测(而不是只用板端套件):
|
||
* 1. 配置引擎的核心是"文本 <-> 结构"的纯变换(INI 段/键解析、五种值类型
|
||
* 编解码、外科手术式保存、超长守卫),与硬件无关;板端套件无法穷举
|
||
* 畸形输入,更无法在毫秒内构造"超长文件 / 写失败 / 堆分配失败"。
|
||
* 2. 主机侧可**精确注入**故障:文件缺失、读取失败、写失败、分配失败——
|
||
* 这些在真机上极难复现(需掉电/坏块)。
|
||
* 3. 不占 MCU 的 RAM。
|
||
*
|
||
* 覆盖点(对应 docs/测试说明/单元测试/配置文件单元测试说明.md 的 TC-CFG-xxx):
|
||
* —— 值类型编解码(s_encode / s_decode 经公开 load/save 间接验证 + 直接白盒)——
|
||
* TC-CONFIG-001 UINT 1/2/4 字节小端往返(含 0 / 最大值)
|
||
* TC-CONFIG-002 UINT 非法:空 / 非数字 / 带后缀 / 超范围
|
||
* TC-CONFIG-003 IP4 正常与非法(段数不足、>255、前导非数字、多余后缀)
|
||
* TC-CONFIG-004 MAC 三种分隔符 + 大小写 + 非法
|
||
* TC-CONFIG-005 STR 正常 / 超长拒绝 / 边界(恰好放下)
|
||
* TC-CONFIG-006 INT 合法解码:0 / -1 / INT32_MIN / INT32_MAX / 各宽度边界 /
|
||
* 前导 + / 前导 0(「数据可信」有符号零点类型:负零点偏移可落盘)
|
||
* TC-CONFIG-007 INT 非法拒绝:空 / 非数字 / 带后缀 / 越界(含 strtol 饱和带,
|
||
* 即超 INT32 但钳位后恰落界内的文本)/ 坏 len
|
||
* TC-CONFIG-008 INT 编码带负号输出 + 注册校验(INT len=3 拒绝)+
|
||
* [sys] 段 load/save/load 全链往返一致(出厂模板带负号默认)
|
||
* —— INI 解析(s_split_line / s_match_section / s_key_value / s_parse)——
|
||
* TC-CONFIG-010 注释(; 与 #)与空行忽略
|
||
* TC-CONFIG-011 CRLF / LF 混排
|
||
* TC-CONFIG-012 段名大小写无关匹配、前后空白修剪
|
||
* TC-CONFIG-013 键名大小写无关、值前后空白修剪
|
||
* TC-CONFIG-014 未知段 / 未知键留默认(不崩溃)
|
||
* TC-CONFIG-015 键值行出现在段头之前 = 忽略
|
||
* —— 注册校验(cfg_file_register)——
|
||
* TC-CONFIG-020 正常注册 / 重复段名拒绝 / NULL 参数拒绝
|
||
* 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 拒绝
|
||
* 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受
|
||
* —— load 契约(cfg_file_load)——
|
||
* TC-CONFIG-030 文件缺失 -> 生成出厂模板写盘 + 返回 0
|
||
* TC-CONFIG-031 文件存在 -> 解析有效键进 RAM 副本
|
||
* TC-CONFIG-032 超长文件(> CFG_FILE_MAX_LEN)-> 工厂默认 + 重建模板 + 自检留痕
|
||
* TC-CONFIG-033 读取失败 -> 返回 -1(RAM 保持工厂默认)
|
||
* TC-CONFIG-034 未注册任何段 -> 返回 -1
|
||
* —— save 契约(cfg_file_save)——
|
||
* TC-CONFIG-040 文件缺失 -> 重建完整文档
|
||
* TC-CONFIG-041 外科手术:保留注释/未知段/未知键,受管键原位改写
|
||
* TC-CONFIG-042 缺失受管段/键补写
|
||
* TC-CONFIG-043 输出超长 -> 拒绝保存(-1) 且原文件不动
|
||
* 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动;
|
||
* 非受管键的超长行照旧放过(原样保留)
|
||
* —— poll 契约(cfg_file_poll)——
|
||
* TC-CONFIG-050 首轮建立基线(不重载)
|
||
* TC-CONFIG-051 沉降去抖:连续 2 次稳定才重载
|
||
* 与基线一致不重载
|
||
* TC-CONFIG-053 变更后重载 + 挂回调段差异触发回调
|
||
* TC-CONFIG-054 溢出到文件超长 -> 工厂默认 + 重建模板 + 自检留痕(不触发回调)
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-02
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
|
||
/* 先纳入桩头(DBG_* 映射为计数),再纳入生产源码 */
|
||
#include "dbg_log.h"
|
||
|
||
/* ===================== 内存虚拟文件系统(cfg_store_* 强符号) =====================
|
||
*
|
||
* 生产代码经 cfg_file_store.h 声明的 8 个接口做全部文件/堆操作。本桩提供:
|
||
* - 一块固定容量的内存"磁盘"(模拟 config.ini 的字节内容)
|
||
* - 统计 read/write/alloc 失败注入开关(复现真机极难构造的故障路径)
|
||
* - stat 的 (size, mtime) 令牌:mtime 由单测手动改,驱动 poll 的沉降逻辑
|
||
*/
|
||
|
||
#include "cfg_file_store.h"
|
||
|
||
#define VFS_CAP 8192u /* 虚拟盘容量(> CFG_FILE_MAX_LEN,便于构造超长 */
|
||
|
||
/* 虚拟文件:内容 + 长度 + mtime 令牌 + 是否存在 */
|
||
static uint8_t s_vfs[VFS_CAP];
|
||
static size_t s_vfs_len = 0u;
|
||
static uint32_t s_vfs_mtime = 100u;
|
||
static int s_vfs_exists = 0;
|
||
|
||
/* 故障注入开关 */
|
||
static int s_fail_read = 0; /* cfg_store_read 返回 -1 */
|
||
static int s_fail_write = 0; /* cfg_store_write_atomic 返回 -1 */
|
||
static int s_fail_alloc = 0; /* cfg_store_alloc 返回 NULL */
|
||
static int s_fail_stat = 0; /* cfg_store_stat 返回 -1 */
|
||
|
||
/* 观测计数 */
|
||
static int s_write_count = 0;
|
||
static int s_read_count = 0;
|
||
|
||
/* 路径常量(与生产逻辑无关,仅作为桩内部标识) */
|
||
static const char *s_cfg_path = "/config.ini";
|
||
static const char *s_tmp_path = "/config.tmp";
|
||
|
||
/* ---- 堆:直接映射宿主 CRT(单测不模拟 MicroLIB 512B 堆约束) ---- */
|
||
void *cfg_store_alloc(size_t size) {
|
||
if (s_fail_alloc) {
|
||
return NULL;
|
||
}
|
||
return malloc(size);
|
||
}
|
||
|
||
void cfg_store_free(void *p_buf) {
|
||
free(p_buf);
|
||
}
|
||
|
||
uint32_t cfg_store_heap_free(void) {
|
||
return 0u; /* 模拟器语义:不跟踪宿主堆 */
|
||
}
|
||
|
||
/* ---- 文件操作 ---- */
|
||
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
|
||
size_t n;
|
||
|
||
(void)p_path;
|
||
s_read_count++;
|
||
if (s_fail_read || !s_vfs_exists) {
|
||
return -1;
|
||
}
|
||
n = s_vfs_len;
|
||
if (n > cap) {
|
||
n = cap;
|
||
}
|
||
(void)memcpy(p_buf, s_vfs, n);
|
||
*p_nread = n;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
|
||
(void)p_path;
|
||
s_write_count++;
|
||
if (s_fail_write) {
|
||
return -1;
|
||
}
|
||
if (len > VFS_CAP) {
|
||
return -1; /* 虚拟盘放不下 */
|
||
}
|
||
(void)memcpy(s_vfs, p_buf, len);
|
||
s_vfs_len = len;
|
||
s_vfs_exists = 1;
|
||
/* 真实后端每次写盘 mtime 必变(分钟粒度,同分钟内可能不变);
|
||
* 单测里显式 +1 让"写盘 = 令牌变化"确定可预期 */
|
||
s_vfs_mtime++;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
|
||
(void)p_path;
|
||
if (s_fail_stat || !s_vfs_exists) {
|
||
return -1;
|
||
}
|
||
p_st->size = (uint32_t)s_vfs_len;
|
||
p_st->mtime = s_vfs_mtime;
|
||
return 0;
|
||
}
|
||
|
||
int cfg_store_remove(const char *p_path) {
|
||
(void)p_path;
|
||
s_vfs_exists = 0;
|
||
s_vfs_len = 0u;
|
||
return 0;
|
||
}
|
||
|
||
const char *cfg_store_config_path(void) { return s_cfg_path; }
|
||
const char *cfg_store_tmp_path(void) { return s_tmp_path; }
|
||
|
||
/* ---- sys_log 桩:只计数自检留痕(不落盘) ---- */
|
||
#include "sys_log.h"
|
||
|
||
static int s_syslog_count = 0;
|
||
|
||
void syslog_post(syslog_id_t id, const char *desc) {
|
||
(void)id;
|
||
(void)desc;
|
||
s_syslog_count++;
|
||
}
|
||
|
||
/* ---- dbg_log 桩观测计数定义(stub/dbg_log.h 声明为 extern) ---- */
|
||
int g_cfgtest_err_count = 0;
|
||
int g_cfgtest_info_count = 0;
|
||
int g_cfgtest_debug_count = 0;
|
||
|
||
/* ===================== 测试框架(极简) ===================== */
|
||
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||
|
||
#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)
|
||
|
||
/* ===================== 被测代码 ===================== */
|
||
/* 原样纳入生产源文件(stub 的 dbg_log.h 生效) */
|
||
#include "cfg_file.c"
|
||
|
||
/* ===================== 测试用段定义 ===================== */
|
||
|
||
/* 段 A:[net],含四种值类型,data_size ≤ 64 可挂回调 */
|
||
typedef struct {
|
||
uint8_t u8; /* offset 0 len 1 UINT */
|
||
uint16_t u16; /* offset 2 len 2 UINT(对齐后) */
|
||
uint8_t ip[4]; /* offset 4 len 4 IP4 */
|
||
uint8_t mac[6]; /* offset 8 len 6 MAC */
|
||
char name[12]; /* offset 14 len 12 STR */
|
||
} sec_net_t;
|
||
|
||
static sec_net_t s_net_data;
|
||
static const sec_net_t s_net_def = {
|
||
7u, 1000u, {192u, 168u, 1u, 100u}, {0x00u, 0x11u, 0x22u, 0x33u, 0x44u, 0x55u}, "default"
|
||
};
|
||
|
||
static int s_net_cb_count = 0;
|
||
static void net_on_change(void) { s_net_cb_count++; }
|
||
|
||
/* 注意:键定义 offset 相对结构体起始,按字段真实偏移书写 */
|
||
static const cfg_key_def_t s_net_keys[] = {
|
||
{ "u8", CFG_VAL_UINT, offsetof(sec_net_t, u8), 1u },
|
||
{ "u16", CFG_VAL_UINT, offsetof(sec_net_t, u16), 2u },
|
||
{ "ip", CFG_VAL_IP4, offsetof(sec_net_t, ip), 4u },
|
||
{ "mac", CFG_VAL_MAC, offsetof(sec_net_t, mac), 6u },
|
||
{ "name", CFG_VAL_STR, offsetof(sec_net_t, name), 12u },
|
||
};
|
||
|
||
/* 段 B:[sys],无回调,用于验证多段、超长字段拒绝与 INT 有符号往返(有符号零点类型)。
|
||
* 字段按自然对齐排布:u32@0 / i8@4 / i16@6 / i32@8,offset 用 offsetof 取真值 */
|
||
typedef struct {
|
||
uint32_t baud; /* offset 0 len 4 UINT */
|
||
int8_t ofs8; /* offset 4 len 1 INT */
|
||
int16_t ofs16; /* offset 6 len 2 INT(负默认:模板须带负号写出) */
|
||
int32_t ofs32; /* offset 8 len 4 INT(默认 INT32_MIN:极值往返) */
|
||
} sec_sys_t;
|
||
|
||
static sec_sys_t s_sys_data;
|
||
static const sec_sys_t s_sys_def = { 115200u, -5, -300, (-2147483647 - 1) };
|
||
|
||
static const cfg_key_def_t s_sys_keys[] = {
|
||
{ "baud", CFG_VAL_UINT, offsetof(sec_sys_t, baud), 4u },
|
||
{ "ofs8", CFG_VAL_INT, offsetof(sec_sys_t, ofs8), 1u },
|
||
{ "ofs16", CFG_VAL_INT, offsetof(sec_sys_t, ofs16), 2u },
|
||
{ "ofs32", CFG_VAL_INT, offsetof(sec_sys_t, ofs32), 4u },
|
||
};
|
||
|
||
/* ===================== 辅助 ===================== */
|
||
|
||
/* 复位"模块静态态 + 虚拟盘 + 全部观测计数" = 刚上电 */
|
||
static void vfs_reset(void) {
|
||
memset(s_vfs, 0, sizeof(s_vfs));
|
||
s_vfs_len = 0u;
|
||
s_vfs_mtime = 100u;
|
||
s_vfs_exists = 0;
|
||
s_fail_read = s_fail_write = s_fail_alloc = s_fail_stat = 0;
|
||
s_write_count = s_read_count = 0;
|
||
s_syslog_count = 0;
|
||
g_cfgtest_err_count = 0;
|
||
g_cfgtest_info_count = 0;
|
||
g_cfgtest_debug_count = 0;
|
||
s_net_cb_count = 0;
|
||
/* 清空段注册表(生产静态态无公开复位接口,测试用直接 memset——
|
||
* 这是白盒单测的可接受代价,模块静态态布局在头文件可见) */
|
||
memset(s_sections, 0, sizeof(s_sections));
|
||
memset(&s_base, 0, sizeof(s_base));
|
||
memset(&s_last, 0, sizeof(s_last));
|
||
s_stable = 0u;
|
||
s_base_valid = 0u;
|
||
memset(s_snap, 0, sizeof(s_snap));
|
||
memset(s_sec_hdr, 0, sizeof(s_sec_hdr));
|
||
memset(s_key_line, 0, sizeof(s_key_line));
|
||
/* 段 RAM 副本复位工厂默认 */
|
||
s_net_data = s_net_def;
|
||
s_sys_data = s_sys_def;
|
||
}
|
||
|
||
/* 向虚拟盘写入一段文本(建立/更新"文件已存在"状态)。
|
||
* 语义 = 一次外部写盘:内容变 + mtime 令牌变(真实后端每次写盘 mtime 必变,
|
||
* 是 poll 变更检测的信号源)。需精确控制令牌时用 vfs_put_notoken()。 */
|
||
static void vfs_put(const char *text) {
|
||
size_t n = strlen(text);
|
||
if (n > VFS_CAP) {
|
||
n = VFS_CAP;
|
||
}
|
||
memcpy(s_vfs, text, n);
|
||
s_vfs_len = n;
|
||
s_vfs_exists = 1;
|
||
s_vfs_mtime++;
|
||
}
|
||
|
||
/* 写入内容但**不改** mtime 令牌(用于构造"内容变、令牌不变"的边界) */
|
||
static void vfs_put_notoken(const char *text) {
|
||
size_t n = strlen(text);
|
||
if (n > VFS_CAP) {
|
||
n = VFS_CAP;
|
||
}
|
||
memcpy(s_vfs, text, n);
|
||
s_vfs_len = n;
|
||
s_vfs_exists = 1;
|
||
}
|
||
|
||
/* 取虚拟盘当前内容为 NUL 结尾字符串(供检索断言);返回静态缓冲 */
|
||
static const char *vfs_get(void) {
|
||
static char buf[VFS_CAP + 1];
|
||
memcpy(buf, s_vfs, s_vfs_len);
|
||
buf[s_vfs_len] = '\0';
|
||
return buf;
|
||
}
|
||
|
||
/* 注册标准两段 */
|
||
static void register_std(void) {
|
||
cfg_section_reg_t a;
|
||
cfg_section_reg_t b;
|
||
|
||
memset(&a, 0, sizeof(a));
|
||
a.name = "net";
|
||
a.p_data = &s_net_data;
|
||
a.p_defaults = &s_net_def;
|
||
a.data_size = sizeof(sec_net_t);
|
||
a.p_keys = s_net_keys;
|
||
a.key_count = (uint8_t)(sizeof(s_net_keys) / sizeof(s_net_keys[0]));
|
||
a.p_cb = net_on_change;
|
||
|
||
memset(&b, 0, sizeof(b));
|
||
b.name = "sys";
|
||
b.p_data = &s_sys_data;
|
||
b.p_defaults = &s_sys_def;
|
||
b.data_size = sizeof(sec_sys_t);
|
||
b.p_keys = s_sys_keys;
|
||
b.key_count = (uint8_t)(sizeof(s_sys_keys) / sizeof(s_sys_keys[0]));
|
||
b.p_cb = NULL;
|
||
|
||
CHECK(cfg_file_register(&a) == 0, "register net section");
|
||
CHECK(cfg_file_register(&b) == 0, "register sys section");
|
||
}
|
||
|
||
|
||
/* 容量钉专用:把注册表填到指定段数(名字 s0..sN,共用 net 段 RAM/键表) */
|
||
static int register_fill(int n)
|
||
{
|
||
char nm[4];
|
||
int i;
|
||
int ok = 1;
|
||
|
||
for (i = 0; i < n; i++) {
|
||
cfg_section_reg_t r;
|
||
memset(&r, 0, sizeof(r));
|
||
nm[0] = 's'; nm[1] = (char)('0' + (i / 10) % 10); nm[2] = (char)('0' + (i % 10)); nm[3] = 0;
|
||
r.name = nm;
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = sizeof(sec_net_t);
|
||
r.p_keys = s_net_keys;
|
||
r.key_count = (uint8_t)(sizeof(s_net_keys) / sizeof(s_net_keys[0]));
|
||
r.p_cb = NULL;
|
||
if (cfg_file_register(&r) != 0) {
|
||
ok = 0;
|
||
}
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
/* TC-CONFIG-056/057:段数与键数容量钉(道G task-8——[comm] 上线后生产占 6/8,
|
||
* 容量与"满则拒"行为必须先在这里显式化,别让第 7 段的接入者在真机上才发现顶格) */
|
||
static void tc_capacity(void)
|
||
{
|
||
printf("[TC-CONFIG-056] section capacity: MAX_SECTIONS pinned, 9th rejected\n");
|
||
{
|
||
char nm[8];
|
||
cfg_section_reg_t r;
|
||
|
||
/* 数字钉:段上限必须是 8(cfg_file.h:39);改数字 = 容量台账变化,必须
|
||
* 同步 docs/子系统设计/配置文件设计说明.md 与本用例生产占位说明 */
|
||
CHECK(CFG_FILE_MAX_SECTIONS == 8u, "CFG_FILE_MAX_SECTIONS pinned to 8");
|
||
vfs_reset();
|
||
CHECK(register_fill((int)CFG_FILE_MAX_SECTIONS) == 1, "filling exactly 8 sections all accepted");
|
||
memset(nm, 0, sizeof(nm));
|
||
(void)memcpy(nm, "sx", 3u);
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = nm;
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = sizeof(sec_net_t);
|
||
r.p_keys = s_net_keys;
|
||
r.key_count = (uint8_t)(sizeof(s_net_keys) / sizeof(s_net_keys[0]));
|
||
CHECK(cfg_file_register(&r) == -1, "9th section rejected when table full");
|
||
CHECK(s_sections[CFG_FILE_MAX_SECTIONS - 1u].used == 1u,
|
||
"rejection left no partial slot (table intact after full)");
|
||
}
|
||
|
||
printf("[TC-CONFIG-057] key capacity: 16 accepted, 17 rejected (MAX_KEYS pinned)\n");
|
||
{
|
||
static const cfg_key_def_t keys17[17] = {
|
||
{ "k0", CFG_VAL_UINT, 0u, 1u }, { "k1", CFG_VAL_UINT, 1u, 1u },
|
||
{ "k2", CFG_VAL_UINT, 2u, 1u }, { "k3", CFG_VAL_UINT, 3u, 1u },
|
||
{ "k4", CFG_VAL_UINT, 4u, 1u }, { "k5", CFG_VAL_UINT, 5u, 1u },
|
||
{ "k6", CFG_VAL_UINT, 6u, 1u }, { "k7", CFG_VAL_UINT, 7u, 1u },
|
||
{ "k8", CFG_VAL_UINT, 8u, 1u }, { "k9", CFG_VAL_UINT, 9u, 1u },
|
||
{ "k10", CFG_VAL_UINT, 10u, 1u }, { "k11", CFG_VAL_UINT, 11u, 1u },
|
||
{ "k12", CFG_VAL_UINT, 12u, 1u }, { "k13", CFG_VAL_UINT, 13u, 1u },
|
||
{ "k14", CFG_VAL_UINT, 14u, 1u }, { "k15", CFG_VAL_UINT, 15u, 1u },
|
||
{ "k16", CFG_VAL_UINT, 16u, 1u },
|
||
};
|
||
static uint8_t kd[17];
|
||
cfg_section_reg_t r;
|
||
|
||
CHECK(CFG_FILE_MAX_KEYS == 16u, "CFG_FILE_MAX_KEYS pinned to 16");
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "capk";
|
||
r.p_data = kd;
|
||
r.p_defaults = kd;
|
||
r.data_size = sizeof(kd);
|
||
r.key_count = 16u;
|
||
r.p_keys = keys17;
|
||
CHECK(cfg_file_register(&r) == 0, "16-key section accepted at limit");
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "capk2";
|
||
r.p_data = kd;
|
||
r.p_defaults = kd;
|
||
r.data_size = sizeof(kd);
|
||
r.key_count = 17u;
|
||
r.p_keys = keys17;
|
||
CHECK(cfg_file_register(&r) == -1, "17-key section rejected over CFG_FILE_MAX_KEYS");
|
||
}
|
||
}
|
||
|
||
/* ===================== 用例 ===================== */
|
||
|
||
/* TC-CONFIG-001/002:UINT 往返与非法值 */
|
||
static void tc_uint(void) {
|
||
printf("[TC-CONFIG-001] UINT encode/decode round-trip\n");
|
||
{
|
||
uint8_t d1[1]; uint8_t d2[2]; uint8_t d4[4];
|
||
CHECK(s_decode_uint(d1, 1u, "255") == 0 && d1[0] == 0xFFu, "u8 max 255");
|
||
CHECK(s_decode_uint(d1, 1u, "0") == 0 && d1[0] == 0u, "u8 zero");
|
||
CHECK(s_decode_uint(d2, 2u, "65535") == 0 && d2[0] == 0xFFu && d2[1] == 0xFFu,
|
||
"u16 max little-endian");
|
||
CHECK(s_decode_uint(d4, 4u, "4294967295") == 0 &&
|
||
d4[0] == 0xFFu && d4[3] == 0xFFu, "u32 max little-endian");
|
||
/* 小端序验证:0x1234 -> d[0]=0x34 d[1]=0x12 */
|
||
CHECK(s_decode_uint(d2, 2u, "4660") == 0 && d2[0] == 0x34u && d2[1] == 0x12u,
|
||
"u16 0x1234 little-endian order");
|
||
}
|
||
|
||
printf("[TC-CONFIG-002] UINT illegal values rejected\n");
|
||
{
|
||
uint8_t d[4];
|
||
CHECK(s_decode_uint(d, 1u, "") == -1, "empty rejected");
|
||
CHECK(s_decode_uint(d, 1u, "abc") == -1, "non-numeric rejected");
|
||
CHECK(s_decode_uint(d, 1u, "12x") == -1, "trailing garbage rejected");
|
||
CHECK(s_decode_uint(d, 1u, "256") == -1, "u8 overflow rejected");
|
||
CHECK(s_decode_uint(d, 2u, "65536") == -1, "u16 overflow rejected");
|
||
CHECK(s_decode_uint(d, 3u, "1") == -1, "invalid len 3 rejected");
|
||
CHECK(s_decode_uint(d, 1u, "-1") == -1, "negative rejected");
|
||
}
|
||
}
|
||
|
||
/* TC-CONFIG-003:IP4 */
|
||
static void tc_ip4(void) {
|
||
uint8_t d[4];
|
||
|
||
printf("[TC-CONFIG-003] IPv4 decode\n");
|
||
CHECK(s_decode_ip4(d, "0.0.0.0") == 0 && d[0] == 0u && d[3] == 0u, "all-zero");
|
||
CHECK(s_decode_ip4(d, "255.255.255.255") == 0 &&
|
||
d[0] == 255u && d[3] == 255u, "all-255");
|
||
CHECK(s_decode_ip4(d, "192.168.31.100") == 0 &&
|
||
d[0] == 192u && d[1] == 168u && d[2] == 31u && d[3] == 100u, "typical");
|
||
CHECK(s_decode_ip4(d, "1.2.3") == -1, "too few octets rejected");
|
||
CHECK(s_decode_ip4(d, "1.2.3.4.5") == -1, "too many octets rejected");
|
||
CHECK(s_decode_ip4(d, "256.1.1.1") == -1, "octet >255 rejected");
|
||
CHECK(s_decode_ip4(d, "a.1.1.1") == -1, "non-digit rejected");
|
||
CHECK(s_decode_ip4(d, "1.1.1.") == -1, "trailing dot rejected");
|
||
CHECK(s_decode_ip4(d, "") == -1, "empty rejected");
|
||
}
|
||
|
||
/* TC-CONFIG-004:MAC */
|
||
static void tc_mac(void) {
|
||
uint8_t d[6];
|
||
|
||
printf("[TC-CONFIG-004] MAC decode (separators/case)\n");
|
||
CHECK(s_decode_mac(d, "00:11:22:33:44:55") == 0 &&
|
||
d[0] == 0x00u && d[5] == 0x55u, "colon separator");
|
||
CHECK(s_decode_mac(d, "00-11-22-33-44-55") == 0 &&
|
||
d[1] == 0x11u, "dash separator");
|
||
/* 点号仅在**字节边界**作分隔符替代(与 ':'/'-' 等价,见 cfg_file.h 注释);
|
||
* 不支持 Cisco 4-4-4 分组("0011.2233.4455")——那不是本引擎的契约 */
|
||
CHECK(s_decode_mac(d, "00.11.22.33.44.55") == 0 &&
|
||
d[0] == 0x00u && d[1] == 0x11u && d[5] == 0x55u, "dot separator (byte boundary)");
|
||
CHECK(s_decode_mac(d, "AA:BB:CC:DD:EE:FF") == 0 &&
|
||
d[0] == 0xAAu && d[5] == 0xFFu, "uppercase hex");
|
||
CHECK(s_decode_mac(d, "aa:bb:cc:dd:ee:ff") == 0 &&
|
||
d[0] == 0xAAu, "lowercase hex");
|
||
CHECK(s_decode_mac(d, "00:11:22:33:44") == -1, "too short rejected");
|
||
CHECK(s_decode_mac(d, "00:11:22:33:44:55:66") == -1, "too long rejected");
|
||
CHECK(s_decode_mac(d, "00:11:22:33:44:zz") == -1, "non-hex rejected");
|
||
CHECK(s_decode_mac(d, "00:11:22:33:44;55") == -1, "bad separator rejected");
|
||
CHECK(s_decode_mac(d, "") == -1, "empty rejected");
|
||
}
|
||
|
||
/* TC-CONFIG-005:STR */
|
||
static void tc_str(void) {
|
||
uint8_t d[8];
|
||
|
||
printf("[TC-CONFIG-005] STR decode (length boundary)\n");
|
||
CHECK(s_decode_str(d, 8u, "hello") == 0 && d[0] == 'h' && d[5] == '\0',
|
||
"normal string + NUL");
|
||
CHECK(s_decode_str(d, 8u, "1234567") == 0 && d[6] == '7' && d[7] == '\0',
|
||
"exactly len-1 chars fits");
|
||
CHECK(s_decode_str(d, 8u, "12345678") == -1, "len chars rejected (no room for NUL)");
|
||
CHECK(s_decode_str(d, 8u, "") == 0 && d[0] == '\0', "empty string ok");
|
||
}
|
||
|
||
/* TC-CONFIG-006/007:INT 解码(「数据可信」有符号零点类型——负零点偏移) */
|
||
static void tc_int_decode(void) {
|
||
uint8_t d[4];
|
||
|
||
printf("[TC-CONFIG-006] INT decode valid\n");
|
||
CHECK(s_decode_int(d, 4u, "0") == 0 && d[0] == 0u && d[3] == 0u, "int32 zero");
|
||
CHECK(s_decode_int(d, 4u, "-1") == 0 && d[0] == 0xFFu && d[1] == 0xFFu &&
|
||
d[2] == 0xFFu && d[3] == 0xFFu, "-1 = four FF bytes (two's complement)");
|
||
CHECK(s_decode_int(d, 4u, "-2147483648") == 0 && d[0] == 0x00u &&
|
||
d[1] == 0x00u && d[2] == 0x00u && d[3] == 0x80u, "INT32_MIN accepted");
|
||
CHECK(s_decode_int(d, 4u, "2147483647") == 0 && d[0] == 0xFFu &&
|
||
d[1] == 0xFFu && d[2] == 0xFFu && d[3] == 0x7Fu, "INT32_MAX accepted");
|
||
CHECK(s_decode_int(d, 1u, "-128") == 0 && d[0] == 0x80u, "int8 -128");
|
||
CHECK(s_decode_int(d, 1u, "127") == 0 && d[0] == 0x7Fu, "int8 +127");
|
||
CHECK(s_decode_int(d, 2u, "-32768") == 0 && d[0] == 0x00u && d[1] == 0x80u,
|
||
"int16 -32768");
|
||
CHECK(s_decode_int(d, 2u, "32767") == 0 && d[0] == 0xFFu && d[1] == 0x7Fu,
|
||
"int16 +32767");
|
||
CHECK(s_decode_int(d, 4u, "+42") == 0 && d[0] == 42u, "leading + accepted");
|
||
CHECK(s_decode_int(d, 4u, "-0") == 0 && d[0] == 0u && d[3] == 0u, "-0 decodes 0");
|
||
CHECK(s_decode_int(d, 4u, "00012") == 0 && d[0] == 12u && d[3] == 0u,
|
||
"leading zeros accepted");
|
||
|
||
printf("[TC-CONFIG-007] INT decode invalid rejected\n");
|
||
CHECK(s_decode_int(d, 4u, "") == -1, "empty rejected");
|
||
CHECK(s_decode_int(d, 4u, "abc") == -1, "non-numeric rejected");
|
||
CHECK(s_decode_int(d, 4u, "12x") == -1, "trailing garbage rejected");
|
||
CHECK(s_decode_int(d, 4u, "-") == -1, "bare sign rejected");
|
||
CHECK(s_decode_int(d, 4u, "1.5") == -1, "decimal point rejected");
|
||
CHECK(s_decode_int(d, 4u, "0x10") == -1, "hex text rejected");
|
||
CHECK(s_decode_int(d, 1u, "-129") == -1, "int8 -129 rejected");
|
||
CHECK(s_decode_int(d, 1u, "128") == -1, "int8 128 rejected");
|
||
CHECK(s_decode_int(d, 2u, "-32769") == -1, "int16 -32769 rejected");
|
||
CHECK(s_decode_int(d, 2u, "32768") == -1, "int16 32768 rejected");
|
||
CHECK(s_decode_int(d, 4u, "2147483648") == -1, "INT32_MAX + 1 rejected (ERANGE)");
|
||
CHECK(s_decode_int(d, 4u, "-2147483649") == -1, "INT32_MIN - 1 rejected (ERANGE)");
|
||
CHECK(s_decode_int(d, 4u, "99999999999") == -1, "huge magnitude rejected (ERANGE)");
|
||
CHECK(s_decode_int(d, 4u, "-99999999999") == -1, "huge negative rejected (ERANGE)");
|
||
CHECK(s_decode_int(d, 3u, "1") == -1, "invalid len 3 rejected");
|
||
}
|
||
|
||
/* TC-CONFIG-008:INT 编码带负号 + [sys] 段 load/save/load 全链往返 */
|
||
static void tc_int_encode(void) {
|
||
char buf[24];
|
||
uint8_t field[4];
|
||
static const cfg_key_def_t k32 = { "ofs32", CFG_VAL_INT, 0u, 4u };
|
||
static const cfg_key_def_t k8 = { "ofs8", CFG_VAL_INT, 0u, 1u };
|
||
|
||
printf("[TC-CONFIG-008] INT encode (sign in text)\n");
|
||
field[0] = 0xFFu; field[1] = 0xFFu; field[2] = 0xFFu; field[3] = 0xFFu;
|
||
CHECK(s_encode(&k32, field, buf, sizeof(buf)) > 0 && strcmp(buf, "-1") == 0,
|
||
"FFFFFFFF encodes -1");
|
||
field[0] = 0x00u; field[1] = 0x00u; field[2] = 0x00u; field[3] = 0x80u;
|
||
CHECK(s_encode(&k32, field, buf, sizeof(buf)) > 0 &&
|
||
strcmp(buf, "-2147483648") == 0, "INT32_MIN bytes encode with sign");
|
||
field[0] = 0xFFu; field[1] = 0xFFu; field[2] = 0xFFu; field[3] = 0x7Fu;
|
||
CHECK(s_encode(&k32, field, buf, sizeof(buf)) > 0 &&
|
||
strcmp(buf, "2147483647") == 0, "INT32_MAX bytes encode");
|
||
field[0] = 0x80u;
|
||
CHECK(s_encode(&k8, field, buf, sizeof(buf)) > 0 && strcmp(buf, "-128") == 0,
|
||
"int8 0x80 encodes -128");
|
||
field[0] = 0x7Fu;
|
||
CHECK(s_encode(&k8, field, buf, sizeof(buf)) > 0 && strcmp(buf, "127") == 0,
|
||
"int8 0x7F encodes 127");
|
||
|
||
printf("[TC-CONFIG-008] INT section roundtrip (load -> save -> load)\n");
|
||
vfs_reset();
|
||
register_std();
|
||
CHECK(cfg_file_load() == 0, "load creates template");
|
||
CHECK(strstr(vfs_get(), "ofs32=-2147483648") != NULL,
|
||
"template INT32_MIN default written with sign");
|
||
CHECK(strstr(vfs_get(), "ofs8=-5") != NULL, "template int8 default -5");
|
||
|
||
s_sys_data.ofs32 = -123456;
|
||
s_sys_data.ofs16 = -2;
|
||
s_sys_data.ofs8 = -128;
|
||
CHECK(cfg_file_save() == 0, "save with negatives");
|
||
CHECK(strstr(vfs_get(), "ofs32=-123456") != NULL, "negative saved with sign");
|
||
CHECK(strstr(vfs_get(), "ofs8=-128") != NULL, "int8 -128 saved");
|
||
s_sys_data.ofs32 = 1;
|
||
s_sys_data.ofs16 = 1;
|
||
s_sys_data.ofs8 = 1;
|
||
CHECK(cfg_file_load() == 0, "reload ok");
|
||
CHECK(s_sys_data.ofs32 == -123456 && s_sys_data.ofs16 == -2 &&
|
||
s_sys_data.ofs8 == -128, "INT values roundtrip identical");
|
||
|
||
/* 越界文本 = 该键拒绝、留工厂默认并留痕;同段其余键不受牵连 */
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[sys]\r\nbaud=9600\r\nofs32=2147483648\r\nofs8=-129\r\nofs16=-300\r\n");
|
||
CHECK(cfg_file_load() == 0, "load with out-of-range INT keys");
|
||
CHECK(s_sys_data.ofs32 == s_sys_def.ofs32, "out-of-range ofs32 keeps default");
|
||
CHECK(s_sys_data.ofs8 == s_sys_def.ofs8, "out-of-range ofs8 keeps default");
|
||
CHECK(s_sys_data.ofs16 == -300, "valid ofs16 in same section parsed");
|
||
CHECK(s_sys_data.baud == 9600u, "UINT key beside INT parsed");
|
||
}
|
||
|
||
/* TC-CONFIG-010~015:INI 解析 */
|
||
static void tc_parse(void) {
|
||
printf("[TC-CONFIG-010..015] INI parsing via load\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put(
|
||
"; comment line\r\n"
|
||
"# hash comment\r\n"
|
||
"\r\n"
|
||
"[net]\r\n"
|
||
"u8 = 42\r\n"
|
||
"ip = 10.0.0.1\r\n"
|
||
"unknown_key = whatever\r\n"
|
||
"\r\n"
|
||
"[unknown_section]\r\n"
|
||
"x = 1\r\n"
|
||
"\r\n"
|
||
"[sys]\r\n"
|
||
"baud = 9600\r\n");
|
||
|
||
CHECK(cfg_file_load() == 0, "load returns 0");
|
||
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-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");
|
||
}
|
||
|
||
printf("[TC-CONFIG-012] section/key name case-insensitive + value whitespace trim\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
/* 段名大小写无关;键名大小写无关;'=' 两侧与值两侧空白修剪。
|
||
* 注意:段名括号内**不**支持前导空白([ NET ] 中的前导空格会保留),
|
||
* 这是既有契约(s_match_section 只 trim 尾空白),不是本用例的断言点。 */
|
||
vfs_put(
|
||
"[NET]\r\n"
|
||
" U8 = 9 \r\n"
|
||
" IP = 1.2.3.4\r\n");
|
||
CHECK(cfg_file_load() == 0, "load ok");
|
||
CHECK(s_net_data.u8 == 9u, "uppercase section+key matched (case-insensitive)");
|
||
CHECK(s_net_data.ip[0] == 1u && s_net_data.ip[3] == 4u, "value whitespace trimmed");
|
||
}
|
||
|
||
printf("[TC-CONFIG-011] LF-only line endings\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\nu8=77\nip=8.8.8.8\n");
|
||
CHECK(cfg_file_load() == 0, "load ok with LF");
|
||
CHECK(s_net_data.u8 == 77u, "LF: value parsed");
|
||
CHECK(s_net_data.ip[3] == 8u, "LF: ip parsed");
|
||
}
|
||
|
||
printf("[TC-CONFIG-015] key-value before any section ignored\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("u8 = 123\r\n[net]\r\nip = 5.5.5.5\r\n");
|
||
CHECK(cfg_file_load() == 0, "load ok");
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "orphan key before section ignored");
|
||
CHECK(s_net_data.ip[0] == 5u, "valid key after section parsed");
|
||
}
|
||
|
||
printf("[TC-CONFIG-014b] invalid value keeps factory default + logs\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8 = 999\r\nip = not-an-ip\r\n");
|
||
g_cfgtest_err_count = 0;
|
||
CHECK(cfg_file_load() == 0, "load ok despite invalid values");
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "invalid u8 keeps default");
|
||
CHECK(s_net_data.ip[0] == s_net_def.ip[0], "invalid ip keeps default");
|
||
CHECK(g_cfgtest_err_count >= 2, "invalid values logged (DBG_ERROR)");
|
||
}
|
||
}
|
||
|
||
/* TC-CONFIG-020~023:register 校验 */
|
||
static void tc_register(void) {
|
||
printf("[TC-CONFIG-020] register validation\n");
|
||
{
|
||
cfg_section_reg_t r;
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "net";
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = sizeof(sec_net_t);
|
||
r.p_keys = s_net_keys;
|
||
r.key_count = 5u;
|
||
|
||
CHECK(cfg_file_register(&r) == 0, "valid register");
|
||
CHECK(cfg_file_register(&r) == -1, "duplicate section name rejected");
|
||
CHECK(cfg_file_register(NULL) == -1, "NULL descriptor rejected");
|
||
|
||
r.name = "another";
|
||
r.p_data = NULL;
|
||
CHECK(cfg_file_register(&r) == -1, "NULL p_data rejected");
|
||
}
|
||
|
||
printf("[TC-CONFIG-021] invalid key definitions rejected\n");
|
||
{
|
||
cfg_section_reg_t r;
|
||
static const cfg_key_def_t bad_uint[] = { { "k", CFG_VAL_UINT, 0u, 3u } };
|
||
static const cfg_key_def_t bad_int[] = { { "k", CFG_VAL_INT, 0u, 3u } };
|
||
static const cfg_key_def_t bad_mac[] = { { "k", CFG_VAL_MAC, 0u, 4u } };
|
||
static const cfg_key_def_t bad_str[] = { { "k", CFG_VAL_STR, 0u, 80u } };
|
||
static const cfg_key_def_t zero_len[] = { { "k", CFG_VAL_UINT, 0u, 0u } };
|
||
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "x";
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = 8u;
|
||
r.key_count = 1u;
|
||
|
||
r.p_keys = bad_uint;
|
||
CHECK(cfg_file_register(&r) == -1, "UINT len=3 rejected");
|
||
r.p_keys = bad_int;
|
||
CHECK(cfg_file_register(&r) == -1, "INT len=3 rejected");
|
||
r.p_keys = bad_mac;
|
||
CHECK(cfg_file_register(&r) == -1, "MAC len=4 rejected");
|
||
r.p_keys = bad_str;
|
||
CHECK(cfg_file_register(&r) == -1, "STR len=80 rejected");
|
||
r.p_keys = zero_len;
|
||
CHECK(cfg_file_register(&r) == -1, "key len=0 rejected");
|
||
}
|
||
|
||
printf("[TC-CONFIG-022] section name length limit\n");
|
||
{
|
||
cfg_section_reg_t r;
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "0123456789abcdef"; /* 16 chars >= CFG_NAME_MAX(16) */
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = 8u;
|
||
r.p_keys = s_sys_keys;
|
||
r.key_count = 1u;
|
||
CHECK(cfg_file_register(&r) == -1, "section name >= 16 chars rejected");
|
||
}
|
||
|
||
printf("[TC-CONFIG-023] callback section data_size limit\n");
|
||
{
|
||
cfg_section_reg_t r;
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "cb";
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = CFG_SNAP_MAX + 1u; /* > 64,挂回调则拒绝 */
|
||
r.p_keys = s_sys_keys;
|
||
r.key_count = 1u;
|
||
r.p_cb = net_on_change;
|
||
CHECK(cfg_file_register(&r) == -1, "callback with data_size>64 rejected");
|
||
|
||
r.p_cb = NULL;
|
||
CHECK(cfg_file_register(&r) == 0, "same section without callback accepted");
|
||
}
|
||
|
||
printf("key name length limit\n");
|
||
{
|
||
cfg_section_reg_t r;
|
||
static const cfg_key_def_t long_key[] = {
|
||
{ "0123456789abcdef", CFG_VAL_UINT, 0u, 1u } /* 16 chars >= CFG_NAME_MAX(16) */
|
||
};
|
||
static const cfg_key_def_t max_key[] = {
|
||
{ "0123456789abcde", CFG_VAL_UINT, 0u, 1u } /* 15 chars = CFG_FILE_MAX_NAME_LEN */
|
||
};
|
||
|
||
vfs_reset();
|
||
memset(&r, 0, sizeof(r));
|
||
r.name = "klen";
|
||
r.p_data = &s_net_data;
|
||
r.p_defaults = &s_net_def;
|
||
r.data_size = 8u;
|
||
r.key_count = 1u;
|
||
|
||
r.p_keys = long_key;
|
||
CHECK(cfg_file_register(&r) == -1, "key name >= 16 chars rejected");
|
||
|
||
r.p_keys = max_key;
|
||
CHECK(cfg_file_register(&r) == 0, "key name == 15 chars accepted");
|
||
}
|
||
}
|
||
|
||
/* TC-CONFIG-030~034:load 契约 */
|
||
static void tc_load(void) {
|
||
printf("[TC-CONFIG-030] missing file -> factory template written\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
s_write_count = 0;
|
||
CHECK(cfg_file_load() == 0, "load returns 0 when file missing");
|
||
CHECK(s_vfs_exists == 1, "template file created");
|
||
CHECK(s_write_count == 1, "one write happened");
|
||
/* 模板须含段头与键 */
|
||
CHECK(strstr(vfs_get(), "[net]") != NULL, "template has [net]");
|
||
CHECK(strstr(vfs_get(), "[sys]") != NULL, "template has [sys]");
|
||
CHECK(strstr(vfs_get(), "u8=7") != NULL, "template has default u8=7");
|
||
}
|
||
|
||
printf("[TC-CONFIG-032] oversized file -> factory default + regenerate + syslog\n");
|
||
{
|
||
size_t i;
|
||
vfs_reset();
|
||
register_std();
|
||
/* 构造 > CFG_FILE_MAX_LEN 的文件 */
|
||
for (i = 0; i < CFG_FILE_MAX_LEN + 100u; i++) {
|
||
s_vfs[i] = (uint8_t)'A';
|
||
}
|
||
s_vfs_len = CFG_FILE_MAX_LEN + 100u;
|
||
s_vfs_exists = 1;
|
||
s_net_data.u8 = 0xEEu; /* 预置脏值,验证被复位 */
|
||
s_syslog_count = 0;
|
||
CHECK(cfg_file_load() == 0, "oversized load returns 0");
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "RAM reset to factory default");
|
||
CHECK(s_syslog_count >= 1, "self-test log recorded");
|
||
CHECK(s_vfs_len <= CFG_FILE_MAX_LEN, "file regenerated within limit");
|
||
}
|
||
|
||
printf("[TC-CONFIG-033] read failure -> -1, RAM keeps factory default\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=99\r\n");
|
||
s_fail_read = 1;
|
||
CHECK(cfg_file_load() == -1, "read failure returns -1");
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "RAM stays factory default");
|
||
}
|
||
|
||
printf("[TC-CONFIG-034] load with no registered section -> -1\n");
|
||
{
|
||
vfs_reset();
|
||
CHECK(cfg_file_load() == -1, "no section registered returns -1");
|
||
}
|
||
}
|
||
|
||
/* TC-CONFIG-040~043:save 契约 */
|
||
static void tc_save(void) {
|
||
printf("[TC-CONFIG-040] save with missing file -> full document\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
s_net_data.u8 = 12u;
|
||
s_sys_data.baud = 38400u;
|
||
CHECK(cfg_file_save() == 0, "save ok");
|
||
CHECK(s_vfs_exists == 1, "file created");
|
||
CHECK(strstr(vfs_get(), "u8=12") != NULL, "current value written");
|
||
CHECK(strstr(vfs_get(), "baud=38400") != NULL, "second section written");
|
||
}
|
||
|
||
printf("[TC-CONFIG-041] surgical save preserves comments/unknown\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put(
|
||
"; my precious comment\r\n"
|
||
"[net]\r\n"
|
||
"u8 = 1\r\n"
|
||
"vendor_key = keepme\r\n"
|
||
"\r\n"
|
||
"[vendor]\r\n"
|
||
"x = y\r\n");
|
||
s_net_data.u8 = 200u;
|
||
CHECK(cfg_file_save() == 0, "surgical save ok");
|
||
CHECK(strstr(vfs_get(), "; my precious comment") != NULL, "comment preserved");
|
||
CHECK(strstr(vfs_get(), "vendor_key = keepme") != NULL ||
|
||
strstr(vfs_get(), "vendor_key=keepme") != NULL, "unknown key preserved");
|
||
CHECK(strstr(vfs_get(), "[vendor]") != NULL, "unknown section preserved");
|
||
CHECK(strstr(vfs_get(), "u8=200") != NULL, "managed key rewritten in place");
|
||
}
|
||
|
||
printf("[TC-CONFIG-042] surgical save appends missing managed section/key\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put(
|
||
"; head\r\n"
|
||
"[net]\r\n"
|
||
"u8 = 3\r\n");
|
||
s_sys_data.baud = 57600u;
|
||
CHECK(cfg_file_save() == 0, "save ok");
|
||
CHECK(strstr(vfs_get(), "[sys]") != NULL, "missing section appended");
|
||
CHECK(strstr(vfs_get(), "baud=57600") != NULL, "missing key written");
|
||
}
|
||
|
||
printf("[TC-CONFIG-043] output overflow -> reject, original untouched\n");
|
||
{
|
||
size_t i;
|
||
vfs_reset();
|
||
register_std();
|
||
/* 构造一个接近上限、且含大量未知内容的文件;外科手术输出会超限 */
|
||
for (i = 0; i < CFG_FILE_MAX_LEN - 4u; i++) {
|
||
s_vfs[i] = (uint8_t)((i % 60u == 0u) ? '\n' : 'Z');
|
||
}
|
||
s_vfs_len = CFG_FILE_MAX_LEN - 4u;
|
||
s_vfs_exists = 1;
|
||
s_syslog_count = 0;
|
||
/* 记录原文件长度,验证不动 */
|
||
{
|
||
size_t before = s_vfs_len;
|
||
int r = cfg_file_save();
|
||
CHECK(r == -1, "overflow rejected with -1");
|
||
CHECK(s_vfs_len == before, "original file untouched on overflow");
|
||
CHECK(s_syslog_count >= 1, "overflow self-test logged");
|
||
}
|
||
}
|
||
|
||
printf("over-long managed key line -> reject\n");
|
||
{
|
||
char buf[512];
|
||
size_t n;
|
||
size_t i;
|
||
size_t before;
|
||
int r;
|
||
|
||
/* 构造受管键 u8 的超长行(行总长 > CFG_LINE_TEST_MAX):值为 200 位数字。
|
||
* 修复前:Pass1 静默跳过该行 → Pass2 在段头补写新 KV + 原文超长行原样保留
|
||
* → 同一键两行。修复后:显式拒绝保存,原文件不动。 */
|
||
vfs_reset();
|
||
register_std();
|
||
n = (size_t)snprintf(buf, sizeof(buf), "[net]\r\nu8 = ");
|
||
for (i = 0u; i < 200u && n < sizeof(buf) - 3u; i++) {
|
||
buf[n++] = '9';
|
||
}
|
||
buf[n++] = '\r';
|
||
buf[n++] = '\n';
|
||
buf[n] = '\0';
|
||
vfs_put(buf);
|
||
s_net_data.u8 = 7u;
|
||
s_syslog_count = 0;
|
||
before = s_vfs_len;
|
||
r = cfg_file_save();
|
||
CHECK(r == -1, "over-long managed key line rejected (-1)");
|
||
CHECK(s_vfs_len == before, "original file untouched on over-long line");
|
||
CHECK(s_syslog_count >= 1, "line-too-long self-test logged");
|
||
|
||
/* 反向:**非受管键**的超长行不属本守卫范围,应照旧原样保留、保存成功 */
|
||
vfs_reset();
|
||
register_std();
|
||
n = (size_t)snprintf(buf, sizeof(buf), "[net]\r\nu8=1\r\nvendor = ");
|
||
for (i = 0u; i < 200u && n < sizeof(buf) - 3u; i++) {
|
||
buf[n++] = 'k';
|
||
}
|
||
buf[n++] = '\r';
|
||
buf[n++] = '\n';
|
||
buf[n] = '\0';
|
||
vfs_put(buf);
|
||
s_net_data.u8 = 9u;
|
||
CHECK(cfg_file_save() == 0, "over-long unknown key line tolerated");
|
||
CHECK(strstr(vfs_get(), "u8=9") != NULL, "managed key still rewritten");
|
||
CHECK(strstr(vfs_get(), "vendor = kkk") != NULL, "unknown long line preserved");
|
||
}
|
||
}
|
||
|
||
/* TC-CONFIG-050~054:poll 契约 */
|
||
static void tc_poll(void) {
|
||
printf("[TC-CONFIG-050] poll first round establishes baseline\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=5\r\n");
|
||
cfg_file_poll(); /* 建基线 */
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "no reload on first poll (baseline only)");
|
||
CHECK(s_base_valid == 1u, "baseline established");
|
||
}
|
||
|
||
printf("[TC-CONFIG-051/052] settle debounce + no-change skip\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=5\r\n");
|
||
cfg_file_poll(); /* baseline */
|
||
cfg_file_poll(); /* stable 1, same as base -> no reload */
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "same-as-base poll: no reload");
|
||
|
||
/* 改变文件(mtime 变) -> 需连续 CFG_FILE_SETTLE_CNT 次稳定才重载 */
|
||
vfs_put("[net]\r\nu8=88\r\n");
|
||
cfg_file_poll(); /* stable=1, 未沉降 */
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "not reloaded before settle");
|
||
cfg_file_poll(); /* stable=2 -> 重载 */
|
||
CHECK(s_net_data.u8 == 88u, "reloaded after settle");
|
||
}
|
||
|
||
printf("[TC-CONFIG-053] change triggers reload + callback\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=5\r\nip=1.1.1.1\r\n");
|
||
cfg_file_poll(); /* baseline */
|
||
s_net_cb_count = 0;
|
||
vfs_put("[net]\r\nu8=5\r\nip=2.2.2.2\r\n"); /* ip 变化 + mtime 令牌变 */
|
||
cfg_file_poll();
|
||
cfg_file_poll(); /* settle -> reload + cb */
|
||
CHECK(s_net_data.ip[0] == 2u, "changed value reloaded");
|
||
CHECK(s_net_cb_count >= 1, "callback fired on section change");
|
||
}
|
||
|
||
printf("[TC-CONFIG-055] content change without size/mtime token change -> not detected\n");
|
||
{
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=55\r\n"); /* 基线:u8 两位 */
|
||
cfg_file_poll(); /* baseline */
|
||
/* 只改内容、且**长度与 mtime 都不变**:(size,mtime) 令牌相同 -> 不重载。
|
||
* 这是既有契约(令牌 = 变更信号源);真实写盘 mtime 必变,故不构成漏更新 */
|
||
vfs_put_notoken("[net]\r\nu8=77\r\n"); /* 同为两位字符,size 不变 */
|
||
cfg_file_poll();
|
||
cfg_file_poll();
|
||
CHECK(s_net_data.u8 == s_net_def.u8,
|
||
"same-token change ignored (size+mtime is the change signal)");
|
||
}
|
||
|
||
printf("[TC-CONFIG-054] poll detects oversized file -> factory + regenerate + syslog\n");
|
||
{
|
||
size_t i;
|
||
vfs_reset();
|
||
register_std();
|
||
vfs_put("[net]\r\nu8=5\r\n");
|
||
cfg_file_poll(); /* baseline */
|
||
/* 膨胀文件到超限 */
|
||
for (i = 0; i < CFG_FILE_MAX_LEN + 50u; i++) {
|
||
s_vfs[i] = (uint8_t)'B';
|
||
}
|
||
s_vfs_len = CFG_FILE_MAX_LEN + 50u;
|
||
s_vfs_mtime++;
|
||
s_net_data.u8 = 0xABu;
|
||
s_syslog_count = 0;
|
||
s_net_cb_count = 0;
|
||
cfg_file_poll();
|
||
cfg_file_poll(); /* settle -> 走超长分支 */
|
||
CHECK(s_net_data.u8 == s_net_def.u8, "oversized poll resets to factory default");
|
||
CHECK(s_syslog_count >= 1, "oversized poll logged to self-test");
|
||
CHECK(s_vfs_len <= CFG_FILE_MAX_LEN, "file regenerated within limit");
|
||
CHECK(s_net_cb_count == 0, "no callback on oversized (online-repair channel kept)");
|
||
}
|
||
}
|
||
|
||
int main(void) {
|
||
printf("=== Config File Tests ===\n");
|
||
|
||
tc_uint();
|
||
tc_int_decode();
|
||
tc_int_encode();
|
||
tc_ip4();
|
||
tc_mac();
|
||
tc_str();
|
||
tc_parse();
|
||
tc_register();
|
||
tc_capacity();
|
||
tc_load();
|
||
tc_save();
|
||
tc_poll();
|
||
|
||
printf("\n=== Config File Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|