Files
STM32F4-Base/test/host/sys_clock/sys_clock_test.c
T
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

593 lines
26 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 uninitialised semantics\n");
/* 注意:本用例不做 sys_clock_init / sys_clock_set */
CHECK_EQ_U32(sys_clock_get(), UINT32_MAX, "get before init returns UINT32_MAX");
CHECK(sys_clock_get_time(&t) == -1, "get_time before init returns -1");
memset(buf, 0, sizeof(buf));
CHECK(sys_clock_get_str(buf, sizeof(buf)) == buf, "get_str returns the buf pointer");
CHECK(strcmp(buf, "----/--/-- --:--:--") == 0, "get_str placeholder string before init");
}
static void tc_001_init_ok(void) {
int rc;
printf("TC-SYSCLK-001 init normal (valid 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, "valid RTC -> init returns 0");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 8, 24, 0, 0, 0),
"base = Unix seconds of 2026-08-24 00:00:00");
CHECK(s_rtc_set_calls == 0, "valid RTC is never written back (no time correction)");
}
static void tc_002_init_leap(void) {
printf("TC-SYSCLK-002 init leap-year conversion\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 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 2, 29, 12, 0, 0),
"2024-02-29 12:00:00 converts correctly");
/* 2000 是世纪闰年:2/29 存在 */
rtc_set(2000, 2, 29, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2000-02-29 accepted (century leap year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 2, 29, 0, 0, 0),
"2000-02-29 converts correctly");
/* 3 月 1 日:验证闰年 2 月多 1 天被正确累计 */
rtc_set(2024, 3, 1, 0, 0, 0);
CHECK(sys_clock_init() == 0, "2024-03-01 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 3, 1, 0, 0, 0),
"2024-03-01 is exactly 1 day after 2024-02-29");
CHECK_EQ_U32(ref_unix(2024, 3, 1, 0, 0, 0) - ref_unix(2024, 2, 29, 0, 0, 0),
86400u, "leap-day gap = 86400 s");
}
static void tc_003_init_month_end(void) {
printf("TC-SYSCLK-003 init end-of-month boundaries\n");
rtc_set(2026, 1, 31, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "1/31 accepted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 1, 31, 0, 0, 0), "1/31 conversion");
rtc_set(2026, 2, 28, 23, 59, 59); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "2/28 accepted (common year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 2, 28, 23, 59, 59), "2/28 end-of-day conversion");
rtc_set(2026, 4, 30, 0, 0, 0); s_rtc_read_rc = BSP_OK;
CHECK(sys_clock_init() == 0, "4/30 accepted (30-day month)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 4, 30, 0, 0, 0), "4/30 conversion");
/* 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, "common year 2/28 -> 3/1 gap = 86400 s");
}
static void tc_004_init_epoch_bounds(void) {
printf("TC-SYSCLK-004 init century boundaries (Unix lower bound / 2038 upper bound)\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 accepted (earliest acceptable year)");
CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 1, 1, 0, 0, 0), "2000-01-01 conversion");
/* 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 accepted (within uint32 limit)");
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) accepted (Unix epoch)");
CHECK_EQ_U32(sys_clock_get(), 0u, "after set(0) get == 0 (Unix epoch)");
{
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 reverse conversion)");
}
}
static void tc_005_init_rtc_read_fail(void) {
printf("TC-SYSCLK-005 init RTC read failure -> degrade to default time and return -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 read failure -> init returns -1 (reports the chip fault)");
/* 降级基准 = 编译期默认 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),
"degraded base = compile-time default time (wall clock still usable)");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted (RTC abnormal)");
s_rtc_read_rc = BSP_OK; /* 复位注入 */
}
static void tc_006_init_out_of_range_selfheal(void) {
printf("TC-SYSCLK-006 init RTC out of range -> self-heal by writing back the default time (returns 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 -> self-heal write-back ok -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR, "shadow RTC corrected to the default year");
rtc_set(2026, 13, 5, 0, 0, 0); /* month > 12 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "month=13 -> self-heal -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
rtc_set(2026, 5, 0, 0, 0, 0); /* day < 1 → 越界 */
clear_obs();
CHECK(sys_clock_init() == 0, "day=0 -> self-heal -> returns 0");
CHECK(s_rtc_set_calls == 1, "out-of-range triggers exactly one write-back");
}
static void tc_020_init_selfheal_fail_degrades(void) {
printf("TC-SYSCLK-020 init out of range and write-back fails -> degrade to default time and return -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, "out of range + write-back failure -> init returns -1");
CHECK(s_rtc_set_calls == 1, "write-back attempted once (failed)");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"degraded base = compile-time default time (wall clock still usable)");
s_rtc_write_rc = BSP_OK;
}
static void tc_007_init_corrects_rtc(void) {
printf("TC-SYSCLK-007 init out of range -> auto write-back of default time and re-verify\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, "out of range -> RTC written back once");
CHECK(rc == 0, "write-back ok + re-read valid -> returns 0 (self-heal succeeded, not a degrade)");
CHECK(s_rtc.year == RTC_DEFAULT_YEAR && s_rtc.month == RTC_DEFAULT_MONTH
&& s_rtc.day == RTC_DEFAULT_DAY, "shadow RTC was written with the default time");
CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH,
RTC_DEFAULT_DAY, 0, 0, 0),
"base = the corrected default time");
}
static void tc_008_get_advance(void) {
printf("TC-SYSCLK-008 get advances normally (sub-second truncation included)\n");
base_at(1000000u, 5000u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "at the base instant the value equals the base");
tick_advance(999u);
CHECK_EQ_U32(sys_clock_get(), 1000000u, "999ms is under 1 s -> unchanged (truncated)");
tick_advance(1u);
CHECK_EQ_U32(sys_clock_get(), 1000001u, "accumulated 1000ms -> +1 s");
tick_advance(2500u);
CHECK_EQ_U32(sys_clock_get(), 1000003u, "another +2500ms -> +3 s total (remaining 500 ms truncated)");
}
static void tc_009_get_no_rebase_below_threshold(void) {
printf("TC-SYSCLK-009 below the 30-day threshold no re-baselining\n");
base_at(2000000u, 0u);
/* 29 天 < 30 天阈值:线性推进,基准不动 */
tick_advance(29u * 24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), 2000000u + 29u * 24u * 3600u,
"29 days advance linearly and correctly (no re-baselining)");
}
static void tc_010_get_rebase_at_threshold(void) {
printf("TC-SYSCLK-010 30-day lazy re-baselining (window cleared, value lossless, remainder carried over)\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, "after re-baselining the wall clock value is lossless (+30 days 1 second)");
/* 再推 500ms:结转的余数正好补足,再多 1 秒 */
tick_advance(500u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u, "sub-second remainder carried over (+500 ms completes 1 second)");
/* 再推一整天:仍线性 */
tick_advance(24u * 3600u * 1000u);
CHECK_EQ_U32(sys_clock_get(), expect + 1u + 86400u, "keeps advancing linearly after re-baselining");
}
static void tc_011_get_tick_wrap(void) {
printf("TC-SYSCLK-011 tick wraparound (unsigned diff) is safe\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, "diff is correct across the tick wraparound");
}
static void tc_012_get_long_run_no_drift(void) {
printf("TC-SYSCLK-012 many re-baselining rounds do not drift over time\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-day steps (with 3 re-baseline rounds) stay linear with no drift");
}
}
static void tc_013_get_str(void) {
printf("TC-SYSCLK-013 get_str formatting\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, "formatted as 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, "zero-padded midnight format is correct");
}
static void tc_014_get_time_fields(void) {
printf("TC-SYSCLK-014 get_time calendar fields\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 returns 0");
CHECK(t.year == 2026 && t.month == 10 && t.day == 2,
"year/month/day correct");
CHECK(t.hour == 8 && t.minute == 9 && t.second == 10, "hour/minute/second correct");
CHECK(t.week == 5, "2026-10-02 is Friday (week==5, 0=Sunday)");
}
static void tc_015_get_time_null(void) {
printf("TC-SYSCLK-015 get_time rejects a NULL argument\n");
base_at(6000000u, 0u);
CHECK(sys_clock_get_time(NULL) == -1, "p_time == NULL -> returns -1");
}
static void tc_016_set_writes_rtc(void) {
printf("TC-SYSCLK-016 set updates the base + writes back 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 returns 0");
CHECK_EQ_U32(sys_clock_get(), 7000000u, "base has been updated");
CHECK(s_rtc_set_calls == 1, "sd_time != NULL -> RTC written back once");
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 written content equals what was passed in");
/* 写失败:基准仍更新,但打错误日志 */
clear_obs();
s_rtc_write_rc = BSP_ERR_IO;
sys_clock_set(7000001u, &sd);
CHECK_EQ_U32(sys_clock_get(), 7000001u, "base is still updated when the RTC write fails");
CHECK(g_sclk_err_count >= 1, "DBG_ERROR emitted when the RTC write fails");
s_rtc_write_rc = BSP_OK;
}
static void tc_017_set_no_rtc(void) {
printf("TC-SYSCLK-017 set sd_time=NULL never touches the RTC\n");
clear_obs();
CHECK(sys_clock_set(8000000u, NULL) == 0, "set(NULL) returns 0");
CHECK(s_rtc_set_calls == 0, "sd_time == NULL -> RTC not written");
CHECK_EQ_U32(sys_clock_get(), 8000000u, "base updated");
}
static void tc_018_set_unix_fields(void) {
printf("TC-SYSCLK-018 set_unix writes back the RTC with correct fields\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 returns 0");
CHECK(s_rtc_set_calls == 1, "RTC written back once");
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 calendar fields match the Unix seconds");
CHECK(s_rtc.week == 5, "RTC weekday field = 5 (2026-10-02 is Friday)");
(void)expect_week;
}
static void tc_019_week_known_dates(void) {
printf("TC-SYSCLK-019 weekday (Zeller) exhaustive check against known dates\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 failed (bad test data)");
}
}
}
int main(void) {
printf("=== System Clock host-side unit tests ===\n");
printf("Target: App/util/sys_clock.c (time conversion + wall-clock base)\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;
}