Files
STM32F4-Base/test/host/sys_clock/sys_clock_test.c
T
wandering1 78248a1cdf feat(S-5): 收编 SD2506/TPAFE 并给 dhara 失败分类,门禁基线 19→17
前一笔立契约与门禁(`Drivers/BSP/bsp_err.h` + `bsp-err` 钩子 + RS485 试点);这一笔按用户
指定顺序 C3 → C1 → C2 把三个模块真正收编,并让存储失败第一次变得可读、可分类。

**C3 · SD2506 整模块(11 个公共 API + 6 个内部 helper)**:三枚私有宏 `SD2506_OK`/
`SD2506_ERROR`/`SD2506_I2C_ERROR` **删除而不是留别名**——留别名等于新代码还能用旧形状,
把迁移洗掉;编译器替我们找全所有调用点。新增 `sd2506_err_from_hal()`,**把 I2C 超时从
"和没人应答共用一个 −2"里拆出来归成 `BSP_ERR_TIMEOUT`(可重试)**——SD2506 是启动链第一站、
I2C1 上电时序敏感,这是本波唯一真正的语义改善,不是改名。`sd2506_init` 的 charge 寄存器
**回读校验不符**原返参数类错误,改归 `BSP_ERR_IO`(那不是调用方的错)。

**C1 · 同形两义**:`GD5F_BUSY_TIMEOUT` → `GD5F_WAIT_EXPIRED`(**值 −2 不变**,纯消"名字叫
TIMEOUT 其实是返回码")。**GD5F 私有码集整体收编刻意没做**:169 处引用里 48 处在板级套件、
还要动 Trap 记录用词,属另一波带真机+套件回归的工作,不夹带。TPAFE5160 收编 23 处:
`BUSY_TIMEOUT → BSP_ERR_TIMEOUT`、`ERROR → BSP_ERR_PARAM`(**同值 −1,行为零变化**)、
`FRSTDATA_ERR → BSP_ERR_NO_DEVICE`(E-44 的本义就是"芯片没驱动 DB 总线",归到器件无响应比
私有 −3 有用,且避开与 `BSP_ERR_TIMEOUT` 撞值)。实测 `tpafe5160.o`/`gd5f2gq5ue.o` 的 Code
增量为 **0**——改名不产生一条指令。

⚠ **`tpafe5160_snapshot()` 刻意不套成败码**:它是 1=有新帧 / 0=无新帧 / −1=参数错 的三态
**状态**返回,0 不是错误而是"这一拍没有新数据",强行收编会把慢消费者的正常路径变成失败。
顺带订正我上一版在 `bsp_err.h` 的断言——"本层只有 `net_*` 把数量与错误挤同一返回值"是错的,
`snapshot`/`adc_input_poll` 就是第二族;两族现都已写明并各注保留理由。

**C2 · FTL 坍缩点分类**:`ftl_flush_cache`/`ftl_read_page` 不再把 dhara 失败压成
  `GD5F_ERROR`,
按**处置差异**分三类,并在最早同时知道 LPN 与原始码的位置就地打印:`ECC`/`TOO_BAD` →
`BSP_ERR_CRC`(页级,重试或换页,不代表卷坏);`JOURNAL_FULL`/`MAP_FULL` →
`BSP_ERR_NO_SPACE`(介质健康但块池/日志见底,此时重格式化只是把问题推回去);
`BAD_BLOCK`/`RECOVER`/`CORRUPT_MAP`/越界 → `BSP_ERR_IO`(完整性故障,保卷 + 上报,不许自愈
重格式化)。这与 **E-31** 是同一条纪律的两端。可读名用自建短表:**本仓 dhara 没有编
`error.c`,用 `dhara_strerror()` 会直接链接失败**。`disk_read`/`disk_write` 仍回
  `RES_ERROR`
(FatFs 契约);要让 defaultTask 卷健康探针按类分支还差跨调用存活的快照 → C4 待还。

**两次靠核实拦住的自我错误**:① 我一度认定 `DBG_ERROR("... err=%d", err)` 是把结构体塞进
varargs 的格式 bug——核 `Lib/dhara/error.h` 后确认 `dhara_error_t` 是 `enum`(标量),那行日志
本来合法,改了反而是引入错误;② `bsp-err` 一次 `replace_all` 越界把 8 处 **GD5F 码空间**的
判据改成 `!= BSP_OK`(数值同为 0、无行为变化),但那正是"把两个码空间混起来",已逐一还原。
`cppcheck` 亦抓到 `cls` 声明作用域过宽,已收进失败分支。

**验证**:`@build` 0 错 0 警告(`Code` 213,648、RO 18,860、`ER_IROM1` 232,508 B /
  22.2%、
`ROM 合计` 233,696;**RAM 三项零增量**);`@test` PASS(692 ms,烧入戳 `Oct 23 01:35:32`)
且逐条读到被改模块的锚点:`SD2506 init: OK (ret=0)` 与 ID/时间/温度/电池四条读回正常、
`ADC init: OK`、`NAND init: OK`、`Map resumed: 14094/97504`、`adc=` 帧计数持续递增(证明
snapshot 三态未被改坏)、`[ERR]`/Fault/FAIL 0 条。主机单测:系统时钟 100/100、dhara NAND
移植层 149/149(两者都因本次改动需补 `Drivers/BSP` include 路径,补后在钩子内实跑 Passed)。
`pre-commit run` 全量 RC=0(20 个实跑钩子 Passed);`bsp-err` 基线 **19 → 17**(还掉两个
`*_BUSY_TIMEOUT = -2`)、`--prune` 干净;`doc-links --all --sections-strict` 147 篇
  0 问题;
`id-check --strict`、`layer-deps`、`mem-numbers`、`mem-budget`、`ui-golden` 全过;33
  个文件
字节卫生 0 异常(无 CR / 无 U+FFFD / 无行尾空格 / 均有尾换行)。

**口径与数字联动**:新增 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.4(三类码与处置
理由);`pre-commit使用说明.md` §2.27 与 `AGENTS.md` 基线计数 19 → 17;TPAFE README、
GD5F 测试指南与 Trap 记录均**保留旧名留痕**以便对旧日志;配图桶按 .map 对象级增量重算
(`dhara/FTL` 10,931 → 11,222,+291 全部落在 `nand_ftl.o`;应用代码/UI +36 落在
`app_main.o`;其余散量入"其他"7,557;桶和 = Code+RO 断言通过、文本溢出 0);
`ram_baseline --update/--write`、`ram_report_20261023.md` 快照、报告 §1.3/§5.1/§5.4
  与
部署文档 §3.3 数字全部同步;报告 §0.4/§9 的 S-5 状态改为 🟡 主体已落地。
2026-10-23 07:42:02 +08:00

593 lines
25 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.
/*
* 模块名称:System Clock 主机侧单元测试
* 模块功能:在 PC 上对 App/util/sys_clock.c 的**时间换算与墙钟基准逻辑**做穷举验证。
*
* 被测对象:真实源文件 ../../../App/util/sys_clock.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。硬件访问由本文件提供的强符号
* (HAL_GetTick / sd2506_get_time / sd2506_set_time)替换为可观测桩;
* 临界区宏与 DBG_* 由 stub/ 头文件替换。
*
* 隔离:**不改生产代码**——模块静态态(s_base_tick / s_base_unix_secs)经
* 公开 API 复位:
* - "未初始化"状态只能靠进程首用例(在 main 最前、任何 init/set 之前)
* 断言,见 tc_000;
* - 其余用例用 sys_clock_set(unix, NULL) 建立确定基准(无 RTC 副作用)。
*
* 覆盖点(对应 docs/测试说明/系统时钟单元测试说明.md 的 TC-CLK-xxx):
* TC-SYSCLK-000 未初始化:sys_clock_get / get_time / get_str 的无效语义
* TC-SYSCLK-001 init 正常:RTC 合法 → 返回 0,基准 = 换算 Unix 秒
* TC-SYSCLK-002 init 闰年换算:2024-02-29 / 2000-02-29(世纪闰)/ 2/29→3/1 间隔
* TC-SYSCLK-003 init 月末边界:1/31、2/28、4/30 的 Unix 秒精确值
* TC-SYSCLK-004 init 世纪边界:2000-01-01、2038 上限(0x7FFFFFFF)、Unix 0 经 set
* TC-SYSCLK-005 init RTC 读失败 → 降级默认时间,返回 -1 + 打错误日志
* TC-SYSCLK-006 init RTC 越界(年份/月/日非法)→ 自愈写回默认时间(返回 0)
* TC-SYSCLK-020 init 越界且写回失败 → 降级默认时间 + 返回 -1
* TC-SYSCLK-007 init 越界 → 自动写回默认时间并复验(rtc 被修正)
* TC-SYSCLK-008 get 正常推进:tick +1000 → +1 秒(含亚秒截断)
* TC-SYSCLK-009 get 未跨阈值不回绕:tick + 29 天仍线性
* TC-SYSCLK-010 get 30 天再基准化:窗口清零、墙钟值无损、含亚秒余数结转
* TC-SYSCLK-011 get tick 回绕(无符号差)安全
* TC-SYSCLK-012 get 再基准化后长期不漂移(多轮 30 天)
* TC-SYSCLK-013 get_str 格式化 + 未初始化占位串
* TC-SYSCLK-014 get_time 日历字段(年/月/日/时/分/秒/星期)与已知日期对照
* TC-SYSCLK-015 get_time NULL 参数被拒
* TC-SYSCLK-016 set 更新基准 + 写回 RTC(观察 sd2506_set_time 被调)
* TC-SYSCLK-017 set sd_time=NULL 不碰 RTC
* TC-SYSCLK-018 set_unix 写回 RTC 且日历字段正确(含星期)
* TC-SYSCLK-019 星期(Zeller)与已知日期对照穷举(跨世纪/闰/月末)
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-02
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/*
* 先纳入桩头文件,再纳入生产源码——保证 HAL_GetTick / sd2506_time_t 等
* 类型与声明在使用前可见(生产源码内部是同名 include,走 include guard
* 不再重复)。
* - stub/main.h → HAL_GetTick 声明
* - stub/FreeRTOS.h → 临界区宏(空操作)
* - stub/task.h → 经 FreeRTOS.h
* - stub/dbg_log.h → DBG_* 映射与 g_sclk_err_count
*/
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dbg_log.h"
/*
* sd2506_time_t 与 BSP_OK 必须先可见(桩函数签名 / 阴影寄存器用它)。
* 真实 sd2506.h 内部 #include "main.h" 命中本目录 stub/main.h,故可安全使用;
* sys_clock.h 提供被测 API 原型(与 sys_clock.c 内部 include 走同一 guard)。
*/
#include "sd2506.h"
#include "sys_clock.h"
/* ===================== 平台桩:可控时钟 ===================== */
/* 可控毫秒时钟(单测手动推进;真实 MCU 由 TIM7 累加) */
static uint32_t s_tick = 0u;
uint32_t HAL_GetTick(void) { return s_tick; }
/* ===================== 平台桩:SD2506 RTC 伪实现 =====================
*
* 用一个可注入的"影子 RTC"替换真实 I2C 读写:
* - s_rtc :阴影寄存器,存当前 RTC 时间
* - s_rtc_read_rc :下一次/后续 sd2506_get_time 的返回码(可注入故障)
* - s_rtc_write_rc :sd2506_set_time 的返回码
* - s_rtc_get_calls :读次数(观测 init 是否重读)
* - s_rtc_set_calls :写次数(观测 init/set 是否写回)
*/
static sd2506_time_t s_rtc;
static bsp_err_t s_rtc_read_rc = BSP_OK;
static bsp_err_t s_rtc_write_rc = BSP_OK;
static int s_rtc_get_calls = 0;
static int s_rtc_set_calls = 0;
int sd2506_get_time(sd2506_time_t *p_time) {
s_rtc_get_calls++;
if (s_rtc_read_rc != BSP_OK) {
return s_rtc_read_rc; /* 故障注入:不动 p_time */
}
*p_time = s_rtc;
return BSP_OK;
}
int sd2506_set_time(const sd2506_time_t *p_time) {
s_rtc_set_calls++;
if (s_rtc_write_rc != BSP_OK) {
return s_rtc_write_rc;
}
s_rtc = *p_time; /* 成功则阴影寄存器被更新(复读可见) */
return BSP_OK;
}
/* DBG_ERROR 计数 */
int g_sclk_err_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)
#define CHECK_EQ_U32(got, want, name) do { \
uint32_t _g = (uint32_t)(got), _w = (uint32_t)(want); \
s_total++; \
if (_g == _w) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s got=%lu want=%lu (line %d)\n", \
name, (unsigned long)_g, (unsigned long)_w, __LINE__); } \
} while (0)
/* ===================== 被测代码 ===================== */
/* 原样纳入生产源文件(stub/ 里的头文件生效) */
#include "sys_clock.c"
/* ===================== 辅助 ===================== */
/* 把阴影 RTC 置为一个确定时间 */
static void rtc_set(uint16_t y, uint8_t mo, uint8_t d,
uint8_t h, uint8_t mi, uint8_t s) {
s_rtc.year = y; s_rtc.month = mo; s_rtc.day = d;
s_rtc.hour = h; s_rtc.minute = mi; s_rtc.second = s;
s_rtc.week = 0; /* 驱动不校验 week,init 只读它不用于换算 */
}
/* 清观测计数(不改模块静态态) */
static void clear_obs(void) {
s_rtc_get_calls = 0;
s_rtc_set_calls = 0;
g_sclk_err_count = 0;
}
/* 建立一个确定的墙钟基准(无 RTC 副作用 —— sd_time=NULL) */
static void base_at(uint32_t unix_secs, uint32_t tick) {
s_tick = tick;
sys_clock_set(unix_secs, NULL);
}
/* 时钟推进 */
static void tick_advance(uint32_t ms) { s_tick += ms; }
/*
* 独立计算"某公历日期 00:00:00 的 Unix 秒"(与生产算法正交,用于交叉校验)。
* 仅用 1970~2099 范围,纯整数。
*/
static uint32_t ref_days_since_epoch(uint32_t y, uint32_t mo, uint32_t d) {
uint32_t days = 0, yy;
for (yy = 1970; yy < y; yy++) {
days += ((yy % 4 == 0 && yy % 100 != 0) || yy % 400 == 0) ? 366u : 365u;
}
{
uint8_t md[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) md[1] = 29;
uint32_t i;
for (i = 0; i + 1 < mo; i++) days += md[i];
}
days += d - 1u;
return days;
}
static uint32_t ref_unix(uint32_t y, uint32_t mo, uint32_t d,
uint32_t h, uint32_t mi, uint32_t s) {
return ref_days_since_epoch(y, mo, d) * 86400u + h * 3600u + mi * 60u + s;
}
/* ===================== 用例 ===================== */
/* 必须是第一个用例:此时模块静态态仍为"刚上电"(s_base_unix_secs = UINT32_MAX) */
static void tc_000_uninitialized(void) {
sd2506_time_t t;
char buf[32];
printf("TC-SYSCLK-000 未初始化语义\n");
/* 注意:本用例不做 sys_clock_init / sys_clock_set */
CHECK_EQ_U32(sys_clock_get(), UINT32_MAX, "未初始化 get 返回 UINT32_MAX");
CHECK(sys_clock_get_time(&t) == -1, "未初始化 get_time 返回 -1");
memset(buf, 0, sizeof(buf));
CHECK(sys_clock_get_str(buf, sizeof(buf)) == buf, "get_str 返回 buf 指针");
CHECK(strcmp(buf, "----/--/-- --:--:--") == 0, "get_str 未初始化占位串");
}
static void tc_001_init_ok(void) {
int rc;
printf("TC-SYSCLK-001 init 正常(RTC 合法)\n");
rtc_set(2026, 8, 24, 0, 0, 0);
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
clear_obs();
s_tick = 12345u;
rc = sys_clock_init();
CHECK(rc == 0, "RTC 合法 → init 返回 0");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 8, 24, 0, 0, 0),
"基准 = 2026-08-24 00:00:00 的 Unix 秒");
CHECK(s_rtc_set_calls == 0, "合法 RTC 不被写回(无校时)");
}
static void tc_002_init_leap(void) {
printf("TC-SYSCLK-002 init 闰年换算\n");
/* 2024 是闰年:2/29 存在 */
rtc_set(2024, 2, 29, 12, 0, 0);
s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2024-02-29 合法");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 2, 29, 12, 0, 0),
"2024-02-29 12:00:00 换算正确");
/* 2000 是世纪闰年:2/29 存在 */
rtc_set(2000, 2, 29, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2000-02-29 合法(世纪闰年)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 2, 29, 0, 0, 0),
"2000-02-29 换算正确");
/* 3 月 1 日:验证闰年 2 月多 1 天被正确累计 */
rtc_set(2024, 3, 1, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2024-03-01 合法");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 3, 1, 0, 0, 0),
"2024-03-01 与 2024-02-29 相差恰 1 天");
CHECK_EQ_U32(ref_unix(2024, 3, 1, 0, 0, 0) - ref_unix(2024, 2, 29, 0, 0, 0),
86400u, "闰日间隔 = 86400 s");
}
static void tc_003_init_month_end(void) {
printf("TC-SYSCLK-003 init 月末边界\n");
rtc_set(2026, 1, 31, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "1/31 合法");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 1, 31, 0, 0, 0), "1/31 换算");
rtc_set(2026, 2, 28, 23, 59, 59); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2/28 合法(平年)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 2, 28, 23, 59, 59), "2/28 末端换算");
rtc_set(2026, 4, 30, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "4/30 合法(30 天月)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 4, 30, 0, 0, 0), "4/30 换算");
/* 2/28 → 3/1 间隔恰 1 天(平年) */
rtc_set(2026, 2, 28, 0, 0, 0); sys_clock_init();
uint32_t a = sys_clock_get();
rtc_set(2026, 3, 1, 0, 0, 0); sys_clock_init();
uint32_t b = sys_clock_get();
CHECK_EQ_U32(b - a, 86400u, "平年 2/28 → 3/1 间隔 = 86400 s");
}
static void tc_004_init_epoch_bounds(void) {
printf("TC-SYSCLK-004 init 世纪边界(Unix 下限 / 2038 上限)\n");
/*
* 注意:init 接受的 RTC 年份范围是 2000~2099(越界即判故障并自愈),
* 故"Unix 0(1970-01-01)"**不可能经 init 建立**——1970 会被判越界。
* Unix 0 经由 set 路径可达,见 tc_017(base_at(0) 等价)。
* 这里验证 init 可接受的最早年月,以及 uint32 上限。
*/
rtc_set(2000, 1, 1, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2000-01-01 合法(可接受最早年)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 1, 1, 0, 0, 0), "2000-01-01 换算");
/* 2038 上限:2038-01-19 03:14:07 = 0x7FFFFFFF */
rtc_set(2038, 1, 19, 3, 14, 7); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2038-01-19 合法(uint32 上限内)");
CHECK_EQ_U32(sys_clock_get(), 0x7FFFFFFFu, "2038-01-19 03:14:07 → 0x7FFFFFFF");
/* Unix 0 经 set 路径显式可达(与 init 的范围限制无关) */
clear_obs();
CHECK(sys_clock_set(0u, NULL) == 0, "set(0) 合法(Unix 零点)");
CHECK_EQ_U32(sys_clock_get(), 0u, "set(0) 后 get == 0(Unix 零点)");
{
sd2506_time_t t;
CHECK(sys_clock_get_time(&t) == 0
&& t.year == 1970 && t.month == 1 && t.day == 1,
"Unix 0 → 1970-01-01(unix_to_sd2506 反向换算)");
}
}
static void tc_005_init_rtc_read_fail(void) {
printf("TC-SYSCLK-005 init RTC 读失败 → 降级默认时间并返回 -1\n");
s_rtc_read_rc = BSP_ERR_IO; /* 注入读故障 */
s_rtc_write_rc = BSP_OK;
clear_obs();
s_tick = 777u;
int rc = sys_clock_init();
CHECK(rc == -1, "RTC 读失败 → init 返回 -1(报告芯片故障)");
/* 降级基准 = 编译期默认 RTC_DEFAULT_*(2026-08-24 00:00:00) */
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, RTC_DEFAULT_HOUR,
RTC_DEFAULT_MINUTE, RTC_DEFAULT_SECOND),
"降级基准 = 编译期默认时间(墙钟仍可用)");
CHECK(g_sclk_err_count >= 1, "打了 DBG_ERROR(RTC 异常)");
s_rtc_read_rc = BSP_OK; /* 复位注入 */
}
static void tc_006_init_out_of_range_selfheal(void) {
printf("TC-SYSCLK-006 init RTC 越界 → 自愈写回默认时间(返回 0)\n");
/*
* RTC_DEFAULT_INIT_ENABLE=1 且 FORCE=0 时:越界值触发"写回默认时间",
* 写成功且复读合法 → rtc_ok 恢复为 1 → **返回 0**(自愈,非降级)。
* (只有写回/复读仍失败才走降级 -1,见 tc_005 与 tc_020。)
*/
rtc_set(1999, 5, 5, 0, 0, 0); /* year < 2000 → 越界 */
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
clear_obs();
CHECK(sys_clock_init() == 0, "year=1999 → 自愈写回成功 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR, "阴影 RTC 已修正为默认年");
rtc_set(2026, 13, 5, 0, 0, 0); /* month > 12 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "month=13 → 自愈 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
rtc_set(2026, 5, 0, 0, 0, 0); /* day < 1 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "day=0 → 自愈 → 返回 0");
CHECK(s_rtc_set_calls == 1, "越界触发一次写回");
}
static void tc_020_init_selfheal_fail_degrades(void) {
printf("TC-SYSCLK-020 init 越界且写回失败 → 降级默认时间并返回 -1\n");
/*
* 组合故障:读成功但值越界(触发写回),写回失败 → 复读仍越界 →
* rtc_ok 清 0 → 走降级分支(启用默认值时建基准 + 返回 -1)。
*/
rtc_set(1999, 5, 5, 0, 0, 0);
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_ERR_IO; /* 写回失败 */
clear_obs();
s_tick = 42u;
int rc = sys_clock_init();
CHECK(rc == -1, "越界 + 写回失败 → init 返回 -1");
CHECK(s_rtc_set_calls == 1, "尝试写回一次(失败)");
CHECK(g_sclk_err_count >= 1, "打了 DBG_ERROR");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"降级基准 = 编译期默认时间(墙钟仍可用)");
s_rtc_write_rc = BSP_OK;
}
static void tc_007_init_corrects_rtc(void) {
printf("TC-SYSCLK-007 init 越界 → 自动写回默认时间并复验\n");
rtc_set(1999, 1, 1, 0, 0, 0); /* 越界,触发校时写回 */
s_rtc_read_rc = BSP_OK;
s_rtc_write_rc = BSP_OK;
clear_obs();
int rc = sys_clock_init();
CHECK(s_rtc_set_calls == 1, "越界 → 写回 RTC 一次");
CHECK(rc == 0, "写回成功 + 复读合法 → 返回 0(自愈成功,非降级)");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR && s_rtc.month == RTC_DEFAULT_MONTH
&& s_rtc.day == RTC_DEFAULT_DAY, "阴影 RTC 已被写入默认时间");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"基准 = 修正后的默认时间");
}
static void tc_008_get_advance(void) {
printf("TC-SYSCLK-008 get 正常推进(含亚秒截断)\n");
base_at(1000000u, 5000u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "基准时刻 = 基准值");
tick_advance(999u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "999ms 未满 1s → 不变(截断)");
tick_advance(1u);
CHECK_EQ_U32(sys_clock_get(), 1000001u, "累计 1000ms → +1s");
tick_advance(2500u);
CHECK_EQ_U32(sys_clock_get(), 1000003u, "再 +2500ms → 共 +3s(余 500ms 截断)");
}
static void tc_009_get_no_rebase_below_threshold(void) {
printf("TC-SYSCLK-009 未达 30 天阈值不触发再基准化\n");
base_at(2000000u, 0u);
/* 29 天 < 30 天阈值:线性推进,基准不动 */
tick_advance(29u * 24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), 2000000u + 29u * 24u * 3600u,
"29 天线性推进正确(未再基准化)");
}
static void tc_010_get_rebase_at_threshold(void) {
printf("TC-SYSCLK-010 30 天惰性再基准化(窗口清零、值无损、余数结转)\n");
base_at(3000000u, 0u);
/* 推进恰好 30 天 + 1500ms(余数 500ms 应结转到新窗) */
tick_advance(30u * 24u * 3600u * 1000u + 1500u);
uint32_t expect = 3000000u + 30u * 24u * 3600u + 1u; /* 1500ms → +1s,余 500ms */
CHECK_EQ_U32(sys_clock_get(), expect, "再基准化后墙钟值无损(+30 天 1 秒)");
/* 再推 500ms:结转的余数正好补足,再多 1 秒 */
tick_advance(500u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u, "亚秒余数已结转(+500ms 补足 1 秒)");
/* 再推一整天:仍线性 */
tick_advance(24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u + 86400u, "再基准化后继续线性推进");
}
static void tc_011_get_tick_wrap(void) {
printf("TC-SYSCLK-011 tick 回绕(无符号差)安全\n");
/* 基准设在接近 2^32 处 */
base_at(4000000u, 0xFFFFF000u);
/* 推进 0x2000 ms(跨越 2^32 回绕):差 = 0x2000 = 8192ms → +8s */
tick_advance(0x2000u);
CHECK_EQ_U32(sys_clock_get(), 4000000u + 8u, "跨 tick 回绕后差值正确");
}
static void tc_012_get_long_run_no_drift(void) {
printf("TC-SYSCLK-012 多轮再基准化长期不漂移\n");
base_at(5000000u, 0u);
/*
* 惰性再基准化的**正确用法**:周期性调用 get() 让差值窗口始终 < 30 天。
* 若一次性 tick_advance(90 天),中间不做 get(),diff 会超过 2^32 ms
* (49.7 天)而混叠——那是**调用方违约**,不是本模块缺陷(阈值 30 天
* 就是为保证"每 <49.7 天调用一次"时窗口恒不溢出)。
* 故此处按 10 天为步长推进 9 次,累计 90 天,每次观测一次。
*/
uint32_t base = 5000000u;
uint32_t i;
for (i = 1u; i <= 9u; i++) {
tick_advance(10u * 86400u * 1000u); /* 每次 +10 天(< 30 天阈值) */
CHECK_EQ_U32(sys_clock_get(), base + i * 10u * 86400u,
"10 天步进(含 3 轮再基准)线性无漂移");
}
}
static void tc_013_get_str(void) {
printf("TC-SYSCLK-013 get_str 格式化\n");
char buf[32];
base_at(ref_unix(2026, 10, 2, 13, 4, 5), 0u);
memset(buf, 0, sizeof(buf));
sys_clock_get_str(buf, sizeof(buf));
CHECK(strcmp(buf, "2026-10-02 13:04:05") == 0, "格式化 YYYY-MM-DD HH:MM:SS");
base_at(ref_unix(2000, 1, 1, 0, 0, 0), 0u);
memset(buf, 0, sizeof(buf));
sys_clock_get_str(buf, sizeof(buf));
CHECK(strcmp(buf, "2000-01-01 00:00:00") == 0, "零点补零格式正确");
}
static void tc_014_get_time_fields(void) {
printf("TC-SYSCLK-014 get_time 日历字段\n");
sd2506_time_t t;
/* 2026-10-02 是周五(已知) */
base_at(ref_unix(2026, 10, 2, 8, 9, 10), 0u);
CHECK(sys_clock_get_time(&t) == 0, "get_time 返回 0");
CHECK(t.year == 2026 && t.month == 10 && t.day == 2,
"年月日正确");
CHECK(t.hour == 8 && t.minute == 9 && t.second == 10, "时分秒正确");
CHECK(t.week == 5, "2026-10-02 星期五(week==5,0=周日)");
}
static void tc_015_get_time_null(void) {
printf("TC-SYSCLK-015 get_time NULL 参数被拒\n");
base_at(6000000u, 0u);
CHECK(sys_clock_get_time(NULL) == -1, "p_time == NULL → 返回 -1");
}
static void tc_016_set_writes_rtc(void) {
printf("TC-SYSCLK-016 set 更新基准 + 写回 RTC\n");
sd2506_time_t sd;
memset(&sd, 0, sizeof(sd));
sd.year = 2027; sd.month = 3; sd.day = 15;
sd.hour = 6; sd.minute = 7; sd.second = 8; sd.week = 1;
s_rtc_write_rc = BSP_OK;
clear_obs();
s_tick = 100u;
CHECK(sys_clock_set(7000000u, &sd) == 0, "set 返回 0");
CHECK_EQ_U32(sys_clock_get(), 7000000u, "基准已更新");
CHECK(s_rtc_set_calls == 1, "sd_time != NULL → 写回 RTC 一次");
CHECK(s_rtc.year == 2027 && s_rtc.month == 3 && s_rtc.day == 15
&& s_rtc.hour == 6 && s_rtc.minute == 7 && s_rtc.second == 8,
"RTC 写入内容与传入一致");
/* 写失败:基准仍更新,但打错误日志 */
clear_obs();
s_rtc_write_rc = BSP_ERR_IO;
sys_clock_set(7000001u, &sd);
CHECK_EQ_U32(sys_clock_get(), 7000001u, "RTC 写失败仍更新基准");
CHECK(g_sclk_err_count >= 1, "RTC 写失败打 DBG_ERROR");
s_rtc_write_rc = BSP_OK;
}
static void tc_017_set_no_rtc(void) {
printf("TC-SYSCLK-017 set sd_time=NULL 不碰 RTC\n");
clear_obs();
CHECK(sys_clock_set(8000000u, NULL) == 0, "set(NULL) 返回 0");
CHECK(s_rtc_set_calls == 0, "sd_time == NULL → 不写 RTC");
CHECK_EQ_U32(sys_clock_get(), 8000000u, "基准更新");
}
static void tc_018_set_unix_fields(void) {
printf("TC-SYSCLK-018 set_unix 写回 RTC 且字段正确\n");
sd2506_time_t expect_week;
uint32_t u = ref_unix(2026, 10, 2, 15, 30, 45);
clear_obs();
s_rtc_write_rc = BSP_OK;
s_tick = 0u;
CHECK(sys_clock_set_unix(u) == 0, "set_unix 返回 0");
CHECK(s_rtc_set_calls == 1, "写回 RTC 一次");
CHECK(s_rtc.year == 2026 && s_rtc.month == 10 && s_rtc.day == 2
&& s_rtc.hour == 15 && s_rtc.minute == 30 && s_rtc.second == 45,
"RTC 日历字段与 Unix 秒一致");
CHECK(s_rtc.week == 5, "RTC 星期字段 = 5(2026-10-02 周五)");
(void)expect_week;
}
static void tc_019_week_known_dates(void) {
printf("TC-SYSCLK-019 星期(Zeller)已知日期穷举\n");
/* 期望值经 Python datetime 独立核对(0=周日);
* 覆盖 1/2 月(Zeller 需上调为上年 13/14 月的分支)与 3~12 月(2026-10-02
* 修正前全部错误),以及世纪闰年、非闰世纪、范围首尾。*/
static const struct { uint16_t y; uint8_t m, d, w; } cases[] = {
{ 2000, 1, 1, 6 }, /* 星期六(Jan 分支) */
{ 1970, 1, 1, 4 }, /* 星期四(Jan 分支) */
{ 2024, 2, 29, 4 }, /* 星期四(Feb 分支 + 闰日) */
{ 2000, 2, 29, 2 }, /* 星期二(世纪闰 + Feb 分支) */
{ 2026, 10, 2, 5 }, /* 星期五(10 月,修正前曾错为 4) */
{ 2038, 1, 19, 2 }, /* 星期二(uint32 上限日) */
{ 1999, 12, 31, 5 }, /* 星期五(12 月,修正前曾错) */
{ 2026, 1, 31, 6 }, /* 星期六 */
{ 2025, 3, 1, 6 }, /* 星期六(3 月,修正前曾错为 5) */
{ 2100, 1, 1, 5 }, /* 星期五(2100 不闰) */
{ 2026, 8, 24, 1 }, /* 星期一(编译期默认日) */
{ 2026, 4, 30, 4 }, /* 星期四(30 天月月末) */
{ 2026, 5, 1, 5 }, /* 星期五(月首) */
{ 2026, 7, 4, 6 }, /* 星期六 */
{ 2026, 11, 30, 1 }, /* 星期一(30 天月月末) */
{ 2026, 12, 31, 4 }, /* 星期四(年末) */
};
size_t i;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
sd2506_time_t t;
uint32_t u = ref_unix(cases[i].y, cases[i].m, cases[i].d, 12, 0, 0);
base_at(u, 0u);
if (sys_clock_get_time(&t) == 0) {
char nm[64];
snprintf(nm, sizeof(nm), "%04u-%02u-%02u week == %u",
cases[i].y, cases[i].m, cases[i].d, cases[i].w);
CHECK(t.week == cases[i].w, nm);
} else {
CHECK(0, "get_time 失败(用例数据问题)");
}
}
}
int main(void) {
printf("=== System Clock 主机侧单元测试 ===\n");
printf("被测:App/util/sys_clock.c(时间换算 + 墙钟基准)\n\n");
/* tc_000 必须最先(依赖"刚上电"的静态态) */
tc_000_uninitialized();
tc_001_init_ok();
tc_002_init_leap();
tc_003_init_month_end();
tc_004_init_epoch_bounds();
tc_005_init_rtc_read_fail();
tc_006_init_out_of_range_selfheal();
tc_020_init_selfheal_fail_degrades();
tc_007_init_corrects_rtc();
tc_008_get_advance();
tc_009_get_no_rebase_below_threshold();
tc_010_get_rebase_at_threshold();
tc_011_get_tick_wrap();
tc_012_get_long_run_no_drift();
tc_013_get_str();
tc_014_get_time_fields();
tc_015_get_time_null();
tc_016_set_writes_rtc();
tc_017_set_no_rtc();
tc_018_set_unix_fields();
tc_019_week_known_dates();
printf("\n=== System Clock Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}