@@ -0,0 +1,592 @@
/*
* 模块名称: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-CLK-000 未初始化:sys_clock_get / get_time / get_str 的无效语义
* TC-CLK-001 init 正常:RTC 合法 → 返回 0,基准 = 换算 Unix 秒
* TC-CLK-002 init 闰年换算:2024-02-29 / 2000-02-29(世纪闰)/ 2/29→3/1 间隔
* TC-CLK-003 init 月末边界:1/31、2/28、4/30 的 Unix 秒精确值
* TC-CLK-004 init 世纪边界:2000-01-01、2038 上限(0x7FFFFFFF)、Unix 0 经 set
* TC-CLK-005 init RTC 读失败 → 降级默认时间,返回 -1 + 打错误日志
* TC-CLK-006 init RTC 越界(年份/月/日非法)→ 自愈写回默认时间(返回 0)
* TC-CLK-020 init 越界且写回失败 → 降级默认时间 + 返回 -1
* TC-CLK-007 init 越界 → 自动写回默认时间并复验(rtc 被修正)
* TC-CLK-008 get 正常推进:tick +1000 → +1 秒(含亚秒截断)
* TC-CLK-009 get 未跨阈值不回绕:tick + 29 天仍线性
* TC-CLK-010 get 30 天再基准化:窗口清零、墙钟值无损、含亚秒余数结转
* TC-CLK-011 get tick 回绕(无符号差)安全
* TC-CLK-012 get 再基准化后长期不漂移(多轮 30 天)
* TC-CLK-013 get_str 格式化 + 未初始化占位串
* TC-CLK-014 get_time 日历字段(年/月/日/时/分/秒/星期)与已知日期对照
* TC-CLK-015 get_time NULL 参数被拒
* TC-CLK-016 set 更新基准 + 写回 RTC(观察 sd2506_set_time 被调)
* TC-CLK-017 set sd_time=NULL 不碰 RTC
* TC-CLK-018 set_unix 写回 RTC 且日历字段正确(含星期)
* TC-CLK-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 与 SD2506_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 int s_rtc_read_rc = SD2506_OK ;
static int s_rtc_write_rc = SD2506_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 ! = SD2506_OK ) {
return s_rtc_read_rc ; /* 故障注入:不动 p_time */
}
* p_time = s_rtc ;
return SD2506_OK ;
}
int sd2506_set_time ( const sd2506_time_t * p_time ) {
s_rtc_set_calls + + ;
if ( s_rtc_write_rc ! = SD2506_OK ) {
return s_rtc_write_rc ;
}
s_rtc = * p_time ; /* 成功则阴影寄存器被更新(复读可见) */
return SD2506_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-CLK-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-CLK-001 init 正常(RTC 合法) \n " ) ;
rtc_set ( 2026 , 8 , 24 , 0 , 0 , 0 ) ;
s_rtc_read_rc = SD2506_OK ;
s_rtc_write_rc = SD2506_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-CLK-002 init 闰年换算 \n " ) ;
/* 2024 是闰年:2/29 存在 */
rtc_set ( 2024 , 2 , 29 , 12 , 0 , 0 ) ;
s_rtc_read_rc = SD2506_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-CLK-003 init 月末边界 \n " ) ;
rtc_set ( 2026 , 1 , 31 , 0 , 0 , 0 ) ; s_rtc_read_rc = SD2506_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 = SD2506_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 = SD2506_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-CLK-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 = SD2506_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 = SD2506_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-CLK-005 init RTC 读失败 → 降级默认时间并返回 -1 \n " ) ;
s_rtc_read_rc = SD2506_ERROR ; /* 注入读故障 */
s_rtc_write_rc = SD2506_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 = SD2506_OK ; /* 复位注入 */
}
static void tc_006_init_out_of_range_selfheal ( void ) {
printf ( " TC-CLK-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 = SD2506_OK ;
s_rtc_write_rc = SD2506_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-CLK-020 init 越界且写回失败 → 降级默认时间并返回 -1 \n " ) ;
/*
* 组合故障:读成功但值越界(触发写回),写回失败 → 复读仍越界 →
* rtc_ok 清 0 → 走降级分支(启用默认值时建基准 + 返回 -1)。
*/
rtc_set ( 1999 , 5 , 5 , 0 , 0 , 0 ) ;
s_rtc_read_rc = SD2506_OK ;
s_rtc_write_rc = SD2506_ERROR ; /* 写回失败 */
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 = SD2506_OK ;
}
static void tc_007_init_corrects_rtc ( void ) {
printf ( " TC-CLK-007 init 越界 → 自动写回默认时间并复验 \n " ) ;
rtc_set ( 1999 , 1 , 1 , 0 , 0 , 0 ) ; /* 越界,触发校时写回 */
s_rtc_read_rc = SD2506_OK ;
s_rtc_write_rc = SD2506_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-CLK-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-CLK-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-CLK-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-CLK-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-CLK-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-CLK-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-CLK-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-CLK-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-CLK-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 = SD2506_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 = SD2506_ERROR ;
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 = SD2506_OK ;
}
static void tc_017_set_no_rtc ( void ) {
printf ( " TC-CLK-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-CLK-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 = SD2506_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-CLK-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 ;
}