Files
STM32F4-Base/test/host/cfg_file/cfg_file_test.c
T
wandering1 411594bf67 feat(采集标定): 卡1 TPAFE 量程口径定死 + 卡3 cfg_file 补 CFG_VAL_INT
卡 1(文档口径;卡 0/12 实测类按用户指示跳过,只落口径与占位):
- ADC通道映射说明.md 文首摘要行的 ±10 V 更正为 ±5 V 双极性:板上 RANGE
  接 GND,±5 V 满量程 = 10 V 跨度,LSB ≈ 152.59 µV/码(@引脚)
- §7 TPAFE 量程行重写并括注 ±10 V 是芯片可选档(RANGE=HIGH,跨度 20 V、
  LSB 305.18 µV)、本板未用;§1 缺陷实质 2 补「满量程 = 10 V 跨度」
- 新增 §7.1 端到端量纲链路分段口径:②引脚侧 LSB 系数定死(TPAFE 152.59 /
  STM32 805.66 µV/码,纯转换器事实、与前端网络无关,卡 2 的 ROM 默认按此
  冻结);③1k+1k 网络「2:1 分压 vs 源阻抗匹配」两读法待卡 0 注入斜率判别;
  ④端子→AINn 变比为仓库缺口待卡 0b;305.18/1611.3 定性为读法 A 的条件值、
  判别前禁入代码;§9 变更记录编号不动(继保规划有指向本文件 §9 的跨文档
  引用),新增行追加于其后
- 全仓其余 ±10 V 仅剩芯片手册转写与 tpafe5160 RANGE 枚举可选档注释
  (芯片能力描述,非本板口径);数据可信.md §2.1 冲突挂账归 lead 销项

卡 3(代码;cfg_file.c 带 BOM,编辑后核 EF BB BF 完好):
- 新增值类型 CFG_VAL_INT(枚举尾部追加、既有值不变;len 1/2/4 小端补码):
  s_decode_int() = strtol + ERANGE + 宽度上下界双校验(两端 long 均 32 位,
  超范围文本饱和成 LONG_MAX 仅比界抓不住,ERANGE 必查);s_encode() 带负号
  %ld 输出;cfg_file_register() 拒 INT 非法 len
- 单测新增 TC-CONFIG-006/007/008:-1 / INT32_MIN / INT32_MAX / 越界拒绝
  (含饱和带 2147483648)/ 编码带负号 / [sys] 段 load-save-load 往返一致 /
  注册拒绝 INT len=3 —— cfg_file 套件 200/200 PASS
- 回归:net_cfg 45/45、ftp_cfg 16/16 PASS,既有 network/ftp 段行为不变
- 越界改动说明:TC-CONFIG-006~008 定义行登记进属主文档
  docs/测试说明/配置文件单元测试说明.md(id-check 增量拦截要求在属主登记处
  补定义,该文件不在本卡写范围但为门禁唯一修复路径,仅追加 3 行用例 + 1 行
  变更记录,lead 评审时留意)

gate:@build 0 错误 0 警告(Code=214104 RO=18860 RW=1204 ZI=131852);
RAM 增量 0 B(RW/ZI 与 ram_baseline 2026-10-24 行完全一致,IRAM1 57.7%、
CCM 87.9% 不变),Flash +324 B;uvprojx 零挂载变化、未入库。
staged 门禁全绿,唯 mem-numbers / mem-budget 因 Flash 增量需台账回填
(--write 目标 docs/内存管理/** 属单写者文件,本道不得触碰):
gate: only mem-budget/mem-numbers pending lead ledger backfill
(--all-files 全量自查确认失败面仅此两条;BOM/CR/U+FFFD/字面量 ASCII 已
手工自查)
2026-10-25 03:13:32 +08:00

990 lines
40 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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(数据可信卡 3:负零点偏移可落盘)
* 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 拒绝
* TC-CONFIG-024 键名超长拒绝(≥ 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) 且原文件不动
* TC-CONFIG-044 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动(P2-1);
* 非受管键的超长行照旧放过(原样保留)
* —— poll 契约(cfg_file_poll)——
* TC-CONFIG-050 首轮建立基线(不重载)
* TC-CONFIG-051 沉降去抖:连续 2 次稳定才重载
* TC-CONFIG-052 与基线一致不重载
* 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 有符号往返(卡 3)。
* 字段按自然对齐排布: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");
}
/* ===================== 用例 ===================== */
/* 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.1.100") == 0 &&
d[0] == 192u && d[1] == 168u && d[2] == 1u && 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 解码(数据可信卡 3——有符号零点偏移) */
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-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_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("[TC-CONFIG-024] 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("[TC-CONFIG-044] over-long managed key line -> reject (P2-1)\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_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;
}