feat(UI/采集/104): 通讯设置页 + 开机账本 + adc_cal 可诊断化——三车道同树一次构建入库
三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572
ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec
k_mem_numbers.py
--write`),凭证与实际 .map 同源。归属如下:
· **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死
区页同骨架同纪
律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化,
新增六态含
WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位
轮询;`port_104` 由
宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改
[comm]
的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o
n 9004` → 还原 2404,全程无
FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057
钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。
· **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户
SRAM(magic +
小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥
UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**;
文案口径「自 RTC
电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同
源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open
/close 洗掉)。
运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入
**全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。
· **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()`
/ `adc_cal_note_persisted()`
(水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在
`adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。
三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色):
① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份;
② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功;
③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。
另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c
`(依赖 I2C 与 RCC 位掩码,
MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1
04-034、
TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。
门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche
ck / coding-standard /
bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11
7/117、run_par 67/67;菜单 8 状态
池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保
证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。
This commit is contained in:
@@ -36,6 +36,14 @@
|
||||
* 闭锁 / 两路齐第三路缺仍闭锁(全或无)/ 三路齐放行且 UB 仍无 VALID
|
||||
* (证明它是"三路"而不是 feed_eng 那个"8 路")/ 复位后立刻回闭锁
|
||||
* (现查、无自有状态可残留)
|
||||
* —— 以下四项是"改了没落盘"的可诊断化(道G task-43 第②项;四态**定义只在
|
||||
* App/algo/adc_cal.h 一处**,本表只描述断言、不重定义)——
|
||||
* TC-ADCCAL-019 观测件不得反过来制造故障:get_stats(NULL) 静默返回、随时可读
|
||||
* TC-ADCCAL-020 一次成功整定 → set_ok +1、水位不动 ⇒ 未落盘差额 = 1(改盘上没跟上,看得见)
|
||||
* TC-ADCCAL-021 多次整定 + 一次落盘回执 → 差额归 0(水位是**盖写**不是自增:一次 save
|
||||
* 覆盖整档,若自增会留下"假未落盘"——这条就是那个坑的回归锁)
|
||||
* TC-ADCCAL-022 被拒整定(非法通道 / 越出合法带)只加 set_reject,不污染 set_ok 与差额
|
||||
* TC-ADCCAL-023 空转回执不倒挂:水位永不越过 set_ok(uint32 相减一绕回就是巨大的假差额)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-25(道A 卡 2/卡 4)
|
||||
@@ -524,6 +532,67 @@ static void tc_018_prot_blocked(void) {
|
||||
CHECK(adc_cal_prot_blocked() == 1u, "repeat call stable (idempotent pure read)");
|
||||
}
|
||||
|
||||
/* ============ 观测计数(改了没落盘 → 可诊断,道G task-43 第②项) ============ */
|
||||
/*
|
||||
* 四态定义只在 App/algo/adc_cal.h 一处;本用例**全部按增量断言**——静态计数跨用例
|
||||
* 累计,任何"绝对值等于几"的写法都会因为前面 TC 调过多少次 adc_cal_set() 而假失败
|
||||
* (也与 vol_lock 套件同一套写法先例)。
|
||||
*/
|
||||
static void tc_019_023_persist_obs(void) {
|
||||
adc_cal_stats_t s;
|
||||
uint32_t ok0;
|
||||
uint32_t rj0;
|
||||
|
||||
printf("-- TC-ADCCAL-019..023 persist observability --\n");
|
||||
|
||||
/* TC-ADCCAL-019:观测件不得反过来制造故障 */
|
||||
adc_cal_get_stats(NULL);
|
||||
CHECK(1, "get_stats(NULL) returns silently (an observer must not create a fault)");
|
||||
|
||||
/* 先立基线:把"到目前为止的改写"记为已落盘,后面全部按增量看 */
|
||||
adc_cal_note_persisted();
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.set_ok == s.persisted_set_ok, "after a sync point water catches count (gap == 0)");
|
||||
|
||||
/* TC-ADCCAL-020:一次成功整定 → 差额 1,水位不动 */
|
||||
ok0 = s.set_ok;
|
||||
rj0 = s.set_reject;
|
||||
CHECK(adc_cal_set(ADC_SEM_UA, ADC_CAL_K_Q16_TPAFE * 2, 100L) == 0, "in-band set accepted");
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.set_ok == ok0 + 1u, "set_ok incremented by exactly one");
|
||||
CHECK(s.set_reject == rj0, "set_reject untouched by a successful set");
|
||||
CHECK(s.persisted_set_ok == ok0, "water still at the old level (nothing saved yet)");
|
||||
CHECK(s.set_ok - s.persisted_set_ok == 1u, "gap == 1: RAM changed, disk not (the diagnosable fact)");
|
||||
|
||||
/* TC-ADCCAL-021:多次整定 + 一次落盘回执 → 差额归 0(水位是盖写不是自增) */
|
||||
CHECK(adc_cal_set(ADC_SEM_UN, ADC_CAL_K_Q16_TPAFE, -50L) == 0, "second channel set accepted");
|
||||
/* 合法带是 ROM 系数的 [0.5x, 2x](含端点,TC-ADCCAL-008 已钉)——这里必须留在带内,
|
||||
* 否则第二次改写被拒,差额就不是我声称的 3 了(写用例时踩过一次,改成 2x 上界) */
|
||||
CHECK(adc_cal_set(ADC_SEM_IA, ADC_CAL_K_Q16_TPAFE * 2, 7L) == 0, "third channel set accepted (band upper end)");
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.set_ok - s.persisted_set_ok == 3u, "three pending sets before the single save");
|
||||
adc_cal_note_persisted(); /* 一次 save 覆盖整档:等价 run_par_store_save() 成功路径 */
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.set_ok - s.persisted_set_ok == 0u, "one save clears all three pending sets (water is a snapshot, not a tick)");
|
||||
|
||||
/* TC-ADCCAL-022:被拒的两类整定只加 set_reject,不污染差额 */
|
||||
ok0 = s.set_ok;
|
||||
rj0 = s.set_reject;
|
||||
CHECK(adc_cal_set(ADC_SEM_TEMP, ADC_CAL_K_Q16_TPAFE, 0L) == -1, "TEMP rejected");
|
||||
CHECK(adc_cal_set(ADC_SEM_UA, ADC_CAL_K_Q16_TPAFE * 100, 0L) == -1, "out-of-band k rejected (RAM untouched)");
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.set_reject == rj0 + 2u, "set_reject counts both reject reasons");
|
||||
CHECK(s.set_ok == ok0, "rejected sets never inflate set_ok");
|
||||
CHECK(s.set_ok == s.persisted_set_ok, "gap stays zero across rejected attempts (no phantom pending write)");
|
||||
|
||||
/* TC-ADCCAL-023:空转回执不倒挂 */
|
||||
adc_cal_note_persisted();
|
||||
adc_cal_note_persisted();
|
||||
adc_cal_get_stats(&s);
|
||||
CHECK(s.persisted_set_ok == s.set_ok, "idle acks keep water exactly at the level (never overrun)");
|
||||
CHECK(s.set_ok >= s.persisted_set_ok, "subtraction can never wrap (gap stays non-negative)");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
int main(void) {
|
||||
printf("=== ADC Cal Layer Tests ===\n");
|
||||
@@ -544,6 +613,7 @@ int main(void) {
|
||||
tc_012_file_corrupt();
|
||||
tc_013_section_missing();
|
||||
tc_018_prot_blocked();
|
||||
tc_019_023_persist_obs();
|
||||
|
||||
printf("\n=== ADC Cal Layer Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 模块名称:开机账本(boot_stats)主机侧单元测试
|
||||
* 模块功能:在 PC 上离线验证 App/util/boot_stats.c 的**上电次数四态判定、记录校验、
|
||||
* I2C 失败分支(读失败绝不回写 / 写失败只少一笔账)、幂等、饱和**,
|
||||
* 以及复位原因优先级链的唯一出处。
|
||||
*
|
||||
* 被测对象:真实源文件 App/util/boot_stats.c(#include 原样纳入)。桩:
|
||||
* - stub/main.h:六个 RCC_CSR 复位标志位掩码(位号照抄 CMSIS);
|
||||
* - sd2506.h / bsp_err.h / boot_stats.h / app_main.h 用**真实头**;
|
||||
* - sd2506_read_sram / sd2506_write_sram / sd2506_read_ctr1 由本文件实现
|
||||
* 为可注入错误的假件(含 70 B 假 SRAM,越界返 BSP_ERR_PARAM,与真驱动
|
||||
* 的契约一致);g_rcc_csr_latched 也由本文件给出。
|
||||
*
|
||||
* 为什么这些判据必须在这儿测而不在真机上测(真机只覆盖"一切正常"那一条):
|
||||
* 1. **读失败 ≠ 没有记录**:I2C 抖动一次就把计数当 0 重来,屏幕上是个很像样的小
|
||||
* 数字、日志里一个字都没有;读失败还顺手回写的话,故障就从"这次读不到"升级成
|
||||
* "永久少账"(004 用"假 SRAM 内容一字不许变"把这条钉住)。
|
||||
* 2. **首次上电 ≠ RTC 掉过电**:合并成一态,文案就把前者冒充后者(001 vs 003)。
|
||||
* 3. **只信 magic 不信校验**:坏区常是整片全 0/全 FF,但"个别字节翻转"时 magic
|
||||
* 可能仍然好、次数错 256 倍(007)。
|
||||
* 4. **饱和 vs 回绕**:停在 42 亿一眼看出到头;回绕成个位数会被读成"刚上电"(009)。
|
||||
* 5. BLF 读不到不该把好记录判坏(012)——判定顺序写反的代价是"RTC 抖一下,
|
||||
* 装置变成第一次上电"。
|
||||
*
|
||||
* 编号:TC-BOOTSTAT-001 起(小写子项 = 同一判据的另一面)。族需已登记进
|
||||
* scripts/id_registry.json;未登记时 check_ids 校验 1 会报"未登记前缀"。
|
||||
* 创建日期:2026-10-27(道G task-12)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "boot_stats.h"
|
||||
#include "sd2506.h" /* 真实头:bsp_err_t / SD2506_CTR1_BLF / 三个原型 */
|
||||
#include "app_main.h" /* extern volatile uint32_t g_rcc_csr_latched */
|
||||
|
||||
/* 被测生产源(照仓内既有套件:include_dirs 里有 App/util) */
|
||||
#include "boot_stats.c"
|
||||
|
||||
/* ---------------- g_rcc_csr_latched(真实定义在未编译的 app_main.c) ---------------- */
|
||||
volatile uint32_t g_rcc_csr_latched;
|
||||
|
||||
/* ---------------- SD2506 假件 ---------------- */
|
||||
#define FAKE_SRAM_SIZE 70u
|
||||
|
||||
static uint8_t s_sram[FAKE_SRAM_SIZE];
|
||||
static uint8_t s_ctr1_val;
|
||||
static int s_read_err; /* != BSP_OK ⇒ 注入读失败 */
|
||||
static int s_write_err; /* != BSP_OK ⇒ 注入写失败 */
|
||||
static int s_ctr1_err;
|
||||
static int s_read_calls;
|
||||
static int s_write_calls;
|
||||
static int s_ctr1_calls;
|
||||
|
||||
static void fake_reset(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0u; i < FAKE_SRAM_SIZE; i++) {
|
||||
s_sram[i] = 0xFFu; /* 芯片出厂/掉电后的常见形态:成片全 1 */
|
||||
}
|
||||
s_ctr1_val = 0u;
|
||||
s_read_err = BSP_OK;
|
||||
s_write_err = BSP_OK;
|
||||
s_ctr1_err = BSP_OK;
|
||||
s_read_calls = 0;
|
||||
s_write_calls = 0;
|
||||
s_ctr1_calls = 0;
|
||||
g_rcc_csr_latched = 0u;
|
||||
/* 被测模块的静态状态也要归零——它在真实机器上"每 boot 一次",
|
||||
* 测试里靠这条模拟"又开了一次机"(复位 SRAM 侧但不清假 SRAM = 跨掉电保持)。 */
|
||||
(void)memset(&s_bs, 0, sizeof(s_bs));
|
||||
}
|
||||
|
||||
/* 造一条合法记录(校验按生产同一算法算,别把算法抄第二份在这里) */
|
||||
static void fake_put_count(uint32_t cnt, uint8_t ver)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t sum = 0u;
|
||||
|
||||
s_sram[0] = 0xB5u;
|
||||
s_sram[1] = 0x72u;
|
||||
s_sram[2] = (uint8_t)(cnt & 0xFFu);
|
||||
s_sram[3] = (uint8_t)((cnt >> 8) & 0xFFu);
|
||||
s_sram[4] = (uint8_t)((cnt >> 16) & 0xFFu);
|
||||
s_sram[5] = (uint8_t)((cnt >> 24) & 0xFFu);
|
||||
s_sram[6] = ver;
|
||||
for (i = 0u; i < BS_REC_LEN - 1u; i++) {
|
||||
sum = (uint8_t)(sum ^ s_sram[i]);
|
||||
}
|
||||
s_sram[7] = sum;
|
||||
}
|
||||
|
||||
static uint32_t fake_get_count(void)
|
||||
{
|
||||
return ((uint32_t)s_sram[2]) | ((uint32_t)s_sram[3] << 8) |
|
||||
((uint32_t)s_sram[4] << 16) | ((uint32_t)s_sram[5] << 24);
|
||||
}
|
||||
|
||||
bsp_err_t sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
s_read_calls++;
|
||||
if (s_read_err != BSP_OK) {
|
||||
return (bsp_err_t)s_read_err;
|
||||
}
|
||||
if ((p_buf == 0) || (len == 0u) || (len > FAKE_SRAM_SIZE) ||
|
||||
((uint16_t)addr + (uint16_t)len > FAKE_SRAM_SIZE)) {
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
for (i = 0u; i < len; i++) {
|
||||
p_buf[i] = s_sram[addr + i];
|
||||
}
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
bsp_err_t sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
s_write_calls++;
|
||||
if (s_write_err != BSP_OK) {
|
||||
return (bsp_err_t)s_write_err;
|
||||
}
|
||||
if ((p_buf == 0) || (len == 0u) || (len > FAKE_SRAM_SIZE) ||
|
||||
((uint16_t)addr + (uint16_t)len > FAKE_SRAM_SIZE)) {
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
for (i = 0u; i < len; i++) {
|
||||
s_sram[addr + i] = p_buf[i];
|
||||
}
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
bsp_err_t sd2506_read_ctr1(uint8_t *p_val)
|
||||
{
|
||||
s_ctr1_calls++;
|
||||
if (s_ctr1_err != BSP_OK) {
|
||||
return (bsp_err_t)s_ctr1_err;
|
||||
}
|
||||
if (p_val == 0) {
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
*p_val = s_ctr1_val;
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/* sd2506_init 不测(属 SD2506 自己的套件),给个链接期够用的定义 */
|
||||
bsp_err_t sd2506_init(void) { return BSP_OK; }
|
||||
|
||||
/* ---------------- 断言骨架 ---------------- */
|
||||
static int s_total;
|
||||
static int s_passed;
|
||||
static int s_failed;
|
||||
|
||||
#define CHECK(cond, name) \
|
||||
do { \
|
||||
s_total++; \
|
||||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||||
else { s_failed++; printf(" [FAIL] %s\n", name); } \
|
||||
} while (0)
|
||||
|
||||
static boot_stats_t snap(void)
|
||||
{
|
||||
boot_stats_t b;
|
||||
|
||||
boot_stats_get(&b);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ===================== 上电次数四态 ===================== */
|
||||
static void tc_seq_states(void)
|
||||
{
|
||||
boot_stats_t b;
|
||||
uint8_t before[BS_REC_LEN];
|
||||
|
||||
/* 001 首次启用:SRAM 成片全 1(magic 过不去)+ RTC 没报欠压 ⇒ FRESH,不是 RESTARTED */
|
||||
fake_reset();
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.ready == 1u) && (b.seq_state == BOOT_STATS_SEQ_FRESH) && (b.boot_count == 1u)
|
||||
&& (b.write_failed == 0u) && (s_write_calls == 1) && (fake_get_count() == 1u),
|
||||
"TC-BOOTSTAT-001 blank SRAM without BLF starts the ledger at 1 and marks it FRESH, not RESTARTED");
|
||||
|
||||
/* 002 延续:合法记录 count=7 ⇒ count=8,且回写后介质里也是 8 */
|
||||
fake_reset();
|
||||
fake_put_count(7u, BS_REC_VER);
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_CONTINUED) && (b.boot_count == 8u)
|
||||
&& (fake_get_count() == 8u),
|
||||
"TC-BOOTSTAT-002 a valid record continues the count and persists the incremented value");
|
||||
|
||||
/* 002b 跨掉电保持的本体:假 SRAM 不清、只重开被测模块(等价于"又上电一次") */
|
||||
(void)memset(&s_bs, 0, sizeof(s_bs));
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_CONTINUED) && (b.boot_count == 9u),
|
||||
"TC-BOOTSTAT-002b re-initialising over an intact medium keeps counting up rather than restarting at 1");
|
||||
|
||||
/* 003 电池域掉过电:记录无效 + BLF 置位 ⇒ RESTARTED(与 001 是两件事) */
|
||||
fake_reset();
|
||||
s_ctr1_val = SD2506_CTR1_BLF;
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_RESTARTED) && (b.boot_count == 1u) && (b.rtc_blf == 1u),
|
||||
"TC-BOOTSTAT-003 an invalid record with the battery-low flag set is reported as RESTARTED, never as first power-on");
|
||||
|
||||
/* 004 I2C 读失败 ⇒ 不可信,且**绝不回写**(把可能完好的记录覆盖掉是不可接受的) */
|
||||
fake_reset();
|
||||
fake_put_count(41u, BS_REC_VER);
|
||||
(void)memcpy(before, s_sram, BS_REC_LEN);
|
||||
s_read_err = BSP_ERR_TIMEOUT;
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_UNKNOWN) && (b.boot_count == 0u)
|
||||
&& (s_write_calls == 0) && (memcmp(s_sram, before, BS_REC_LEN) == 0),
|
||||
"TC-BOOTSTAT-004 an I2C read failure marks the ledger untrusted and writes nothing back, leaving the stored record byte-identical");
|
||||
|
||||
/* 004b 读不到时页面要能分辨"不可信"与"零次":同一张表里 ready=1 且 seq=UNKNOWN */
|
||||
CHECK((b.ready == 1u) && (b.seq_state == BOOT_STATS_SEQ_UNKNOWN),
|
||||
"TC-BOOTSTAT-004b the untrusted state is distinguishable from both a real count and an uninitialised table");
|
||||
|
||||
/* 005 写失败 ⇒ 本次仍报数,但打上"少一笔账"的记号(下次开机读旧值,只会少不会重) */
|
||||
fake_reset();
|
||||
s_write_err = BSP_ERR_IO;
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.boot_count == 1u) && (b.write_failed == 1u) && (s_write_calls == 1),
|
||||
"TC-BOOTSTAT-005 a failed write-back still reports this boot but flags it, so the ledger can only lose one count");
|
||||
|
||||
/* 006 幂等:bring-up 重试不得重复计数(真实机器上 sd2506 之后可能重进一次) */
|
||||
fake_reset();
|
||||
boot_stats_init();
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.boot_count == 1u) && (s_write_calls == 1),
|
||||
"TC-BOOTSTAT-006 a second init in the same boot neither counts twice nor writes twice");
|
||||
|
||||
/* 007 校验和不符(magic 与版本都好,中段漂了一位)⇒ 不当真账读 */
|
||||
fake_reset();
|
||||
fake_put_count(9u, BS_REC_VER);
|
||||
s_sram[4] = (uint8_t)(s_sram[4] ^ 0x01u); /* 高位字节翻转:256 倍的错账 */
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_FRESH) && (b.boot_count == 1u),
|
||||
"TC-BOOTSTAT-007 a single flipped byte that leaves the magic intact is caught by the checksum instead of reporting a count off by 256");
|
||||
|
||||
/* 008 格式版本不符(将来改了布局)⇒ 整条作废,且回写时落回当前版本 */
|
||||
fake_reset();
|
||||
fake_put_count(1234u, 2u);
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_FRESH) && (b.boot_count == 1u)
|
||||
&& (s_sram[6] == BS_REC_VER) && (s_sram[7] == s_rec_sum(s_sram)),
|
||||
"TC-BOOTSTAT-008 a record from a future layout is rejected outright and rewritten under the current version with a fresh checksum");
|
||||
|
||||
/* 009 饱和:到顶不再自增也不再写(回绕成个位数会被读成"刚上电") */
|
||||
fake_reset();
|
||||
fake_put_count(0xFFFFFFF0u, BS_REC_VER);
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.boot_count == 0xFFFFFFF0u) && (s_write_calls == 0) && (fake_get_count() == 0xFFFFFFF0u),
|
||||
"TC-BOOTSTAT-009 the counter saturates at the ceiling and stops writing instead of wrapping to a small number");
|
||||
|
||||
/* 010 BLF 读不到但记录有效 ⇒ 仍按延续判(读不到佐证不该把好账判坏) */
|
||||
fake_reset();
|
||||
fake_put_count(20u, BS_REC_VER);
|
||||
s_ctr1_err = BSP_ERR_IO;
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_CONTINUED) && (b.boot_count == 21u) && (b.rtc_blf == 0u),
|
||||
"TC-BOOTSTAT-010 an unreadable battery flag never invalidates an otherwise sound record");
|
||||
|
||||
/* 011 出厂全 0 的 SRAM(另一形态:不是全 FF)⇒ FRESH 且第一格就是 1,不报 0 次 */
|
||||
fake_reset();
|
||||
(void)memset(s_sram, 0, sizeof(s_sram));
|
||||
boot_stats_init();
|
||||
b = snap();
|
||||
CHECK((b.seq_state == BOOT_STATS_SEQ_FRESH) && (b.boot_count == 1u),
|
||||
"TC-BOOTSTAT-011 an all-zero SRAM (the other erase pattern) starts the ledger at one rather than showing zero boots");
|
||||
}
|
||||
|
||||
/* ===================== 复位原因链 ===================== */
|
||||
static uint8_t is_ascii(const char *p)
|
||||
{
|
||||
while (*p != '\0') {
|
||||
if (((unsigned char)*p) > 127u) {
|
||||
return 0u;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
static void tc_cause_chain(void)
|
||||
{
|
||||
/* 020 优先级唯一出处:IWDG 压过软件复位(崩溃钩子常以 NVIC_SystemReset 收尾,
|
||||
* 两个标志同时置位时报看门狗才是我们要看的自动恢复事件) */
|
||||
CHECK(boot_stats_cause_from_csr(RCC_CSR_IWDGRSTF | RCC_CSR_SFTRSTF) == BOOT_STATS_CAUSE_IWDG,
|
||||
"TC-BOOTSTAT-020 the independent watchdog outranks a software reset when both flags are set");
|
||||
|
||||
/* 020b WWDG 压过软件复位;单独的软件复位仍归软件 */
|
||||
CHECK((boot_stats_cause_from_csr(RCC_CSR_WWDGRSTF | RCC_CSR_SFTRSTF) == BOOT_STATS_CAUSE_WWDG)
|
||||
&& (boot_stats_cause_from_csr(RCC_CSR_SFTRSTF) == BOOT_STATS_CAUSE_SFT),
|
||||
"TC-BOOTSTAT-020b the window watchdog likewise outranks a software reset while a lone software reset stays itself");
|
||||
|
||||
/* 020c POR 压过 PIN、PIN 压过 BOR;全零 ⇒ UNKNOWN */
|
||||
CHECK((boot_stats_cause_from_csr(RCC_CSR_PORRSTF | RCC_CSR_PINRSTF) == BOOT_STATS_CAUSE_POR)
|
||||
&& (boot_stats_cause_from_csr(RCC_CSR_PINRSTF | RCC_CSR_BORRSTF) == BOOT_STATS_CAUSE_PIN)
|
||||
&& (boot_stats_cause_from_csr(RCC_CSR_BORRSTF) == BOOT_STATS_CAUSE_BOR)
|
||||
&& (boot_stats_cause_from_csr(0u) == BOOT_STATS_CAUSE_UNKNOWN),
|
||||
"TC-BOOTSTAT-020c power-on beats pin which beats brown-out, and no flag at all stays UNKNOWN rather than guessing");
|
||||
|
||||
/* 020d 每一格编码都有可读串、全是纯 ASCII(无 BOM 文件里字符串必须 ASCII),
|
||||
* 且未知编码回落 unknown 而不是断言——这条链跑在开机极早期,不能挡启动 */
|
||||
CHECK((boot_stats_cause_str(BOOT_STATS_CAUSE_UNKNOWN) != 0)
|
||||
&& (boot_stats_cause_str(BOOT_STATS_CAUSE_BOR) != 0)
|
||||
&& (strcmp(boot_stats_cause_str(BOOT_STATS_CAUSE_IWDG), "IWDG (independent watchdog)") == 0)
|
||||
&& (strcmp(boot_stats_cause_str(200u), "unknown") == 0)
|
||||
&& is_ascii(boot_stats_cause_str(BOOT_STATS_CAUSE_SFT)),
|
||||
"TC-BOOTSTAT-020d every code maps to a pure-ASCII description and an out-of-range code falls back to unknown instead of blocking startup");
|
||||
|
||||
/* 020e 快照:开机那次读的就是锁存影子里的复位原因(与串口那条同源同序) */
|
||||
fake_reset();
|
||||
g_rcc_csr_latched = RCC_CSR_IWDGRSTF;
|
||||
boot_stats_init();
|
||||
CHECK(snap().rst_cause == BOOT_STATS_CAUSE_IWDG,
|
||||
"TC-BOOTSTAT-020e the boot snapshot exposes the latched RCC_CSR cause so the screen and the serial line can never disagree");
|
||||
}
|
||||
|
||||
/* ===================== 读口契约 ===================== */
|
||||
static void tc_get_contract(void)
|
||||
{
|
||||
/* 030 未 init:整表零、ready=0(页面该显示占位而不是"0 次") */
|
||||
fake_reset();
|
||||
CHECK((snap().ready == 0u) && (snap().boot_count == 0u),
|
||||
"TC-BOOTSTAT-030 before init the table reads zero with ready clear so a page can show a placeholder instead of zero boots");
|
||||
|
||||
/* 030b NULL 无害(观测口在任何上下文都不该因为调用者手滑而炸) */
|
||||
boot_stats_get(0);
|
||||
CHECK(1, "TC-BOOTSTAT-030b a null output pointer is a harmless no-op");
|
||||
|
||||
/* 030c 读口不碰总线:init 之后再读 100 次,I2C 调用数纹丝不动
|
||||
* ——「任意上下文可读」不是口头承诺,是这条计数断言 */
|
||||
fake_reset();
|
||||
boot_stats_init();
|
||||
s_read_calls = 0;
|
||||
s_write_calls = 0;
|
||||
s_ctr1_calls = 0;
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 100; i++) {
|
||||
(void)snap();
|
||||
}
|
||||
}
|
||||
CHECK((s_read_calls == 0) && (s_write_calls == 0) && (s_ctr1_calls == 0),
|
||||
"TC-BOOTSTAT-030c the getter touches neither I2C nor the clock, so reading the ledger cannot become part of a fault");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("=== Boot Stats Tests ===\n");
|
||||
printf("-- TC-BOOTSTAT-001..011 power-on ledger (four states, checksum, I2C failures, idempotence, saturation) --\n");
|
||||
tc_seq_states();
|
||||
printf("-- TC-BOOTSTAT-020..020e reset cause chain (single source of the priority order) --\n");
|
||||
tc_cause_chain();
|
||||
printf("-- TC-BOOTSTAT-030..030c read-only contract --\n");
|
||||
tc_get_contract();
|
||||
/* 分子必须是"真过了几条":写成 s_total/s_total 会出现
|
||||
* 「21/21 PASSED (failed=1)」这种自相矛盾的汇总行——人只看前半就放行了。 */
|
||||
printf("\n=== Boot Stats Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""开机账本(boot_stats)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/boot_stats/boot_stats_test.c(以 #include 纳入**生产源**
|
||||
App/util/boot_stats.c),在 PC 上离线验证上电次数的**四态判定**(不可信 / 首次启用 /
|
||||
延续 / 电池域掉过电)、记录校验与格式版本、I2C 读失败时**绝不回写**、写失败时
|
||||
"少一笔账"的记号、幂等、饱和,以及复位原因优先级链的唯一出处。
|
||||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||||
|
||||
为什么要主机侧单测(而不是只靠真机):
|
||||
这一格数字的唯一用途是告诉人"这台装置上了多少次电",而它错的时候**永远不像错**:
|
||||
1. I2C 读失败若当成"没有记录",计数会在某次通信抖动后悄悄从 1 重来——屏幕上
|
||||
是个很像样的小数字,没有任何一行日志会说它假;
|
||||
2. 读失败若还顺手回写,就把可能完好的旧记录**覆盖掉**,故障从"读不到"升级成
|
||||
"永久少账";
|
||||
3. "第一次上电"与"RTC 掉过电"合并成一个状态,就等于拿前者冒充后者(文案会写错
|
||||
整整一格的口径);
|
||||
4. 只信 magic 不信校验:电池坏区常是整片全 0 / 全 FF(magic 那关自然过不去),
|
||||
但更常见的"个别字节翻转"里 magic 可能仍然好,次数会错 256 倍。
|
||||
这些都要能把 SRAM 内容、BLF、I2C 错误码逐个构造出来才测得到;真机上跑一次
|
||||
只覆盖"一切正常"那一条。桩边界见下面的注释;上板另有一组判据(断电 ≥5 min 再
|
||||
上电 +1、换电池后落 RESTARTED),见 task-12 卡片,主机测不出来。
|
||||
|
||||
桩的边界:
|
||||
- stub/main.h:只给 RCC_CSR 六个复位标志位掩码(位号照抄 CMSIS,见该文件注释);
|
||||
真实 main.h 拉整棵 HAL 头树,PC 上编不动。
|
||||
- sd2506.h / bsp_err.h / boot_stats.h / app_main.h 一律用**真实头**(它们不依赖
|
||||
HAL 类型);被测源用到的三个 SD2506 函数
|
||||
sd2506_read_sram / sd2506_write_sram / sd2506_read_ctr1 由本测试单元实现为
|
||||
可注入失败的假件(含一片 70 B 假 SRAM,地址越界按真驱动语义返 BSP_ERR_PARAM)。
|
||||
- g_rcc_csr_latched 的真实定义在 App/app/app_main.c(本套件不编译它),
|
||||
由本测试单元给出。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/boot_stats/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
|
||||
设计依据:App/util/boot_stats.h、docs/任务规划/UI补齐.md §2(运行报告四个数的口径)
|
||||
创建日期:2026-10-27(道G task-12)
|
||||
"""
|
||||
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
|
||||
|
||||
# 顺序敏感:stub 必须排在最前,才能用桩 main.h 覆盖真实 main.h。
|
||||
BUILD = TestBuild(
|
||||
name="Boot Stats Tests",
|
||||
src=os.path.join(HERE, "boot_stats_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "util"),
|
||||
os.path.join(ROOT, "App", "app"),
|
||||
os.path.join(ROOT, "Drivers", "BSP"),
|
||||
os.path.join(ROOT, "Drivers", "BSP", "SD2506"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Boot Stats Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 模块名称:main.h 桩(boot_stats 主机单测专用)
|
||||
* 模块功能:只提供 App/util/boot_stats.c 真正用到的 **RCC_CSR 复位标志位掩码**,
|
||||
* 让被测源在 PC 上编得动(真实 main.h 会拉整棵 HAL/CMSIS 头树)。
|
||||
* 创建日期:2026-10-27(道G task-12)
|
||||
*
|
||||
* ⚠ 位号与宏名必须与 CMSIS 一致(本仓真值取自
|
||||
* Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f407xx.h 的 RCC_CSR_xxx_Pos):
|
||||
* BOR=25 / PIN=26 / POR=27 / SFT=28 / IWDG=29 / WWDG=30。
|
||||
* 改了被测代码而这里缺宏 → 编译直接失败,不会出现"桩悄悄放过"的假绿;
|
||||
* 改了位号(不会发生,芯片固定)则断言会红——优先级链是本项目唯一出处,
|
||||
* 见 App/util/boot_stats.h 的 boot_stats_cause_from_csr 函数头。
|
||||
*/
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define RCC_CSR_BORRSTF ((uint32_t)0x02000000u) /* bit25 掉电复位 */
|
||||
#define RCC_CSR_PINRSTF ((uint32_t)0x04000000u) /* bit26 引脚复位 */
|
||||
#define RCC_CSR_PORRSTF ((uint32_t)0x08000000u) /* bit27 上电复位 */
|
||||
#define RCC_CSR_SFTRSTF ((uint32_t)0x10000000u) /* bit28 软件复位 */
|
||||
#define RCC_CSR_IWDGRSTF ((uint32_t)0x20000000u) /* bit29 独立看门狗 */
|
||||
#define RCC_CSR_WWDGRSTF ((uint32_t)0x40000000u) /* bit30 窗口看门狗 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MAIN_H */
|
||||
@@ -38,6 +38,8 @@
|
||||
* setter 守卫(0 拒 / 1023 拒 / 65535 收 / 带内提交)
|
||||
* TC-IEC104-033 ca 端到端:ca=comm 值时 C_RD 正答 COT5 且 TX 头回显同值;
|
||||
* 旧写死值 ca=1 变错地址 → COT13+EVI(证明过滤真源已迁移)
|
||||
* TC-IEC104-034 [comm] 落盘脏位:置脏/清脏/空转/带外拒不置脏(道G task-9,
|
||||
* 通讯设置页保存行 WAIT/OK 的判据源)
|
||||
* TC-IEC104-014 定值两段写(真实 prot_set):越界 PN;执行后 get_f + selfcheck
|
||||
* TC-IEC104-015 C_RD:YC 命中 / 定值命中 / 未知 IOA COT14+EVI
|
||||
* TC-IEC104-016 C_CS:clock_sync 收正确 epoch;IV → PN;clock_sync 失败 → PN
|
||||
@@ -1553,6 +1555,26 @@ static void tc_comm(void)
|
||||
"TC-IEC104-033 the retired hardwired ca=1 now answers COT13+EVI");
|
||||
(void)iec104_comm_ca_set(IEC104_COMM_DEF_CA); /* 收尾:复位出厂 ca */
|
||||
(void)iec104_comm_port_set(IEC104_COMM_DEF_PORT);
|
||||
|
||||
/* ===== TC-IEC104-034 [comm] 落盘脏位与 store_save(道G task-9,页面链前置)=====
|
||||
* 契约 = iec104_comm.h 的两个新出口:setter 成功置脏 / store_save 成功清脏 /
|
||||
* 无脏空转返 0 / 带外拒不置脏。defaultTask 每轮无条件调 store_save(挂载点
|
||||
* 在 App/task/default_task.c,run_par_store_save 同点位),屏上 WAIT/OK 全看
|
||||
* 这枚脏位——脏位错 = 通讯页保存行说谎,这是本条用例钉的东西。
|
||||
* 编号 034 = 向 Lead 申请发号(cursor_owner_only),cursor 现 033。 */
|
||||
printf("[TC-IEC104-034] comm dirty bit drives the store_save cycle\n");
|
||||
CHECK(iec104_comm_dirty_peek() == 1u,
|
||||
"TC-IEC104-034 successful setters leave the section dirty");
|
||||
CHECK((iec104_comm_store_save() == 0) && (iec104_comm_dirty_peek() == 0u),
|
||||
"TC-IEC104-034 store_save clears dirty only on write success");
|
||||
CHECK((strstr(comm_vfs_head(rp_s_vfs_len), "ca=1") != NULL) &&
|
||||
(strstr(comm_vfs_head(rp_s_vfs_len), "port=2404") != NULL),
|
||||
"TC-IEC104-034 the saved file carries the committed values");
|
||||
CHECK((iec104_comm_store_save() == 0) && (iec104_comm_dirty_peek() == 0u),
|
||||
"TC-IEC104-034 a save with no dirty is a silent no-op (poll cost)");
|
||||
CHECK((iec104_comm_ca_set(0u) == -1) && (iec104_comm_dirty_peek() == 0u) &&
|
||||
(iec104_comm_ca() == IEC104_COMM_DEF_CA),
|
||||
"TC-IEC104-034 out-of-band reject neither dirties nor moves the copy");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
* TC-UIDATA-026 注入后转发言行:epoch 0 哨兵不下发、reset_req 自行投操作记录=0 次
|
||||
* TC-UIDATA-027 遥测整定通道未注入防御:八个口一律安全返回、不碰出参;
|
||||
* 残表整表拒绝且不覆盖好表(与 025 同一条纪律的第五张表复刻)
|
||||
* TC-UIDATA-029 通讯参数通道六(道G task-9):未注入防御 + 注入后转发
|
||||
* (行现状逐字段 / 带外拒不置脏 / 脏位零缓存跟随 / 撤销脱离)
|
||||
* TC-UIDATA-028 遥测整定注入后转发言行:行现状逐字段原样上抛、写口的 idx 与
|
||||
* 工程量原样下传、返回码直通、pending 无状态直读(本层零缓存——
|
||||
* 页面 OK/WAIT 的唯一依据必须是执行侧此刻的脏位)
|
||||
@@ -1480,6 +1482,103 @@ static void tc_yt_forwarding(void)
|
||||
CHECK(s_yt_pending_calls == 2, "TC-UIDATA-028q unbind stops reaching executor entirely");
|
||||
}
|
||||
|
||||
/* ── 道G task-9 通道六:通讯参数注入(027/028 的第五张表复刻到第六张表)──
|
||||
* 编号 029 = 向 Lead 申请发号(cursor_owner_only),cursor 现 026。
|
||||
* 假件语义 = simulator/main.c 同形(带内写副本、带外拒、写成功置脏)。 */
|
||||
static uint32_t s_cf_val[2] = { 1u, 2404u };
|
||||
static uint32_t s_cf_lo[2] = { 1u, 1024u };
|
||||
static uint32_t s_cf_hi[2] = { 65535u, 65535u };
|
||||
static uint8_t s_cf_dirty;
|
||||
static uint8_t s_cf_last_idx = 0xFFu;
|
||||
static uint32_t s_cf_last_val;
|
||||
static int s_cf_calls;
|
||||
|
||||
static int cf_stub_row(uint8_t a_idx, ui_data_comm_row_t *a_out)
|
||||
{
|
||||
s_cf_calls++;
|
||||
if (a_idx >= UI_DATA_COMM_ROW_N) {
|
||||
return -1;
|
||||
}
|
||||
a_out->val = s_cf_val[a_idx];
|
||||
a_out->lo = s_cf_lo[a_idx];
|
||||
a_out->hi = s_cf_hi[a_idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cf_stub_set(uint8_t a_idx, uint32_t a_val)
|
||||
{
|
||||
s_cf_calls++;
|
||||
s_cf_last_idx = a_idx;
|
||||
s_cf_last_val = a_val;
|
||||
if ((a_idx >= UI_DATA_COMM_ROW_N) || (a_val < s_cf_lo[a_idx]) ||
|
||||
(a_val > s_cf_hi[a_idx])) {
|
||||
return -1;
|
||||
}
|
||||
s_cf_val[a_idx] = a_val;
|
||||
s_cf_dirty = 1u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t cf_stub_pending(void)
|
||||
{
|
||||
s_cf_calls++;
|
||||
return s_cf_dirty;
|
||||
}
|
||||
|
||||
static const ui_data_comm_ops_t s_cf_stub = {
|
||||
cf_stub_row, cf_stub_set, cf_stub_pending
|
||||
};
|
||||
|
||||
/* 残表:只缺 pending——三口里最容易被"顺手少注一件"漏掉的那个(027l 同法) */
|
||||
static const ui_data_comm_ops_t s_cf_broken = {
|
||||
cf_stub_row, cf_stub_set, NULL
|
||||
};
|
||||
|
||||
/* TC-UIDATA-029a-e 未注入防御:三个口一律安全返回、不碰出参、不假报脏 */
|
||||
static void tc_comm_ch6_not_bound(void)
|
||||
{
|
||||
ui_data_comm_row_t r;
|
||||
const uint32_t sentinel = 0xA5A5A5A5uL;
|
||||
|
||||
(void)ui_data_comm_bind(NULL);
|
||||
r.val = sentinel;
|
||||
CHECK(ui_data_comm_row(0u, &r) == -1, "TC-UIDATA-029a row not bound is -1");
|
||||
CHECK(r.val == sentinel, "TC-UIDATA-029b row not bound wrote nothing");
|
||||
CHECK(ui_data_comm_set(0u, 2u) == -1, "TC-UIDATA-029c set not bound is -1");
|
||||
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029d pending not bound is 0 (never fabricate WAIT)");
|
||||
CHECK(ui_data_comm_bind(&s_cf_broken) == -1, "TC-UIDATA-029e incomplete table refused");
|
||||
}
|
||||
|
||||
/* TC-UIDATA-029f-o 注入后转发言行:现状逐字段原样上抛、写口 idx/值精确送达、
|
||||
* 脏位零缓存跟随(页面 WAIT/OK 的正确性全系于这两条) */
|
||||
static void tc_comm_ch6_forwarding(void)
|
||||
{
|
||||
ui_data_comm_row_t r;
|
||||
|
||||
CHECK(ui_data_comm_bind(&s_cf_stub) == 0, "TC-UIDATA-029f complete table accepted");
|
||||
CHECK(ui_data_comm_row(UI_DATA_COMM_ROW_PORT, &r) == 0, "TC-UIDATA-029g row forwarded");
|
||||
CHECK((r.val == 2404u) && (r.lo == 1024u) && (r.hi == 65535u),
|
||||
"TC-UIDATA-029h row fields copied verbatim (band = executor's, not a copy)");
|
||||
CHECK(ui_data_comm_row(UI_DATA_COMM_ROW_N, &r) == -1,
|
||||
"TC-UIDATA-029i out-of-range idx rejected at the port too");
|
||||
CHECK(ui_data_comm_row(0u, NULL) == -1, "TC-UIDATA-029j NULL out refused even bound");
|
||||
CHECK(ui_data_comm_set(UI_DATA_COMM_ROW_CA, 0u) == -1,
|
||||
"TC-UIDATA-029k out-of-band write rejected (band owned by executor)");
|
||||
CHECK(s_cf_dirty == 0u, "TC-UIDATA-029l rejection left the section clean");
|
||||
CHECK(ui_data_comm_set(UI_DATA_COMM_ROW_CA, 7u) == 0, "TC-UIDATA-029m in-band write accepted");
|
||||
CHECK((s_cf_last_idx == UI_DATA_COMM_ROW_CA) && (s_cf_last_val == 7u),
|
||||
"TC-UIDATA-029n executor got exact idx and value");
|
||||
CHECK(ui_data_comm_pending() == 1u, "TC-UIDATA-029o dirty forwarded (WAIT leg)");
|
||||
s_cf_dirty = 0u;
|
||||
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029p clean follows immediately (OK leg, zero cache)");
|
||||
(void)ui_data_comm_bind(NULL);
|
||||
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029q unbind restores safe defaults");
|
||||
s_cf_dirty = 1u; /* 桩此刻仍脏:撤销后连问都不该问到桩 */
|
||||
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029r unbind stops reaching executor entirely");
|
||||
s_cf_dirty = 0u;
|
||||
s_cf_val[0] = 1u; /* 复位假件,别给后面的用例留串台状态 */
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
|
||||
int main(void)
|
||||
@@ -1539,6 +1638,9 @@ int main(void)
|
||||
printf("-- yt channel: forwarding --\n");
|
||||
tc_yt_forwarding();
|
||||
|
||||
tc_comm_ch6_not_bound(); /* 道G task-9 通道六(TC-UIDATA-029) */
|
||||
tc_comm_ch6_forwarding();
|
||||
|
||||
printf("\n=== UI Data Snapshot Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
|
||||
Reference in New Issue
Block a user