feat(UI): 装置维护 3 页落地(时钟 / 强制复归 / 清除记录),内容页黄金 27→40
三个薄页面模块 ui_page_clock / ui_page_reset / ui_page_clear,页面注册由生成器 自动写双端(裁定 A-1:「三叶各绑一页」与「零新增 .c」不可兼得,选薄页面加生成器, 不写手写 node switch——那种分发在增删叶子时会静默换功能,且没有任何门禁照得到)。 数据侧是注入式函数表:UI 不 include App 层的头。两条硬理由都实测过——分层方向 (layer-deps)之外,模拟器根本不链 sys_clock,ALLOW 豁免挡得住 layer-deps, 挡不住模拟器链接期。面板对时的唯一真判据是回读 RTC:sys_clock_set() 恒返 0 (RTC 写失败只打日志),所以真机侧写 1 后回读 1,最坏约 200 ms 落在一次「保存」 按键上,IWDG 800 ms 窗有余;上界与依据都写进注释。 「无时钟」徽标位置有两个像素级实测坑:放标题带右侧会被 4 字标题连成「时钟设置 无时钟」一句话;放页脚带左侧 x=7 会压住页脚——页脚从 x=6 起排,不是居中。 最终落在清单末行与页脚之间的空带 y=124。clear_done 那张顺带给出像素证据: 清 run.log 之后「操作记录」那一行从 2 变 3,页面上没缓存、没撒谎。 ui_w_records 的 UI_W_REC_LOG_* 顺序必须等于 syslog_id_t 顺序(7 类,只准尾追加), 已加编译期顺序守卫;执行侧 7 条映射表同样带编译期守卫。 测试与真值:内容页黄金 27 → 40 页(+13),文本断言 +68、反向断言 ABSENT +19; 27 张旧参考 git diff 为空 = 逐字节未变。往返池 5448 B 与菜单常驻堆 5368 B 均未变; 字库未扩(源码在用 171 并 KEEP_EXTRA 39 = 清单 183),font-charset 与 cn-chars、 cn-font 字节一致。@build 0 错误 0 警告;uiTask 静态最坏链 1,336 B / 16,384 (余 91%)与基线相同,页面钩子没进最坏链。 一条测试学结论单独留痕:反向断言不能当「位置不重叠」的证据——本脚本的字形匹配 把 y 带判得比字面宽(徽标在 125..136、页脚在 144..155,仍被 (140,160) 判命中)。 该防线改由正向断言「页脚未被徽标压住」承担(压字那一版实测过不了它)。 两条自我更正一并记档(作者自报,按真值而非原口径): ① 先前报「文案零新增汉字」是错的——观测行拟用「动作元件」,「元」不在子集, cn-chars 与 font-charset 如实红;最终改「保护动作 N」才让「零新增」成为事实。 顺序是:先按机器红,再按真值写结论。 ② 先前报「6 个文件 BOM 逐个核过」被 bom-guard 证伪:丢 BOM 发生在上一次编译之后, 正是 AGENTS 那句「历史 0 警告不代表安全」的活例;此后一律以退出码为准。 未覆盖项不假装:台架「N>0 → N=0」在产品构建的未标定闭锁期拿不到(闭锁期 N 恒 0), 判据显式退化为「页面受理 + 落一条操作记录」,写在 rs_watch 注释里。 生成物入库例外照用:uvprojx 只加 3 个 ui_page_ 的 FilePath(真增删源文件), CMakeLists 同批;两处都由 gen_menu.py 自己写,没手写。 本笔先走了一次真钩子:20 条钩子实跑通过(ui-golden 菜单 8 状态加内容页 40 页、 generated-fresh、registration-check、font-charset、ui-data 单测、cppcheck、 coding-standard、bom、mojibake、id-check、doc-links 等),只有 mem-numbers 与 mem-budget 两条红——磁盘上的 .map 出自 20:39 那次构建、含三条在飞道的代码, 与 HEAD 台账必然不同码。按仓库既有约定(这两条等台账回填时允许 --no-verify 并 正文留证)落这一笔;回填由我统一在收尾一笔做。 三条在飞道(task-26 崩溃证据路径、task-16 与 task-17 谓词收敛和卷锁观测、 task-36 台架工具)的未暂存改动被 autostash 暂时收走,钩子看到的是 HEAD 加本笔; 过程中曾把 comm 道未跟踪的 iec104_comm.c 临时移出(它不在 stash 覆盖面内), 那次移出反而让 ui-golden 撞见 CMake 缓存里的悬空引用而红——已改用「先落 comm 那笔、再落本笔」的顺序消掉这个中间态。ui_data_maint_ready() 待删(只有测试在读 的自检口),排在本笔之后单独一笔。
This commit is contained in:
@@ -60,6 +60,21 @@
|
||||
* 已生效的前几项不回滚(prot_set 无回滚接口,谎称整批未写更坏)
|
||||
* TC-UIDATA-021 v2 帧扩展:UI_DATA_VER == 2、既有字段偏移逐字节见证(只允许尾
|
||||
* 追加)、cal_block 两种态(未标定/已标定)整帧往返
|
||||
* ── 以下 5 组 = 装置维护通道与时钟换算(道F task-3,
|
||||
* docs/任务规划/UI补齐.md §2 第 3 行「装置维护 3 页」的地基)──
|
||||
* TC-UIDATA-022 时钟换算与"无效值拒收":12 条锚点正向换算 + 反函数互逆 +
|
||||
* **时区反证**(不得含 28800 偏移)+ 逐月天数(闰/平)+ 非法组合
|
||||
* 逐条 -2 + epoch 0/越域反算 -2(1970 绝不被反算出来)
|
||||
* TC-UIDATA-023 时标串 parse/format:正常串、空串(无效时钟)、记录侧占位串
|
||||
* "1970-01-01 08:00:00" 必须解析失败、分隔位/长度/尾字符/型对值非法
|
||||
* TC-UIDATA-024 跨层编号顺序契约:UI_W_REC_LOG_* 逐字等于 syslog_id_t 枚举序 +
|
||||
* UI 侧缓冲容量与 SYSLOG 契约同宽 + UI_DATA_CLEAR_ST_* 与
|
||||
* SYSLOG_CLEAR_* 逐值相等 + 面板两条留痕串各自不超描述上限
|
||||
* ("与远端串不同"不在这里判:字面量互比是恒真空断言,被
|
||||
* test-cppcheck 抓过,真判据落在 TC-UIDATA-026s)
|
||||
* TC-UIDATA-025 维护通道未注入防御:六口安全返回 + 残表整表拒绝且不覆盖好表
|
||||
* TC-UIDATA-026 注入后转发言行:epoch 0 哨兵不下发、reset_req 自行投操作记录=0 次
|
||||
* (谁触发谁投,防"远端 + 本地"双投)、clear 状态码直通不改码
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||
@@ -84,6 +99,8 @@
|
||||
#include "ui_w_soe.c"
|
||||
#include "soe_rec.h" /* soe_type_t:事件类型编号真源 */
|
||||
#include "prot_elm.h" /* prot_elm_id_t:元件编号真源 */
|
||||
#include "sys_log.h" /* syslog_id_t / SYSLOG_CLEAR_*:记录编号与清除状态真源 */
|
||||
#include "ui_w_records.h" /* UI_W_REC_LOG_*:UI 侧记录源编号(同一 TU 里核对顺序契约) */
|
||||
|
||||
/* ===================== 断言框架 ===================== */
|
||||
|
||||
@@ -886,6 +903,396 @@ static void tc_param_frame_v2(void)
|
||||
CHECK(got.cal_block == 0u, "TC-UIDATA-021j zero-initialized frame means not blocked");
|
||||
}
|
||||
|
||||
/* ============ 装置维护写通道与时钟换算(道F task-3,UI 补齐 §2 第 3 行)============ */
|
||||
|
||||
/*
|
||||
* 维护执行侧桩件:只回答"哪个口被调了几次、最后一次收到什么",不做任何业务判断。
|
||||
* 计数而不是布尔:多处偷投同一条操作记录(本道最怕的"远端 + 本地各投一次")
|
||||
* 只有靠次数才照得出来。
|
||||
*/
|
||||
static int s_stub_clock_rc;
|
||||
static int s_stub_clock_calls;
|
||||
static uint32_t s_stub_clock_last;
|
||||
static int s_stub_reset_rc;
|
||||
static int s_stub_reset_calls;
|
||||
static uint32_t s_stub_active;
|
||||
static int s_stub_note_calls;
|
||||
static char s_stub_note_last[32];
|
||||
static int s_stub_clear_calls;
|
||||
static uint8_t s_stub_clear_last_id;
|
||||
static int s_stub_clear_state_calls;
|
||||
static uint8_t s_stub_clear_state_id;
|
||||
static int s_stub_clear_state_rc;
|
||||
|
||||
static int stub_clock_set(uint32_t a_epoch)
|
||||
{
|
||||
s_stub_clock_calls++;
|
||||
s_stub_clock_last = a_epoch;
|
||||
return s_stub_clock_rc;
|
||||
}
|
||||
|
||||
static int stub_reset_req(void)
|
||||
{
|
||||
s_stub_reset_calls++;
|
||||
return s_stub_reset_rc;
|
||||
}
|
||||
|
||||
static uint32_t stub_active_count(void)
|
||||
{
|
||||
return s_stub_active;
|
||||
}
|
||||
|
||||
static void stub_oper_note(const char *a_desc)
|
||||
{
|
||||
s_stub_note_calls++;
|
||||
if (a_desc != NULL) {
|
||||
(void)strncpy(s_stub_note_last, a_desc, sizeof(s_stub_note_last) - 1u);
|
||||
s_stub_note_last[sizeof(s_stub_note_last) - 1u] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int stub_clear_req(uint8_t a_id)
|
||||
{
|
||||
s_stub_clear_calls++;
|
||||
s_stub_clear_last_id = a_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stub_clear_state(uint8_t a_id)
|
||||
{
|
||||
s_stub_clear_state_calls++;
|
||||
s_stub_clear_state_id = a_id;
|
||||
return s_stub_clear_state_rc;
|
||||
}
|
||||
|
||||
static void stub_reset_all(void)
|
||||
{
|
||||
s_stub_clock_rc = 0;
|
||||
s_stub_clock_calls = 0;
|
||||
s_stub_clock_last = 0u;
|
||||
s_stub_reset_rc = 0;
|
||||
s_stub_reset_calls = 0;
|
||||
s_stub_active = 0u;
|
||||
s_stub_note_calls = 0;
|
||||
s_stub_note_last[0] = '\0';
|
||||
s_stub_clear_calls = 0;
|
||||
s_stub_clear_last_id = 0u;
|
||||
s_stub_clear_state_calls = 0;
|
||||
s_stub_clear_state_id = 0u;
|
||||
s_stub_clear_state_rc = UI_DATA_CLEAR_ST_NONE;
|
||||
}
|
||||
|
||||
static const ui_data_maint_ops_t s_maint_stub = {
|
||||
stub_clock_set, stub_reset_req, stub_active_count,
|
||||
stub_oper_note, stub_clear_req, stub_clear_state
|
||||
};
|
||||
|
||||
/* 故意缺一条(clear_state 为 NULL)的残表:注入必须整表拒绝,不能半途接住 */
|
||||
static const ui_data_maint_ops_t s_maint_broken = {
|
||||
stub_clock_set, stub_reset_req, stub_active_count,
|
||||
stub_oper_note, stub_clear_req, NULL
|
||||
};
|
||||
|
||||
/* 日历用例:字段 → 期望秒(期望值由独立实现(Python/JS 格里历)算出,不自我复制) */
|
||||
typedef struct {
|
||||
uint16_t y;
|
||||
uint8_t mo;
|
||||
uint8_t d;
|
||||
uint8_t h;
|
||||
uint8_t mi;
|
||||
uint8_t s;
|
||||
uint32_t epoch;
|
||||
} clock_case_t;
|
||||
|
||||
static const clock_case_t k_clock_cases[] = {
|
||||
{ 2000u, 1u, 1u, 0u, 0u, 0u, 946684800u }, /* 年域下限(RTC 年字段起点) */
|
||||
{ 2000u, 2u, 29u, 0u, 0u, 0u, 951782400u }, /* 千禧闰年 2/29 */
|
||||
{ 2000u, 3u, 1u, 0u, 0u, 0u, 951868800u }, /* 闰日次日:月序跨界 */
|
||||
{ 2001u, 2u, 28u, 23u, 59u, 59u, 983404799u }, /* 平年 2 月末 + 当日最大秒 */
|
||||
{ 2004u, 2u, 29u, 12u, 0u, 0u, 1078056000u }, /* 4 年闰规则 */
|
||||
{ 2016u,12u, 31u, 0u, 0u, 0u, 1483142400u }, /* 年末 */
|
||||
{ 2020u, 6u, 15u, 6u, 30u, 45u, 1592202645u }, /* 任意中段 */
|
||||
{ 2024u, 2u, 29u, 12u, 34u, 56u, 1709210096u }, /* 近一个闰日 */
|
||||
{ 2025u, 2u, 28u, 0u, 0u, 1u, 1740700801u }, /* 平年 2/28 */
|
||||
{ 2026u,10u, 25u, 8u, 0u, 0u, 1792915200u }, /* 模拟器固定帧那一刻 */
|
||||
{ 2037u,12u, 31u, 23u, 59u, 59u, 2145916799u }, /* 上界前一天 */
|
||||
{ 2038u, 1u, 19u, 3u, 14u, 7u, 2147483647u } /* uint32 秒域上界 = 0x7FFFFFFF */
|
||||
};
|
||||
|
||||
/*
|
||||
* TC-UIDATA-022 时钟换算与"无效值拒收"
|
||||
* a) 12 条锚点正向换算(期望值来自独立格里历实现,不是本函数的自我复述)
|
||||
* b) 反函数逐条回原文(互逆性:屏上显示的时间 = 存进去的时间,不多不少一小时)
|
||||
* c) **时区反证**:2026-10-25 08:00:00 必须是 1792915200,而不是它 +28800;
|
||||
* 本工程墙钟 epoch 域就是本地时间,谁在这里补了 8 小时,模拟器照样自洽、
|
||||
* 只有真机上"屏上输 08:00、存进去 16:00"(App/task/ui_task.c 的 SOE 时标注记
|
||||
* 是同一条纪律的另一处防线)
|
||||
* d) 逐月天数(含闰年 2 月 29 / 平年 2 月 28 / 出域月返 0)
|
||||
* e) 非法组合逐条拒(2 月 30、非闰 2 月 29、月 0/13、日 0/32、时 24、分秒 60、
|
||||
* 年 1999/2039),且 -2 与"越能力上界"的 -3 可分
|
||||
* f) 边界外秒域反算(epoch 0 与 0x80000000 都拒;0 是记录侧占位起点,绝不能
|
||||
* 被反算成 1970 显示到屏上)
|
||||
*/
|
||||
static void tc_clock_convert(void)
|
||||
{
|
||||
ui_data_clock_t t;
|
||||
uint32_t e;
|
||||
uint32_t i;
|
||||
int rc;
|
||||
int ok_fwd = 1;
|
||||
int ok_rev = 1;
|
||||
int n = (int)(sizeof(k_clock_cases) / sizeof(k_clock_cases[0]));
|
||||
|
||||
for (i = 0u; i < (uint32_t)n; i++) {
|
||||
(void)memset(&t, 0, sizeof(t));
|
||||
t.year = k_clock_cases[i].y;
|
||||
t.month = k_clock_cases[i].mo;
|
||||
t.day = k_clock_cases[i].d;
|
||||
t.hour = k_clock_cases[i].h;
|
||||
t.minute = k_clock_cases[i].mi;
|
||||
t.second = k_clock_cases[i].s;
|
||||
e = 0u;
|
||||
rc = ui_data_clock_to_epoch(&t, &e);
|
||||
if ((rc != 0) || (e != k_clock_cases[i].epoch)) {
|
||||
ok_fwd = 0;
|
||||
printf(" case %u: rc=%d got=%lu want=%lu\n", (unsigned)i, rc,
|
||||
(unsigned long)e, (unsigned long)k_clock_cases[i].epoch);
|
||||
}
|
||||
(void)memset(&t, 0xFF, sizeof(t));
|
||||
rc = ui_data_clock_from_epoch(k_clock_cases[i].epoch, &t);
|
||||
if ((rc != 0) || (t.year != k_clock_cases[i].y) || (t.month != k_clock_cases[i].mo) ||
|
||||
(t.day != k_clock_cases[i].d) || (t.hour != k_clock_cases[i].h) ||
|
||||
(t.minute != k_clock_cases[i].mi) || (t.second != k_clock_cases[i].s)) {
|
||||
ok_rev = 0;
|
||||
printf(" rev case %u: rc=%d got=%u-%u-%u %u:%u:%u\n", (unsigned)i, rc,
|
||||
(unsigned)t.year, (unsigned)t.month, (unsigned)t.day,
|
||||
(unsigned)t.hour, (unsigned)t.minute, (unsigned)t.second);
|
||||
}
|
||||
}
|
||||
CHECK(ok_fwd, "TC-UIDATA-022a fields to epoch over 12 anchors");
|
||||
CHECK(ok_rev, "TC-UIDATA-022b epoch to fields is the exact inverse");
|
||||
|
||||
/* 时区反证:本函数不得含任何时区偏移 */
|
||||
(void)memset(&t, 0, sizeof(t));
|
||||
t.year = 2026u; t.month = 10u; t.day = 25u; t.hour = 8u; t.minute = 0u; t.second = 0u;
|
||||
e = 0u;
|
||||
(void)ui_data_clock_to_epoch(&t, &e);
|
||||
CHECK(e == 1792915200u, "TC-UIDATA-022c no timezone shift baked in (local epoch domain)");
|
||||
CHECK(e != (1792915200u + 28800u), "TC-UIDATA-022c2 must not be plus 8 hours");
|
||||
|
||||
/* 逐月天数(闰/平两年 + 出域月) */
|
||||
{
|
||||
static const uint8_t k_leap[12] = { 31u,29u,31u,30u,31u,30u,31u,31u,30u,31u,30u,31u };
|
||||
static const uint8_t k_flat[12] = { 31u,28u,31u,30u,31u,30u,31u,31u,30u,31u,30u,31u };
|
||||
int ok_dim = 1;
|
||||
for (i = 1u; i <= 12u; i++) {
|
||||
if (ui_data_clock_days_in_month(2000u, (uint8_t)i) != k_leap[i - 1u]) ok_dim = 0;
|
||||
if (ui_data_clock_days_in_month(2025u, (uint8_t)i) != k_flat[i - 1u]) ok_dim = 0;
|
||||
}
|
||||
CHECK(ok_dim, "TC-UIDATA-022d days in month (leap 2000 / flat 2025)");
|
||||
CHECK(ui_data_clock_days_in_month(2000u, 0u) == 0u, "TC-UIDATA-022d2 month 0 means invalid");
|
||||
CHECK(ui_data_clock_days_in_month(2000u, 13u) == 0u, "TC-UIDATA-022d3 month 13 means invalid");
|
||||
}
|
||||
|
||||
/* 非法组合逐条:一律 -2(输入错),不是 -3(能力边界) */
|
||||
{
|
||||
static const clock_case_t k_bad[] = {
|
||||
{ 2025u, 2u, 29u, 0u, 0u, 0u, 0u }, /* 非闰年 2/29 */
|
||||
{ 2026u, 2u, 30u, 0u, 0u, 0u, 0u }, /* 2 月 30 日 */
|
||||
{ 2026u, 4u, 31u, 0u, 0u, 0u, 0u }, /* 4 月无 31 日 */
|
||||
{ 2026u, 0u, 1u, 0u, 0u, 0u, 0u }, /* 月 0 */
|
||||
{ 2026u,13u, 1u, 0u, 0u, 0u, 0u }, /* 月 13 */
|
||||
{ 2026u,10u, 0u, 0u, 0u, 0u, 0u }, /* 日 0 */
|
||||
{ 2026u,10u, 32u, 0u, 0u, 0u, 0u }, /* 日 32 */
|
||||
{ 2026u,10u, 25u,24u, 0u, 0u, 0u }, /* 时 24 */
|
||||
{ 2026u,10u, 25u, 0u,60u, 0u, 0u }, /* 分 60 */
|
||||
{ 2026u,10u, 25u, 0u, 0u,60u, 0u }, /* 秒 60 */
|
||||
{ 1999u, 1u, 1u, 0u, 0u, 0u, 0u }, /* 年 < RTC 年域 */
|
||||
{ 2039u, 1u, 1u, 0u, 0u, 0u, 0u } /* 年 > uint32 秒域 */
|
||||
};
|
||||
int ok_bad = 1;
|
||||
for (i = 0u; i < (uint32_t)(sizeof(k_bad) / sizeof(k_bad[0])); i++) {
|
||||
/* 逐字段搬(两种结构体尺寸不同,靠强转按字节拷是给自己埋雷) */
|
||||
t.year = k_bad[i].y;
|
||||
t.month = k_bad[i].mo;
|
||||
t.day = k_bad[i].d;
|
||||
t.hour = k_bad[i].h;
|
||||
t.minute = k_bad[i].mi;
|
||||
t.second = k_bad[i].s;
|
||||
rc = ui_data_clock_to_epoch(&t, &e);
|
||||
if (rc != -2) {
|
||||
ok_bad = 0;
|
||||
printf(" bad case %u: rc=%d (want -2)\n", (unsigned)i, rc);
|
||||
}
|
||||
}
|
||||
CHECK(ok_bad, "TC-UIDATA-022e invalid field combos rejected with -2");
|
||||
CHECK(ui_data_clock_to_epoch(NULL, &e) == -1, "TC-UIDATA-022e2 NULL input is -1");
|
||||
CHECK(ui_data_clock_to_epoch(&t, NULL) == -1, "TC-UIDATA-022e3 NULL output is -1");
|
||||
}
|
||||
|
||||
/* 反算越界:0 是"墙钟无效"的占位起点,绝不能显示成 1970-01-01 */
|
||||
CHECK(ui_data_clock_from_epoch(0u, &t) == -2, "TC-UIDATA-022f epoch 0 rejected (never show 1970)");
|
||||
CHECK(ui_data_clock_from_epoch(946684799u, &t) == -2, "TC-UIDATA-022f2 epoch below 2000 rejected");
|
||||
CHECK(ui_data_clock_from_epoch(0x80000000u, &t) == -2, "TC-UIDATA-022f3 epoch above uint32 domain");
|
||||
CHECK(ui_data_clock_from_epoch(0u, NULL) == -1, "TC-UIDATA-022f4 NULL output is -1");
|
||||
}
|
||||
|
||||
/*
|
||||
* TC-UIDATA-023 时标串 parse / format
|
||||
* 快照 rt.clock 的串型(19 字符)是页面预填的唯一来源;无效时钟时它是空串,
|
||||
* 记录侧的占位串 "1970-01-01 08:00:00" 同样必须解析失败——否则页面会把
|
||||
* 占位串当真实时间预填,用户照抄保存就等于把 1970 写进 RTC。
|
||||
*/
|
||||
static void tc_clock_string(void)
|
||||
{
|
||||
ui_data_clock_t t;
|
||||
char buf[24];
|
||||
uint32_t e;
|
||||
|
||||
(void)memset(&t, 0, sizeof(t)); /* 解析失败按契约不写输出,先给个确定的初值 */
|
||||
e = 0u;
|
||||
|
||||
CHECK(ui_data_clock_parse("2026-10-25 08:00:00", &t) == 0, "TC-UIDATA-023a parse well-formed ts");
|
||||
if (t.year == 2026u && t.month == 10u && t.day == 25u && t.hour == 8u) {
|
||||
(void)ui_data_clock_to_epoch(&t, &e);
|
||||
CHECK(e == 1792915200u, "TC-UIDATA-023b parsed ts maps to same epoch as fields");
|
||||
} else {
|
||||
CHECK(0, "TC-UIDATA-023b parsed fields wrong");
|
||||
}
|
||||
|
||||
CHECK(ui_data_clock_parse("", &t) == -2, "TC-UIDATA-023c empty ts (clock invalid) is -2");
|
||||
CHECK(ui_data_clock_parse("1970-01-01 08:00:00", &t) == -2,
|
||||
"TC-UIDATA-023d record placeholder ts must not parse (no 1970 on screen)");
|
||||
CHECK(ui_data_clock_parse("2026-10-25T08:00:00", &t) == -2, "TC-UIDATA-023e wrong separator");
|
||||
CHECK(ui_data_clock_parse("2026-10-25 08:00:0", &t) == -2, "TC-UIDATA-023f short ts");
|
||||
CHECK(ui_data_clock_parse("2026-10-25 08:00:001", &t) == -2, "TC-UIDATA-023g trailing junk");
|
||||
CHECK(ui_data_clock_parse("2O26-10-25 08:00:00", &t) == -2, "TC-UIDATA-023h non digit year");
|
||||
CHECK(ui_data_clock_parse("2026-02-30 00:00:00", &t) == -2, "TC-UIDATA-023i shaped but invalid day");
|
||||
CHECK(ui_data_clock_parse("20260101 00:00:00", &t) == -2, "TC-UIDATA-023j missing dashes");
|
||||
CHECK(ui_data_clock_parse(NULL, &t) == -1, "TC-UIDATA-023k NULL ts is -1");
|
||||
CHECK(ui_data_clock_parse("2026-10-25 08:00:00", NULL) == -1, "TC-UIDATA-023l NULL out is -1");
|
||||
|
||||
(void)memset(buf, 0x7A, sizeof(buf));
|
||||
CHECK(ui_data_clock_format(&t, buf, sizeof(buf)) == 0, "TC-UIDATA-023m format ok into 24B buffer");
|
||||
CHECK(strcmp(buf, "2026-10-25 08:00:00") == 0, "TC-UIDATA-023n formatted string round-trips");
|
||||
(void)memset(buf, 0x7A, sizeof(buf));
|
||||
CHECK(ui_data_clock_format(&t, buf, 19u) == -1, "TC-UIDATA-023o capacity 19 refused (need 20)");
|
||||
CHECK(buf[0] == (char)0x7A, "TC-UIDATA-023p refused format writes nothing");
|
||||
CHECK(ui_data_clock_format(NULL, buf, sizeof(buf)) == -1, "TC-UIDATA-023q NULL in is -1");
|
||||
}
|
||||
|
||||
/*
|
||||
* TC-UIDATA-024 跨层编号顺序契约(Lead 裁定:UI 侧编号逐字等于 syslog_id_t 枚举序,
|
||||
* 只准尾部追加)+ 清除状态码逐值相等 + 墙钟字段容量与串型一致。
|
||||
* 适配层始终显式映射(从不强转),这条断言钉的是"两套编号今天确实相等"这件事
|
||||
* 本身——枚举中间插值会安静地把事故读成告警,只有在这里才会响。
|
||||
*/
|
||||
static void tc_record_codes(void)
|
||||
{
|
||||
CHECK(UI_W_REC_LOG_RUN == (uint8_t)SYSLOG_RUN, "TC-UIDATA-024a RUN order");
|
||||
CHECK(UI_W_REC_LOG_SELFTEST == (uint8_t)SYSLOG_SELFTEST, "TC-UIDATA-024b SELFTEST order");
|
||||
CHECK(UI_W_REC_LOG_ACCIDENT == (uint8_t)SYSLOG_ACCIDENT, "TC-UIDATA-024c ACCIDENT order");
|
||||
CHECK(UI_W_REC_LOG_ALARM == (uint8_t)SYSLOG_ALARM, "TC-UIDATA-024d ALARM order");
|
||||
CHECK(UI_W_REC_LOG_START == (uint8_t)SYSLOG_START, "TC-UIDATA-024e START order");
|
||||
CHECK(UI_W_REC_LOG_OPER == (uint8_t)SYSLOG_OPER, "TC-UIDATA-024f OPER order");
|
||||
CHECK(UI_W_REC_LOG_YK == (uint8_t)SYSLOG_YK, "TC-UIDATA-024g YK order");
|
||||
CHECK(UI_W_REC_LOG_N == (uint8_t)SYSLOG_COUNT, "TC-UIDATA-024h count matches sentinel (append only below)");
|
||||
CHECK(UI_DATA_CLOCK_TS_LEN == SYSLOG_TS_LEN, "TC-UIDATA-024i ts field length same contract on both sides");
|
||||
CHECK(UI_W_REC_TS_LEN == (SYSLOG_TS_LEN + 1u), "TC-UIDATA-024j UI ts buffer covers SYSLOG_TS_LEN + NUL");
|
||||
CHECK(UI_W_REC_DESC_LEN == (SYSLOG_DESC_MAX + 1u), "TC-UIDATA-024k UI desc buffer covers max + NUL");
|
||||
CHECK(UI_DATA_CLEAR_ST_NONE == SYSLOG_CLEAR_NONE, "TC-UIDATA-024l clear NONE value");
|
||||
CHECK(UI_DATA_CLEAR_ST_OK == SYSLOG_CLEAR_OK, "TC-UIDATA-024m clear OK value");
|
||||
CHECK(UI_DATA_CLEAR_ST_WAIT == SYSLOG_CLEAR_WAIT, "TC-UIDATA-024n clear WAIT value");
|
||||
CHECK(UI_DATA_CLEAR_ST_FAIL == SYSLOG_CLEAR_FAIL, "TC-UIDATA-024o clear FAIL value");
|
||||
/* 操作记录描述上限:本地/远端两条留痕串都必须装得下,超了会被投递侧静默截断 */
|
||||
CHECK(strlen("force reset by panel") <= (size_t)SYSLOG_DESC_MAX,
|
||||
"TC-UIDATA-024p panel reset note fits desc limit");
|
||||
CHECK(strlen("clock set by panel") <= (size_t)SYSLOG_DESC_MAX,
|
||||
"TC-UIDATA-024q panel clock note fits desc limit");
|
||||
/* 这里曾写 CHECK(strcmp("force reset by panel", "force reset") != 0, ...):
|
||||
* 两个字面量互比 = 恒真,test-cppcheck 一眼抓出(钉口径的断言永不可能失败,
|
||||
* 比根本没有断言更坏——它让人以为这条已经被管住了)。同一判据挪到
|
||||
* TC-UIDATA-026s:拿实际落进执行侧缓冲的串去比远端那条文案 */
|
||||
}
|
||||
|
||||
/*
|
||||
* TC-UIDATA-025 维护通道未注入时的防御:六个口一律安全返回、不崩、ready 返 0;
|
||||
* 残表(某成员 NULL)整表拒绝且不覆盖已生效的好表——半途接住会在"按下才崩"的位置崩。
|
||||
*/
|
||||
static void tc_maint_not_bound(void)
|
||||
{
|
||||
(void)ui_data_maint_bind(NULL);
|
||||
stub_reset_all();
|
||||
|
||||
CHECK(ui_data_maint_ready() == 0u, "TC-UIDATA-025a not bound before bind");
|
||||
CHECK(ui_data_maint_clock_set(1792915200u) == -1, "TC-UIDATA-025b clock_set not bound is -1");
|
||||
CHECK(ui_data_maint_reset_req() == -1, "TC-UIDATA-025c reset_req not bound is -1");
|
||||
CHECK(ui_data_maint_active_count() == 0u, "TC-UIDATA-025d active_count not bound is 0");
|
||||
CHECK(ui_data_maint_oper_note("x") == -1, "TC-UIDATA-025e oper_note not bound is -1");
|
||||
CHECK(ui_data_maint_clear_req(UI_W_REC_LOG_RUN) == -1, "TC-UIDATA-025f clear_req not bound is -1");
|
||||
CHECK(ui_data_maint_clear_state(UI_W_REC_LOG_RUN) == UI_DATA_CLEAR_ST_NONE,
|
||||
"TC-UIDATA-025g clear_state not bound is NONE");
|
||||
CHECK(ui_data_maint_bind(&s_maint_broken) == -1, "TC-UIDATA-025h incomplete table refused");
|
||||
CHECK(ui_data_maint_ready() == 0u, "TC-UIDATA-025i refused table left channel unbound");
|
||||
CHECK(ui_data_maint_bind(&s_maint_stub) == 0, "TC-UIDATA-025j complete table accepted");
|
||||
CHECK(ui_data_maint_ready() == 1u, "TC-UIDATA-025k bound after accept");
|
||||
CHECK(ui_data_maint_bind(&s_maint_broken) == -1, "TC-UIDATA-025l broken table refused again");
|
||||
CHECK(ui_data_maint_ready() == 1u, "TC-UIDATA-025m refusal keeps the good table active");
|
||||
}
|
||||
|
||||
/*
|
||||
* TC-UIDATA-026 注入后的转发言行(页面侧全部判据的地基):
|
||||
* - clock_set(0) 是本层保留的"未初始化"哨兵,必须挡在下发之前(桩计数为 0)
|
||||
* - reset_req **不得**自己投操作记录:谁触发谁投,这条留痕属页面;
|
||||
* 在转发层偷投 = 同一次操作两个真源,也正是"远端那条已投过"会被重复投的形态
|
||||
* - clear_state 返回值直通不改码(WAIT/FAIL 都是正常显示态)
|
||||
* - oper_note(NULL) 不投递、不崩
|
||||
*/
|
||||
static void tc_maint_forwarding(void)
|
||||
{
|
||||
(void)ui_data_maint_bind(&s_maint_stub);
|
||||
stub_reset_all();
|
||||
|
||||
CHECK(ui_data_maint_clock_set(0u) == -2, "TC-UIDATA-026a epoch 0 sentinel refused locally");
|
||||
CHECK(s_stub_clock_calls == 0, "TC-UIDATA-026b sentinel never reaches executor");
|
||||
s_stub_clock_rc = 0;
|
||||
CHECK(ui_data_maint_clock_set(1792915200u) == 0, "TC-UIDATA-026c clock_set forwarded");
|
||||
CHECK((s_stub_clock_calls == 1) && (s_stub_clock_last == 1792915200u),
|
||||
"TC-UIDATA-026d executor got the exact epoch");
|
||||
s_stub_clock_rc = -1;
|
||||
CHECK(ui_data_maint_clock_set(1792915200u) == -1, "TC-UIDATA-026e executor failure propagates");
|
||||
|
||||
CHECK(ui_data_maint_reset_req() == 0, "TC-UIDATA-026f reset accepted");
|
||||
CHECK(s_stub_reset_calls == 1, "TC-UIDATA-026g executor reset called once");
|
||||
CHECK(s_stub_note_calls == 0, "TC-UIDATA-026h forwarding layer must NOT post the oper record");
|
||||
(void)ui_data_maint_oper_note("force reset by panel");
|
||||
CHECK((s_stub_note_calls == 1) && (strcmp(s_stub_note_last, "force reset by panel") == 0),
|
||||
"TC-UIDATA-026i page-side note reaches executor verbatim");
|
||||
/* 面板这条与远端 YK11 那条(App/protocol/iec104/iec104_pts.c 投的
|
||||
* "force reset")必须是不同串,否则审计面再也分不出"主站下发的"还是
|
||||
* "现场有人按了面板"。比的是执行侧缓冲里的实际内容,不是两个常量互比 */
|
||||
CHECK(strcmp(s_stub_note_last, "force reset") != 0,
|
||||
"TC-UIDATA-026s posted note differs from the remote YK11 wording");
|
||||
CHECK(ui_data_maint_oper_note(NULL) == -1, "TC-UIDATA-026j NULL note refused");
|
||||
CHECK(s_stub_note_calls == 1, "TC-UIDATA-026k NULL note not posted");
|
||||
s_stub_reset_rc = -1;
|
||||
CHECK(ui_data_maint_reset_req() == -1, "TC-UIDATA-026l rejected reset propagates");
|
||||
|
||||
s_stub_active = 3u;
|
||||
CHECK(ui_data_maint_active_count() == 3u, "TC-UIDATA-026m active count forwarded (proof of reset)");
|
||||
|
||||
CHECK((ui_data_maint_clear_req(UI_W_REC_LOG_YK) == 0) && (s_stub_clear_last_id == UI_W_REC_LOG_YK),
|
||||
"TC-UIDATA-026n clear req id passed through unchanged");
|
||||
s_stub_clear_state_rc = UI_DATA_CLEAR_ST_WAIT;
|
||||
CHECK(ui_data_maint_clear_state(UI_W_REC_LOG_OPER) == UI_DATA_CLEAR_ST_WAIT,
|
||||
"TC-UIDATA-026o WAIT passed through (normal state, not failure)");
|
||||
s_stub_clear_state_rc = UI_DATA_CLEAR_ST_FAIL;
|
||||
CHECK(ui_data_maint_clear_state(UI_W_REC_LOG_OPER) == UI_DATA_CLEAR_ST_FAIL,
|
||||
"TC-UIDATA-026p FAIL passed through unchanged");
|
||||
CHECK(s_stub_clear_state_id == UI_W_REC_LOG_OPER, "TC-UIDATA-026q state queried the asked id");
|
||||
CHECK(s_stub_clear_state_calls == 2, "TC-UIDATA-026r state queried once per call");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
|
||||
int main(void)
|
||||
@@ -930,6 +1337,16 @@ int main(void)
|
||||
tc_param_apply_fail();
|
||||
printf("-- param frame v2 (cal_block) --\n");
|
||||
tc_param_frame_v2();
|
||||
printf("-- clock convert --\n");
|
||||
tc_clock_convert();
|
||||
printf("-- clock ts string --\n");
|
||||
tc_clock_string();
|
||||
printf("-- record id order contract --\n");
|
||||
tc_record_codes();
|
||||
printf("-- maint channel: not bound --\n");
|
||||
tc_maint_not_bound();
|
||||
printf("-- maint channel: forwarding --\n");
|
||||
tc_maint_forwarding();
|
||||
|
||||
printf("\n=== UI Data Snapshot Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
Reference in New Issue
Block a user