/* * 模块名称: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 #include #include /* * 先纳入桩头文件,再纳入生产源码——保证 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; }