924ac3d428
用户裁定「统一」。口径就是 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 回填留待主工作树合并后。
593 lines
26 KiB
C
593 lines
26 KiB
C
/*
|
||
* 模块名称: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;
|
||
}
|