测试:新增 sys_clock 主机侧单测并修复 Zeller 星期缺陷
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user