Files
STM32F4-Base/test/host/sys_clock/sys_clock_test.c
T
wandering1 bda7ab2b50 refactor(编号): 16 个测试模块码单词化 + 门禁识别面连带放宽
批 2(用户授权改名 + 「新名要一眼看懂」裁定)
- 16 个有歧义模块码单词化,共 1,146 处随 scripts/rename_ids.py 改写:
  AI→ADCIN、AH→ADCHEALTH、PS→PROTSET、PE→PROTELEM、PH→PHASOR、
  TS→TIMESYNC、VL→VOLLOCK、NP→NANDPORT、CFG→CONFIG、CLK→SYSCLK、
  WD→WATCHDOG、STO→STORAGE、SH→SHA256、FA→FTPAUTH、FR→FREQ、104→IEC104
- 不动 NET/GD5F/RTC/SOE/UI/ADC/LOG/RS485/TOOL(已是单词、芯片名或业界通用词,
  改了纯属 churn)
- 别名按全局登记:这批字面量已完全限定(TC-<码>-<nnn>),不存在裸形多义
- 映射表存档 scripts/rename_maps/batch2.json 作审计轨迹;§6 模块码表同步

连带修门禁(关键教训)
- 模块码涨到 9 位(ADCHEALTH/WATCHDOG)后,候选正则与族正则的 {0,4} 上界
  会让改名后的号**整批从门禁眼里消失**:实测编号字面量从 8271 掉到 7315、
  悬空假降 490 处。已放宽到 {0,9} 并写进 §6 注记
- 结论:改名批次必须连带复核识别面,只改数据不改判据 = 静默放行;
  这次是靠「可判定比例跌破基线即失败」那条线拦下来的

现状与遗留
- 校验 1 未登记前缀 0 处;可判定比例 82.71%(基线 82.6%,只涨不跌成立)
- 遗留:7 个模块的「单元测试说明.md」仍缺(PROTSET 116、NANDPORT 109、
  PROTELEM 65、FREQ 48、FTPAUTH 45、SHA256 25、SOE 14 处悬空)——改名不解决
  缺文档,处置方式待议
- 遗留:T-2/T-3/T-4/T-6 历史任务号悬空(别名表未收全,属本工具批次漏项)
- 未改任何 C/H 可编译语义(仅注释与宏名内的编号),故不需 @build
2026-10-21 09:53:28 +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 与 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-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 = 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-SYSCLK-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-SYSCLK-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-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 = 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-SYSCLK-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-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 = 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-SYSCLK-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-SYSCLK-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-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 = 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-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 = 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-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;
}