Files
STM32F4-Base/test/host/sys_clock/sys_clock_test.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +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;
}