门禁扩容:新增 lv-conf-sync、simulator/ 纳入全部钩子;含 L3 定值/元件框架落地
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 LVGL 特性裁剪与 L3 保护定值/元件框架。 门禁(.pre-commit-config.yaml,26 个钩子) - 新增 scripts/check_lv_conf_sync.py + 钩子 lv-conf-sync:比对两份 lv_conf.h 的**生效值**(未定义则取 lv_conf_internal.h 兜底默认), 抓 MISMATCH / LVGL 不认的宏名 / 同文件重复定义 - 顶层 exclude 删掉 simulator/:其下 21 个 tracked 文件全是本项目自研 代码,生成物(build/、SDL2/、tests/ref/、shot*)已在 .gitignore; doc-links 的 exclude 同步删除 - lv-conf-sync 的 files: 覆盖两端配置 → 只改 simulator/lv_conf.h 也触发 (pre-commit 4.x 已移除 always: true,实测只告警并跳过,无兜底开关) - 新增 prot-set-unit-test / prot-elm-unit-test 两个主机侧单测钩子 - S11(2.3.5) 判定细化:禁的是"用路径找文件",尖括号系统头带子目录 (如 <sys/stat.h>)不再误报;全仓库复核仅 2 处项目代码判定变化 模拟器代码整改(纳入钩子后暴露,全部修代码而非豁免) - cppcheck 4 处:sim_syslog.c 判空分支内解引用 p_epoch(真实 bug)、 2 处 struct tm 未初始化、1 处指针可加 const - coding-standard 61 处:44 处函数左大括号并到右括号同行、8 处补函数 注释、3 处补文件头注释块、2 处 __ 前缀保护宏改 XXX_H、2 处 sprintf 改 snprintf(写入字节数与原来完全一致) LVGL 特性裁剪(F-1/F-2/F-3) - 两端 lv_conf.h 对齐同一特性子集:删 26 个 LVGL 9.3 不存在的选项名、 3 处重复定义,10 项靠默认值隐式生效的改为显式固化 - 实测对镜像体积中性;价值在防漂移,且模拟器 golden 回归跑的正是固件 实际编译的配置 L3 软件侧 - App/algo/prot_set.* Q10 定值模型(范围校验 / 整表 all-or-nothing / 校验和封存-自检-闭锁)+ prot_elm.* 通用"滞回+定时限"元件框架 7 实例 - test/host/prot_set、test/host/prot_elm 主机侧单测;uvprojx 挂载两处 文档同步 - pre-commit使用说明:§2.8 S11 口径、§2.15 lv-conf-sync 详解、新增 §5.5 复盘(教训:exclude 只写真正不该进钩子的路径) - AGENTS.md、UI_设计架构 §1.4 第 10 条、RAM 分析、审查报告刷新与销项 验证 - @build BUILD_OK 0/0(Code=194276 RO=15872 RW=876 ZI=139892) - 模拟器重建:菜单 8 状态像素逐字节一致(池 5368 B)、11 内容页黄金参考 全过(往返 5448 B)、remo sink 0/25600 位差异、--logtest/--cfgtest PASS - 全仓库 cppcheck / coding-standard / no-mojibake --all-files 全过 - check_doc_links --all 129/0;check_mem_numbers --check 内存数字一致 - 端到端:往 simulator/lv_conf.h 注入一行漂移 → 钩子 FAIL(exit 1)
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 模块名称:保护元件框架(L3,P1 子集)主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/algo/prot_elm.c 的判据核做离线验证。
|
||||
*
|
||||
* 被测对象:真实源文件(#include 原样纳入):
|
||||
* prot_elm.c + prot_set.c(判据吃定值 RAM)
|
||||
* phasor.c + sample_ring.c(prot_elm.c 链接依赖;本测试只走
|
||||
* prot_elm_eval 纯判据路径,不依赖 run() 适配层)
|
||||
*
|
||||
* 覆盖点(docs/算法/继电保护功能复现规划.md §3.3 L3-1/3/4/6、§8 验收):
|
||||
* TC-PE-001 init:全状态清零、动作位图 0
|
||||
* TC-PE-002 过量滞回 + 定时限:1.01× 定值启动、延时到点动作、
|
||||
* 返回系数内保持、跌破返回门槛释放(1.2×/0.9× 验收口径的核)
|
||||
* TC-PE-003 延时 0:启动同拍即动作
|
||||
* TC-PE-004 欠量方向(UV):低于定值启动、高于返回门槛释放
|
||||
* TC-PE-005 投退=0:不判据、状态复位
|
||||
* TC-PE-006 定值闭锁(L5-1 联动):locked → 全元件复位;解锁后恢复
|
||||
* TC-PE-007 时基回绕:跨 0xFFFFFFFF 的延时判定仍正确
|
||||
* TC-PE-008 动作位图聚合
|
||||
* TC-PE-009 守卫:state(NULL)/越界编号、name 越界
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-11
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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)
|
||||
|
||||
#include "prot_set.c"
|
||||
#include "sample_ring.c"
|
||||
#include "phasor.c"
|
||||
#include "prot_elm.c"
|
||||
|
||||
/* 中性测量基线:电流 1.0 A、电压 100 V、3U0 0 V(全部不越限) */
|
||||
static void mk_base(float m[PE_COUNT]) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
|
||||
m[i] = 1.0f;
|
||||
}
|
||||
m[PE_OV] = 100.0f;
|
||||
m[PE_UV] = 100.0f;
|
||||
m[PE_3U0] = 0.0f;
|
||||
}
|
||||
|
||||
static void tc_init_clear(void) {
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
CHECK(prot_elm_active_mask() == 0u, "TC-PE-001 init: active mask 0");
|
||||
{
|
||||
prot_elm_state_t st;
|
||||
uint32_t i;
|
||||
int clean = 1;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
|
||||
prot_elm_state((prot_elm_id_t)i, &st);
|
||||
if ((st.picked != 0u) || (st.asserted != 0u) || (st.enabled != 0u)) {
|
||||
clean = 0;
|
||||
}
|
||||
}
|
||||
CHECK(clean, "TC-PE-001 init: all element states clear");
|
||||
}
|
||||
}
|
||||
|
||||
static void tc_over_hysteresis(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(100.0)); /* 100 ms */
|
||||
/* 默认 OC1_CUR=10.0 A,RET_OVER=0.95 → 返回门槛 ≈9.492 */
|
||||
|
||||
mk_base(m);
|
||||
m[PE_OC1] = 9.9f;
|
||||
prot_elm_eval(1000u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.picked == 0u) && (st.enabled != 0u),
|
||||
"TC-PE-002 0.99x set: no pickup");
|
||||
|
||||
m[PE_OC1] = 10.1f;
|
||||
prot_elm_eval(1001u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted == 0u),
|
||||
"TC-PE-002 1.01x set: pickup before delay");
|
||||
|
||||
prot_elm_eval(1100u, m); /* 1100-1001 = 99 ms */
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK(st.asserted == 0u, "TC-PE-002 not asserted at 99ms");
|
||||
|
||||
prot_elm_eval(1101u, m); /* 100 ms 整 */
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-002 asserted at 100ms");
|
||||
|
||||
m[PE_OC1] = 9.7f; /* 返回门槛(9.492)与动作门槛(10)之间 */
|
||||
prot_elm_eval(1200u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted != 0u),
|
||||
"TC-PE-002 hysteresis: holds between return and pickup threshold");
|
||||
|
||||
m[PE_OC1] = 9.4f; /* 跌破返回门槛 */
|
||||
prot_elm_eval(1300u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-002 below return threshold: drops out");
|
||||
}
|
||||
|
||||
static void tc_zero_delay(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OC3_DLY, PROT_SET_Q10(0.0));
|
||||
mk_base(m);
|
||||
m[PE_OC3] = 2.1f; /* OC3 定值 2.0 */
|
||||
prot_elm_eval(5000u, m);
|
||||
prot_elm_state(PE_OC3, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted != 0u),
|
||||
"TC-PE-003 dly=0: asserted on the same beat as pickup");
|
||||
}
|
||||
|
||||
static void tc_under_direction(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_UV_DLY, PROT_SET_Q10(50.0));
|
||||
/* 默认 UV_VOL=20.0,RET_UNDER=1.05 → 返回门槛 ≈20.996 */
|
||||
|
||||
mk_base(m);
|
||||
m[PE_UV] = 25.0f;
|
||||
prot_elm_eval(2000u, m);
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK(st.picked == 0u, "TC-PE-004 UV above set: no pickup");
|
||||
|
||||
m[PE_UV] = 19.0f;
|
||||
prot_elm_eval(2001u, m);
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK(st.picked != 0u, "TC-PE-004 UV below set: pickup");
|
||||
|
||||
prot_elm_eval(2051u, m); /* 50 ms */
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-004 UV asserted after delay");
|
||||
|
||||
m[PE_UV] = 20.5f; /* 20 与 20.996 之间:保持 */
|
||||
prot_elm_eval(2100u, m);
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-004 UV holds below return threshold");
|
||||
|
||||
m[PE_UV] = 21.5f; /* 高于返回门槛:释放 */
|
||||
prot_elm_eval(2200u, m);
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-004 UV above return threshold: drops out");
|
||||
}
|
||||
|
||||
static void tc_disabled(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OC2_EN, 0);
|
||||
mk_base(m);
|
||||
m[PE_OC2] = 999.0f;
|
||||
prot_elm_eval(3000u, m);
|
||||
prot_elm_state(PE_OC2, &st);
|
||||
CHECK((st.enabled == 0u) && (st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-005 enable=0: no judgement even at 999x");
|
||||
}
|
||||
|
||||
static void tc_set_lock(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
mk_base(m);
|
||||
m[PE_OC1] = 10.1f;
|
||||
(void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(0.0));
|
||||
prot_elm_eval(4000u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-006 pre: element operates while settings valid");
|
||||
|
||||
(void)prot_set_set(PS_OC1_CUR, PROT_SET_Q10(9.0)); /* 改值不封存 */
|
||||
CHECK(prot_set_selfcheck() == -1, "TC-PE-006 tamper detected by selfcheck");
|
||||
prot_elm_eval(4100u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.enabled == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-006 settings lock resets all elements (L5-1 gating)");
|
||||
|
||||
prot_set_seal();
|
||||
prot_elm_eval(4200u, m);
|
||||
prot_elm_state(PE_OC1, &st);
|
||||
CHECK((st.enabled != 0u) && (st.asserted != 0u),
|
||||
"TC-PE-006 unlock restores judgement");
|
||||
}
|
||||
|
||||
static void tc_tick_wrap(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OC2_DLY, PROT_SET_Q10(100.0));
|
||||
mk_base(m);
|
||||
m[PE_OC2] = 5.1f; /* OC2 定值 5.0 */
|
||||
prot_elm_eval(0xFFFFFFF0u, m); /* 启动点贴近回绕 */
|
||||
prot_elm_state(PE_OC2, &st);
|
||||
CHECK(st.picked != 0u, "TC-PE-007 pickup near tick wrap");
|
||||
|
||||
prot_elm_eval(0x00000010u, m); /* 回绕后 32 ms */
|
||||
prot_elm_state(PE_OC2, &st);
|
||||
CHECK(st.asserted == 0u, "TC-PE-007 not asserted at 32ms after wrap");
|
||||
|
||||
prot_elm_eval(0x00000060u, m); /* 回绕后 112 ms */
|
||||
prot_elm_state(PE_OC2, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-007 asserted at 112ms across wrap");
|
||||
}
|
||||
|
||||
static void tc_active_mask(void) {
|
||||
float m[PE_COUNT];
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(0.0));
|
||||
(void)prot_set_set(PS_OV_DLY, PROT_SET_Q10(0.0));
|
||||
mk_base(m);
|
||||
m[PE_OC1] = 10.1f;
|
||||
m[PE_OV] = 130.0f; /* OV 定值 120 */
|
||||
prot_elm_eval(6000u, m);
|
||||
CHECK(prot_elm_active_mask() == ((1uL << PE_OC1) | (1uL << PE_OV)),
|
||||
"TC-PE-008 active mask aggregates OC1+OV only");
|
||||
}
|
||||
|
||||
static void tc_guards(void) {
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
prot_elm_state(PE_COUNT, &st);
|
||||
CHECK((st.enabled == 0u) && (st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-009 out-of-range id -> zero state");
|
||||
prot_elm_state(PE_OC1, NULL); /* 不得崩溃 */
|
||||
CHECK(prot_elm_name(PE_COUNT) != NULL, "TC-PE-009 name out-of-range -> fallback");
|
||||
CHECK(prot_elm_name(PE_3U0) != NULL, "TC-PE-009 name valid id -> non-NULL");
|
||||
prot_elm_eval(7000u, NULL); /* NULL 测量:按 0 处理,不得崩溃 */
|
||||
CHECK(1, "TC-PE-009 eval(NULL meas) survives");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Prot Elm Tests ===\n");
|
||||
tc_init_clear();
|
||||
tc_over_hysteresis();
|
||||
tc_zero_delay();
|
||||
tc_under_direction();
|
||||
tc_disabled();
|
||||
tc_set_lock();
|
||||
tc_tick_wrap();
|
||||
tc_active_mask();
|
||||
tc_guards();
|
||||
|
||||
printf("\n=== Prot Elm Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""保护元件框架(L3,P1 子集)主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:用本机 MSVC 离线编译 test/host/prot_elm/prot_elm_test.c(#include 纳入
|
||||
生产源文件 prot_elm.c + prot_set.c + phasor.c + sample_ring.c),验证
|
||||
越限滞回 / 定时限 / 欠量方向 / 投退 / 定值闭锁联动 / 时基回绕 / 位图聚合。
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' test/host/prot_elm/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
|
||||
设计依据:docs/算法/继电保护功能复现规划.md §3.3(L3-1/3/4/6)、§8(验收判据)。
|
||||
创建日期:2026-10-11
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
BUILD = TestBuild(
|
||||
name="Prot Elm Tests",
|
||||
src=os.path.join(HERE, "prot_elm_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(ROOT, "App", "algo"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Prot Elm Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 模块名称:保护定值模型(P1)主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/algo/prot_set.c 做离线验证。
|
||||
*
|
||||
* 被测对象:真实源文件(#include 原样纳入)。纯逻辑,无需桩。
|
||||
*
|
||||
* 覆盖点(docs/算法/继电保护功能复现规划.md §5.2 / §3.4 L5-1):
|
||||
* TC-PS-001 init:默认值装载、越界位图 0、selfcheck 通过、未闭锁
|
||||
* TC-PS-002 set:范围内写入生效;越界拒写且原值不动
|
||||
* TC-PS-003 校验和封存语义:set 后未 seal → selfcheck 失败并闭锁;
|
||||
* seal → 通过并解锁(L5-1 核心闭环)
|
||||
* TC-PS-004 load_raw:合法整表装载并封存;含越界项 → 整表不装载 +
|
||||
* bad_mask 指认越界项(定值一致性:宁可全旧不可半新)
|
||||
* TC-PS-005 Q10 往返精度(±1 LSB)
|
||||
* TC-PS-006 元数据表自检:min<=max、投退项 0..1、非零通讯地址不重复
|
||||
* TC-PS-007 越界编号守卫(get/set)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-11
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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)
|
||||
|
||||
#include "prot_set.c"
|
||||
|
||||
/* TC-PS-001 */
|
||||
static void tc_init(void) {
|
||||
prot_set_init();
|
||||
CHECK(prot_set_bad_mask() == 0u, "TC-PS-001 init: no bad items");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-001 init: selfcheck pass");
|
||||
CHECK(prot_set_locked() == 0u, "TC-PS-001 init: not locked");
|
||||
CHECK(prot_set_get_f(PS_OC1_CUR) > 9.99f && prot_set_get_f(PS_OC1_CUR) < 10.01f,
|
||||
"TC-PS-001 default OC1 current = 10.0 A");
|
||||
CHECK(prot_set_get_ctl(PS_OC3_TRIP) == 0u,
|
||||
"TC-PS-001 default OC3 = alarm (trip off)");
|
||||
}
|
||||
|
||||
/* TC-PS-002 */
|
||||
static void tc_set_range(void) {
|
||||
prot_set_init();
|
||||
CHECK(prot_set_set(PS_OC2_CUR, PROT_SET_Q10(7.5)) == 0,
|
||||
"TC-PS-002 in-range set accepted");
|
||||
CHECK(prot_set_get_f(PS_OC2_CUR) > 7.49f && prot_set_get_f(PS_OC2_CUR) < 7.51f,
|
||||
"TC-PS-002 value readable after set");
|
||||
CHECK(prot_set_set(PS_OC2_CUR, PROT_SET_Q10(101.0)) == -1,
|
||||
"TC-PS-002 over-max set rejected");
|
||||
CHECK(prot_set_get_f(PS_OC2_CUR) > 7.49f && prot_set_get_f(PS_OC2_CUR) < 7.51f,
|
||||
"TC-PS-002 rejected set leaves value unchanged");
|
||||
CHECK(prot_set_set(PS_OC1_EN, PROT_SET_Q10(2)) == -1,
|
||||
"TC-PS-002 ctl item rejects value 2");
|
||||
}
|
||||
|
||||
/* TC-PS-003 */
|
||||
static void tc_seal_selfcheck(void) {
|
||||
prot_set_init();
|
||||
CHECK(prot_set_set(PS_OC1_CUR, PROT_SET_Q10(8.0)) == 0, "TC-PS-003 set ok");
|
||||
CHECK(prot_set_selfcheck() == -1,
|
||||
"TC-PS-003 set-without-seal => selfcheck FAIL (tamper detect)");
|
||||
CHECK(prot_set_locked() == 1u, "TC-PS-003 selfcheck failure latches lock");
|
||||
prot_set_seal();
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-003 seal => selfcheck pass");
|
||||
CHECK(prot_set_locked() == 0u, "TC-PS-003 seal clears lock");
|
||||
}
|
||||
|
||||
/* TC-PS-004 */
|
||||
static void tc_load_raw(void) {
|
||||
int32_t tbl[PS_ID_COUNT];
|
||||
uint32_t i;
|
||||
|
||||
prot_set_init();
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
tbl[i] = prot_set_get((prot_set_id_t)i);
|
||||
}
|
||||
tbl[PS_OL_CUR] = PROT_SET_Q10(1.5);
|
||||
CHECK(prot_set_load_raw(tbl) == 0, "TC-PS-004 valid table loads");
|
||||
CHECK(prot_set_get_f(PS_OL_CUR) > 1.49f && prot_set_get_f(PS_OL_CUR) < 1.51f,
|
||||
"TC-PS-004 loaded value visible");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-004 load_raw auto-seals");
|
||||
|
||||
tbl[PS_OC3_DLY] = PROT_SET_Q10(60001.0); /* 越上限 */
|
||||
tbl[PS_OL_CUR] = PROT_SET_Q10(2.5); /* 合法改动 */
|
||||
CHECK(prot_set_load_raw(tbl) == -1, "TC-PS-004 bad table rejected");
|
||||
CHECK(prot_set_bad_mask() == (1uL << PS_OC3_DLY),
|
||||
"TC-PS-004 bad_mask points at the offending item");
|
||||
CHECK(prot_set_get_f(PS_OL_CUR) > 1.49f && prot_set_get_f(PS_OL_CUR) < 1.51f,
|
||||
"TC-PS-004 rejected load keeps OLD table (all-or-nothing)");
|
||||
CHECK(prot_set_load_raw(0) == -1, "TC-PS-004 NULL table rejected");
|
||||
}
|
||||
|
||||
/* TC-PS-005 */
|
||||
static void tc_q10_roundtrip(void) {
|
||||
float v = 12.345f;
|
||||
int32_t q;
|
||||
|
||||
prot_set_init();
|
||||
q = PROT_SET_Q10(v);
|
||||
CHECK(prot_set_set(PS_OC1_CUR, q) == 0, "TC-PS-005 set ok");
|
||||
{
|
||||
float back = prot_set_get_f(PS_OC1_CUR);
|
||||
float err = back - v;
|
||||
if (err < 0.0f) { err = -err; }
|
||||
CHECK(err < 0.001f, "TC-PS-005 Q10 roundtrip error < 1 LSB");
|
||||
}
|
||||
}
|
||||
|
||||
/* TC-PS-006 */
|
||||
static void tc_meta_sanity(void) {
|
||||
uint32_t i, j;
|
||||
int ok_range = 1, ok_ctl = 1, ok_addr = 1;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
const prot_set_meta_t *m = &g_prot_set_meta[i];
|
||||
if (m->min > m->max) { ok_range = 0; }
|
||||
if ((m->def < m->min) || (m->def > m->max)) { ok_range = 0; }
|
||||
if ((m->is_ctl != 0u) &&
|
||||
((m->min != 0) || (m->max != PROT_SET_Q10(1)))) { ok_ctl = 0; }
|
||||
if (m->comm_addr == 0u) { continue; }
|
||||
for (j = i + 1u; j < (uint32_t)PS_ID_COUNT; j++) {
|
||||
if (g_prot_set_meta[j].comm_addr == m->comm_addr) { ok_addr = 0; }
|
||||
}
|
||||
}
|
||||
CHECK(ok_range, "TC-PS-006 meta: min<=def<=max for all items");
|
||||
CHECK(ok_ctl, "TC-PS-006 meta: ctl items are 0..1");
|
||||
CHECK(ok_addr, "TC-PS-006 meta: comm addresses unique");
|
||||
CHECK((uint32_t)PS_ID_COUNT <= 32u, "TC-PS-006 item count fits bad_mask bitmap");
|
||||
}
|
||||
|
||||
/* TC-PS-007 */
|
||||
static void tc_id_guard(void) {
|
||||
prot_set_init();
|
||||
CHECK(prot_set_get(PS_ID_COUNT) == 0, "TC-PS-007 get out-of-range id -> 0");
|
||||
CHECK(prot_set_set(PS_ID_COUNT, 0) == -1, "TC-PS-007 set out-of-range id -> -1");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Prot Set Tests ===\n");
|
||||
tc_init();
|
||||
tc_set_range();
|
||||
tc_seal_selfcheck();
|
||||
tc_load_raw();
|
||||
tc_q10_roundtrip();
|
||||
tc_meta_sanity();
|
||||
tc_id_guard();
|
||||
|
||||
printf("\n=== Prot Set Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""保护定值模型(P1)主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:用本机 MSVC 离线编译 test/host/prot_set/prot_set_test.c(#include 纳入
|
||||
生产源文件 App/algo/prot_set.c),验证 Q10 定点模型 / 范围校验 /
|
||||
整表装载 all-or-nothing / 校验和封存-自检-闭锁(L5-1)闭环。
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' test/host/prot_set/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
|
||||
设计依据:docs/算法/继电保护功能复现规划.md §5(定值参数模型)、§3.4(L5-1)。
|
||||
创建日期:2026-10-11
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
BUILD = TestBuild(
|
||||
name="Prot Set Tests",
|
||||
src=os.path.join(HERE, "prot_set_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(ROOT, "App", "algo"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Prot Set Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
Reference in New Issue
Block a user