/* * 模块名称:运行参数段(run_par)主机侧单元测试 * 模块功能:离线钉死「数据可信」运行参数段的三层契约——① [run_par] cfg 段: * 注册/工厂默认/落盘往返/文件改值生效(真实 cfg 引擎 + 内存虚拟盘, * 同 adc_cal 套件先例);② 104 地址映射:命中集/空隙/两段式/分档 * 量程守卫;③ k/b 覆盖直通 adc_cal_set(真实标定层):合法带拒绝、 * 单侧 RMW 保另一侧、回显 = 生效值视图。 * 被测对象:真实源文件(#include 纳入):run_par.c + cfg_file.c + adc_cal.c; * dbg 计数桩(stub/dbg_log.h)+ cfg_store_* 内存虚拟盘 + adc_input_desc * V0.1 权威映射桩。**路由顺序证据(run_par 表先 / prot_set 后)在 * test/host/iec104 TC-IEC104-029 钉(那边才有全协议栈)**。 * * 覆盖点(TC-RUNPAR-xxx;属主登记处 = 本目录,族 TC-HOST-RUNPAR): * TC-RUNPAR-001 init 工厂默认 = 工程量重定后的额定档 + idx 守卫 * TC-RUNPAR-002 死区写往返 + 分档上限(V/A/Hz)+ NaN/负值拒 + 定点量化 * TC-RUNPAR-003 落盘链:set→脏→store_save 成功清脏 + 文件含 9 键 + * load 回读往返 + 外部改文件值生效 + 无脏 save 空操作 * TC-RUNPAR-004 104 读映射:0x8030~0x8038 命中 / 空隙与邻段 -1 / k·b 回显 * TC-RUNPAR-005 104 两段式:选择段不提交不置脏,执行段提交置脏 * TC-RUNPAR-006 k/b 直通:合法带接受与拒绝 / 非正 k 拒 / b 单侧 RMW 保 k / * 非法带值经 load 注入后回显 = 生效值(哨兵视图) * TC-RUNPAR-007 init 幂等:重复调用复位默认 + 清脏;重注册被引擎拒(留痕) * TC-RUNPAR-008 引擎副本 = 生效值(无回调):load 原地改副本即时可见 * * 通过:全部 TC PASS(main 返回 0)。 * 创建日期:2026-10-25(道D 运行参数段) */ #include #include #include #include /* 先桩头后生产源码(顺序敏感,同 adc_cal 套件) */ #include "dbg_log.h" /* ===================== 内存虚拟文件系统(cfg_store_* 强符号) ===================== */ #include "cfg_file_store.h" #define VFS_CAP 8192u 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_vfs_writes = 0; void *cfg_store_alloc(size_t size) { 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; if (!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; if (len > VFS_CAP) { return -1; } (void)memcpy(s_vfs, p_buf, len); s_vfs_len = len; s_vfs_exists = 1; s_vfs_mtime++; s_vfs_writes++; return 0; } int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) { (void)p_path; if (!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 "/config.ini"; } const char *cfg_store_tmp_path(void) { return "/config.tmp"; } /* ---- 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++; } /* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md) ---- */ #include "adc_input.h" static const adc_channel_desc_t s_desc_stub[ADC_SEM_COUNT] = { { ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */ { ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */ { ADC_SRC_STM32, 3u }, /* UC = CH3 */ { ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */ { ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */ { ADC_SRC_STM32, 6u }, /* IB = CH6 */ { ADC_SRC_STM32, 1u }, /* IC = CH1 */ { ADC_SRC_STM32, 2u }, /* IN = CH2 */ { ADC_SRC_STM32, 5u }, /* TEMP = CH5 */ }; const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) { if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) { return NULL; } return &s_desc_stub[(unsigned)ch]; } /* dbg 计数定义(stub/dbg_log.h extern 声明) */ int g_rptest_err_count = 0; int g_rptest_info_count = 0; int g_rptest_debug_count = 0; /* ===================== 测试框架(极简,同 adc_cal 套件) ===================== */ 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) #define ASSERT_NEAR(a, b, eps, name) CHECK(fabsf((float)(a) - (float)(b)) <= (eps), name) /* ===================== 被测代码 ===================== */ #include "cfg_file.c" #include "adc_cal.c" #include "run_par.c" static void vfs_reset(void) { s_vfs_len = 0u; s_vfs_mtime = 100u; s_vfs_exists = 0; } /* 全复位:引擎段复位 + run_par 复位 + 标定复位(重注册被拒 = 预期,清计数) */ static void reset_all_rp(void) { run_par_init(); adc_cal_init(); g_rptest_err_count = 0; } /* ===================== 测试用例 ===================== */ static void tc_defaults(void) { uint8_t i; vfs_reset(); reset_all_rp(); for (i = 0u; i < 4u; i++) { CHECK(fabsf(run_par_deadband(i) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 voltage ch factory 0.5 V"); } for (i = 4u; i < 8u; i++) { CHECK(fabsf(run_par_deadband(i) - 0.005f) <= 1e-9f, "TC-RUNPAR-001 current ch factory 0.005 A"); } CHECK(fabsf(run_par_deadband(8u) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 freq factory 0.5 Hz"); CHECK(run_par_deadband(9u) == 0.0f, "TC-RUNPAR-001 idx guard returns zero"); CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-001 init leaves clean"); } static void tc_set_range(void) { volatile float fnan; vfs_reset(); reset_all_rp(); CHECK((run_par_deadband_set(0u, 0.7f) == 0) && (fabsf(run_par_deadband(0u) - 0.7f) <= 1e-6f), "TC-RUNPAR-002 write round trip voltage domain"); CHECK((run_par_deadband_set(4u, 0.02f) == 0) && (fabsf(run_par_deadband(4u) - 0.02f) <= 1e-6f), "TC-RUNPAR-002 write round trip current domain"); CHECK(run_par_deadband_set(0u, 50.0f) == 0, "TC-RUNPAR-002 voltage cap endpoint inclusive"); CHECK((run_par_deadband_set(0u, 50.001f) == -1) && (fabsf(run_par_deadband(0u) - 50.0f) <= 1e-6f), "TC-RUNPAR-002 over voltage cap rejected"); CHECK(run_par_deadband_set(4u, 1.001f) == -1, "TC-RUNPAR-002 current cap 1 A enforced"); CHECK(run_par_deadband_set(8u, 5.001f) == -1, "TC-RUNPAR-002 freq cap 5 Hz enforced"); CHECK(run_par_deadband_set(0u, -0.001f) == -1, "TC-RUNPAR-002 negative rejected"); fnan = 0.0f; fnan = fnan / fnan; CHECK(run_par_deadband_set(0u, fnan) == -1, "TC-RUNPAR-002 NaN rejected"); CHECK(run_par_deadband_set(9u, 1.0f) == -1, "TC-RUNPAR-002 idx guard rejected"); CHECK((run_par_deadband_set(2u, 0.1234f) == 0) && (fabsf(run_par_deadband(2u) - 0.123f) <= 1e-6f), "TC-RUNPAR-002 milli quantization rounds to nearest"); } static void tc_persist(void) { char probe[24]; vfs_reset(); reset_all_rp(); CHECK(run_par_store_save() == 0, "TC-RUNPAR-003 clean save is no-op ok"); CHECK(run_par_deadband_set(2u, 0.9f) == 0, "TC-RUNPAR-003 seed write ok"); CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 write marks dirty"); CHECK((run_par_store_save() == 0) && (run_par_dirty_peek() == 0u), "TC-RUNPAR-003 save clears dirty on success"); (void)memset(probe, 0, sizeof(probe)); (void)memcpy(probe, "db_uc=", 6u); CHECK((s_vfs_exists == 1) && (strstr((const char *)s_vfs, "db_uc=900") != NULL), "TC-RUNPAR-003 file carries milli value"); CHECK(strstr((const char *)s_vfs, "db_freq=500") != NULL, "TC-RUNPAR-003 all nine keys present (freq spot check)"); /* 往返:改内存 → 落盘 → 引擎 load 回读 */ CHECK(run_par_deadband_set(4u, 0.02f) == 0, "TC-RUNPAR-003 second seed"); (void)run_par_store_save(); run_par_deadband_set(4u, 0.5f); /* 覆盖内存态 */ CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 engine reload ok"); CHECK(fabsf(run_par_deadband(4u) - 0.02f) <= 1e-9f, "TC-RUNPAR-003 reload restores last saved value"); /* set(4,0.5) 是一次未落盘的本地整定 → 脏必须活过 reload(reload 不是 * 清脏事件;只有 save 成功才清) */ CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 unsaved local edit survives reload"); /* 外部改档(FTP 手改 config.ini 的现场形态):load 后副本即生效值 */ { static const char s_edited[] = "[run_par]\ndb_ua=250\ndb_ub=500\ndb_uc=500\ndb_un=500\n" "db_ia=10\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=800\n"; (void)memcpy(s_vfs, s_edited, sizeof(s_edited) - 1u); s_vfs_len = sizeof(s_edited) - 1u; s_vfs_exists = 1; s_vfs_mtime++; } CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 edited file accepted"); CHECK((fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f) && (fabsf(run_par_deadband(4u) - 0.01f) <= 1e-9f) && (fabsf(run_par_deadband(8u) - 0.8f) <= 1e-6f), "TC-RUNPAR-003 edited values live in copy immediately"); /* 越界文件值:引擎照收(int32 域),104/COV 读到超程值 —— 守卫在**写路径**, * 文件通路的防线是"人工编辑 + 沉降去抖 + 可再改回";与 adc_cal 合法带不同, * 死区超程不毁量纲(只是上送变钝),此处钉住"引擎不吞值"即可 */ CHECK(fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f, "TC-RUNPAR-003 value stable re-read"); } static void tc_104_read(void) { float v; vfs_reset(); reset_all_rp(); (void)run_par_deadband_set(0u, 0.6f); CHECK((run_par_104_read(0x8030u, &v) == 0) && (fabsf(v - 0.6f) <= 1e-6f), "TC-RUNPAR-004 db first slot hit"); CHECK((run_par_104_read(0x8038u, &v) == 0) && (fabsf(v - 0.5f) <= 1e-6f), "TC-RUNPAR-004 db last slot (freq) hit"); CHECK(run_par_104_read(0x8039u, &v) == -1, "TC-RUNPAR-004 gap address not owned"); CHECK(run_par_104_read(0x8027u, &v) == -1, "TC-RUNPAR-004 prot_set alarm addr untouched"); CHECK(run_par_104_read(0x802Fu, &v) == -1, "TC-RUNPAR-004 below base not claimed"); CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f), "TC-RUNPAR-004 k echo = adc_cal effective UA"); CHECK((run_par_104_read(0x8047u, &v) == 0) && (fabsf(v - 805.6640625f) <= 1e-4f), "TC-RUNPAR-004 k echo last channel (IN, STM32 ROM)"); CHECK((run_par_104_read(0x8048u, &v) == 0) && (v == 0.0f), "TC-RUNPAR-004 b echo factory zero"); CHECK(run_par_104_read(0x8050u, &v) == -1, "TC-RUNPAR-004 above b block not owned"); } static void tc_104_two_step(void) { float v; vfs_reset(); reset_all_rp(); CHECK((run_par_104_write(0x8031u, 0.8f, 1u) == 0) && (fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f) && (run_par_dirty_peek() == 0u), "TC-RUNPAR-005 select validates without commit or dirty"); CHECK((run_par_104_write(0x8031u, 0.8f, 0u) == 0) && (fabsf(run_par_deadband(1u) - 0.8f) <= 1e-6f) && (run_par_dirty_peek() == 1u), "TC-RUNPAR-005 execute commits and marks dirty"); CHECK(run_par_104_write(0x8031u, 60.0f, 1u) == -1, "TC-RUNPAR-005 range reject in select"); CHECK(run_par_104_write(0x8039u, 1.0f, 0u) == -2, "TC-RUNPAR-005 foreign addr passes through"); CHECK(run_par_104_write(0x8027u, 1.0f, 0u) == -2, "TC-RUNPAR-005 alarm addr passes through"); CHECK((run_par_104_read(0x8031u, &v) == 0) && (fabsf(v - 0.8f) <= 1e-6f), "TC-RUNPAR-005 committed value readable at same addr"); } static void tc_104_cal_proxy(void) { float v; vfs_reset(); reset_all_rp(); CHECK(run_par_104_write(0x8040u, 300.0f, 0u) == 0, "TC-RUNPAR-006 k in-band accepted"); CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 300.0f) <= 0.01f), "TC-RUNPAR-006 k echo follows adc_cal_set"); CHECK((run_par_104_write(0x8040u, 400.0f, 0u) == -1) && (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f), "TC-RUNPAR-006 k over band rejected by adc_cal, RAM intact"); CHECK((run_par_104_write(0x8040u, 70.0f, 0u) == -1), "TC-RUNPAR-006 k under band rejected (0.5x ROM)"); CHECK(run_par_104_write(0x8040u, -1.0f, 0u) == -1, "TC-RUNPAR-006 non-positive k rejected"); CHECK((run_par_104_write(0x8048u, -5.0f, 0u) == 0) && (fabsf(adc_cal_b_codes(ADC_SEM_UA) + 5.0f) <= 1e-3f) && (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f), "TC-RUNPAR-006 b single-side RMW keeps k"); CHECK(run_par_104_write(0x8048u, -9000.0f, 0u) == -1, "TC-RUNPAR-006 b over +-8192 rejected"); CHECK((run_par_104_write(0x8040u, 200.0f, 1u) == 0) && (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f), "TC-RUNPAR-006 k select does not touch adc_cal"); /* 生效值视图回显:文件塞越带 k → 哨兵回退 ROM → 104 回显 ROM */ { static const char s_badk[] = "[adc_cal]\nk_ua=99011483\nvalid=1\n"; (void)memcpy(s_vfs, s_badk, sizeof(s_badk) - 1u); s_vfs_len = sizeof(s_badk) - 1u; s_vfs_exists = 1; s_vfs_mtime++; } CHECK(cfg_file_load() == 0, "TC-RUNPAR-006 poisoned file loaded by engine"); CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f), "TC-RUNPAR-006 echo = effective (ROM fallback), not raw file value"); reset_all_rp(); CHECK(cfg_file_save() == 0, "TC-RUNPAR-006 reset file rewritten"); } static void tc_init_idempotent(void) { int err_before; vfs_reset(); reset_all_rp(); (void)run_par_deadband_set(1u, 0.9f); (void)run_par_104_write(0x8040u, 200.0f, 0u); err_before = g_rptest_err_count; run_par_init(); CHECK(fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 re-init restores defaults"); CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-007 re-init clears dirty"); CHECK(g_rptest_err_count > err_before, "TC-RUNPAR-007 dup register leaves trace"); CHECK(fabsf(run_par_deadband(0u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 values intact after dup reg"); } static void tc_copy_is_effective(void) { static const char s_file[] = "[run_par]\ndb_ua=650\ndb_ub=500\ndb_uc=500\ndb_un=500\n" "db_ia=5\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=500\n"; vfs_reset(); reset_all_rp(); (void)memcpy(s_vfs, s_file, sizeof(s_file) - 1u); s_vfs_len = sizeof(s_file) - 1u; s_vfs_exists = 1; s_vfs_mtime++; CHECK(cfg_file_load() == 0, "TC-RUNPAR-008 load ok"); CHECK(fabsf(run_par_deadband(0u) - 0.65f) <= 1e-6f, "TC-RUNPAR-008 no cache layer: copy read is effective value"); CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-008 load itself is not a local edit"); } int main(void) { printf("=== RunPar Layer Host Tests ===\n"); tc_defaults(); tc_set_range(); tc_persist(); tc_104_read(); tc_104_two_step(); tc_104_cal_proxy(); tc_init_idempotent(); tc_copy_is_effective(); printf("=== RunPar Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }