@@ -1,69 +1,31 @@
/*
* 模块名称:Time Sync( PC 本地时间推送接收 )主机侧单元测试
* 模块功能:在 PC 上对 App/sync/time_sync.c 做离线验证(4 个函数全覆盖:
* time_sync_delay_fed / time_sync_open_listener / time_sync_handle_conn /
* time_sync_task)。
* 模块名称:Time Sync( timeSyncTask 节拍宿主 )主机侧单元测试
* 模块功能:在 PC 上对 App/sync/time_sync.c 做离线验证(2 个函数全覆盖:
* time_sync_delay_fed / time_sync_task)。
*
* 被测对象:真实源文件 ../../../App/sync/time_sync.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
* - FreeRTOS/task.h -> vTaskDelay / xTaskGetTickCount(可控时钟)
* - net_socket.h -> socket/bind/listen/accept/close/recv_to/
* get_generation/htons(全部脚本化)
* - net_socket.h -> net_get_generation(脚本化 + 可跳出主循环)
* - sys_events.h -> osEventFlagsWait(可注入"未就绪")
* - sys_clock.h -> sys_clock_set_unix(记录 epoch)
* - task_watchdog.h -> task_wd_beat / task_wd_bind(记录心跳节奏)
* - dbg_log.h -> DBG_ERROR/DBG_INFO 计数
* - time_sntp.h -> time_sntp_run(替身:记录轮次与收到的代次)
*
* 为什么要主机侧单测(而不是只用板端套件):
* 1. **本模块是探针窗口心跳缺口的当事方**:当时的记录写着"time_sync 监听重建期间心跳缺口
* 800ms~1s,距 IWDG 最坏超时 1701ms 余量仅 ~0.7s"。缺口的成因全部是本模块的
* **纯逻辑**——分片延时(delay_fed)的心跳节奏、监听重试退避(500/2000ms)、
* accept 轮询退避(200ms)、收尾延时(50ms)、以及"屏障等待态也要喂心跳"。
* 这些在真机上只能靠"跑起来看复位不复位"间接观察,主机侧可**逐片断言**。
* 2. **时间包重组状态机**(handle_conn)是纯逻辑:累积到 8 字节才解析、30s 总预算
* 随 tick 推进耗尽、TCP 分段送达、EOF/真错误/超时三种返回码分支——板端套件
* 无法精确构造"分段 + 预算耗尽 + tick 回绕"的组合。
* 3. 不占 MCU 的 RAM,毫秒级完成。
* 版本史:v1 时代本套件还测 :8888 监听重试与 "TIME" 包重组状态机(80 断言点)。
* 2026-11-04 :8888 对时退役(PR #77 后用户裁定,理由见墙钟维护说明 §15),
* 监听/协议/OPER 留痕三组用例随生产代码一并退场——历史覆盖见 git 历史
* ( test/host/time_sync/ 2026-10-10 版)。留下并扩强的部分是**这个任务
* 现在的全部生产逻辑**:心跳分片节奏(探针窗口心跳缺口的当事方至今
* 仍是本模块——第 8b 轮消抖、2026-10-02 屏障喂心跳两条台架教训都钉在
* 这里)与"循环按代次驱动 SNTP"。
*
* 覆盖点(TC-TS-xxx):
* —— time_sync_delay_fed(可喂心跳的分片延时)——
* TC-TIMESYNC-001 0ms 不睡眠不喂狗
* TC-TIMESYNC-002 恰好一片(50ms)
* TC-TIMESYNC-003 非整片(120ms)切成 50/50/20,每片喂心跳
* TC-TIMESYNC-004 长延时(2000ms) = 40 片 × 50ms,每片喂心跳
* TC-TIMESYNC-005 不足一片(49ms)单片处理
* TC-TIMESYNC-006 总预算 30s = 600 片(片长上限恒 50ms)
* —— time_sync_open_listener(监听建立与重试)——
* TC-TIMESYNC-010 首次成功:socket/bind/listen 各一次,端口经 net_htons,无退避
* TC-TIMESYNC-011 bind 失败 -> 关 socket + 退避 2000ms + 留痕,随后重试成功
* TC-TIMESYNC-012 listen 失败 -> 关 socket + 退避 2000ms,随后重试成功
* TC-TIMESYNC-013 socket 连续失败 -> 5 次后返回 -1( 5 条 ERROR, 5× 500ms 退避)
* TC-TIMESYNC-014 第 3 次重试成功并返回新描述符
* TC-TIMESYNC-015 每次重试前 + 每片延时各喂一次心跳,且心跳位恒为 TIMESYNC
* —— time_sync_handle_conn(时间包重组状态机)——
* TC-TIMESYNC-020 8 字节一次收齐 -> 大端解析 epoch + 更新墙钟 + 关连接
* TC-TIMESYNC-021 分段送达(4+4)-> 累积后解析;第二次只请求剩余 12 字节
* TC-TIMESYNC-022 一次到达 16 字节 -> 只取前 8 字节
* TC-TIMESYNC-023 前缀非 "TIME" -> 不更新墙钟,但仍关连接
* TC-TIMESYNC-024 对端关闭(n==0) -> 立即退出、不解析
* TC-TIMESYNC-025 真错误(n==-1) -> 立即退出、不解析
* TC-TIMESYNC-026 只收到 4 字节后 EOF -> 不足 8 字节不解析
* TC-TIMESYNC-027 30s 预算耗尽 -> 留痕 "no TIME packet in 30s, drop conn"
* TC-TIMESYNC-028 剩余预算不足一片 -> 末片按剩余(20ms)切分
* TC-TIMESYNC-029 32 位 tick 回绕 -> 预算计算仍正确
* —— time_sync_task(任务主循环)——
* TC-TIMESYNC-040 sys_evtHandle==NULL:跳过就绪屏障;accept 轮询->成功->处理->收尾
* TC-TIMESYNC-041 就绪屏障:100ms 分片等待 NET_READY( osFlagsNoClear 不消费就绪位)
* TC-TIMESYNC-042 屏障未就绪期间持续喂心跳(2026-10-02 台架根因的回归防线)
* TC-TIMESYNC-043 网络代次变化 -> 关闭旧监听并重监听
* TC-TIMESYNC-044 监听建立失败 -> 5 次重试后放弃并退避 2000ms
* 对时操作留痕(道G task-50,语义行名不配号——编号规范已停发测试用例号):
* "sync trace on success" / "… rejected" / "… two in a row" / "… bad magic" 四组
* 断言口径(用户裁定 2026-10-27,编号规范 §9):新用例一律**语义行名**,
* 不再配 TC 号;旧 TC-TIMESYNC-* 号随用例退役永久作废、不得复用。
*
* 通过:全部 TC PASS( main 返回 0)。
* 通过:全部断言 PASS( main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-10
* 创建日期:2026-10-10( 2026-11-04 随 :8888 退役收缩重写)
*/
# include <setjmp.h>
@@ -75,14 +37,13 @@
# include "FreeRTOS.h"
# include "task.h"
# include "net_socket.h"
# include "sys_clock.h"
# include "sys_events.h"
# include "task_watchdog.h" /* 真实头(App/util):task_wd_id_t / TASK_WD_TIMESYNC */
# include "sys_log .h" /* 真实头(App/log,纯 stdint 无 OS 依赖):syslog_id_t / SYSLOG_OPER 契约,假件签名与之逐字对齐 */
# include "time_sntp .h" /* 真实头(App/sync):time_sntp_run 签名原汁,替身必须对表 */
/* ===================== FreeRTOS 桩 ===================== */
static TickType_t s_tick ;
# define DELAY_LOG_MAX 800
# define DELAY_LOG_MAX 2048
static uint32_t s_delays [ DELAY_LOG_MAX ] ;
static int s_delay_calls ;
@@ -102,7 +63,7 @@ osEventFlagsId_t sys_evtHandle = NULL;
static uint32_t s_evt_seq [ 8 ] ;
static int s_evt_seq_n ;
static uint32_t s_evt_default = 0xFFFFFFFFu ; /* 未脚本化时的返回值(默认:就绪) */
static uint32_t s_evt_default = 0xFFFFFFFFu ; /* 未脚本化时的返回值 */
static int s_evt_calls ;
static int s_evt_longjmp_at = - 1 ; /* >0 时第 N 次调用触发 longjmp */
static uint32_t s_evt_last_flags ;
@@ -129,12 +90,12 @@ uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t optio
return s_evt_default ;
}
/* ===================== 看门狗桩 ===================== */
/* ===================== 看门狗桩 =====================
* ⚠ 计数变量命名带 s_wd_ 前缀:C 的"暂定定义"规则下,同一编译单元里
* 两个同名 `static int x;` 会被静默合并(不报错),历史版本里 task_wd_bind
* 与 net_bind 的计数器撞过一次名导致断言全部失真(2026-10-10 踩过)。 */
static int s_beat_calls ;
static int s_beat_id_bad ;
/* ⚠ 命名必须与 net_socket 桩的 net_bind 计数区分开:C 的"暂定定义"规则下,
* 同一编译单元里两个 `static int x;` 会被**静默合并为同一个对象**(不报错),
* 于是 task_wd_bind 与 net_bind 会共用计数器,断言全部失真(2026-10-10 踩过)。 */
static int s_wd_bind_calls ;
static int s_wd_bind_id_bad ;
@@ -152,126 +113,16 @@ void task_wd_bind(task_wd_id_t id) {
}
}
/* ===================== sys_cl ock 桩 ===================== */
static int s_set_unix_calls ;
static uint32_t s_set_unix_last ;
static int s_set_unix_rc ; /* 可脚本化返回值:非 0 = 对时被拒(task-50) */
int sys_clock_set_unix ( uint32_t unix_secs ) {
s_set_unix_calls + + ;
s_set_unix_last = unix_secs ;
return s_set_unix_rc ;
}
/* ===================== sys_log 投递桩(task-50) =====================
* 假件按投递顺序记录:条数 / 日志 ID / 末条描述 / 对时文案出现次数。
* "出现次数"专为钉"连续两次不合并"——若生产侧哪天改成边沿锁存,
* 第三条用例会红,而那正是要拦的语义漂移。 */
static int s_post_calls ;
static int s_post_id ;
static char s_post_last_desc [ 32 ] ;
static int s_post_desc_count ;
void syslog_post_quiet ( syslog_id_t id , const char * desc ) {
s_post_calls + + ;
s_post_id = ( int ) id ;
if ( desc ! = NULL ) {
( void ) strncpy ( s_post_last_desc , desc , sizeof ( s_post_last_desc ) - 1u ) ;
s_post_last_desc [ sizeof ( s_post_last_desc ) - 1u ] = ' \0 ' ;
if ( strcmp ( desc , " clock set by 8888 " ) = = 0 ) {
s_post_desc_count + + ;
}
}
}
/* ===================== net_socket 桩 ===================== */
static uint16_t s_htons_last ;
static int s_socket_calls ;
static int s_socket_seq [ 8 ] ;
static int s_socket_seq_n ;
static int s_socket_default = 5 ;
static int s_bind_calls ;
static int s_bind_seq [ 8 ] ;
static int s_bind_seq_n ;
static uint16_t s_bind_port ;
static uint16_t s_bind_family ;
static int s_listen_calls ;
static int s_listen_seq [ 8 ] ;
static int s_listen_seq_n ;
static int s_accept_calls ;
static int s_accept_seq [ 16 ] ;
static int s_accept_seq_n ;
static int s_close_calls ;
static int s_close_socks [ 32 ] ;
/* ===================== net_s ocket 桩 =====================
* :8888 退役后本任务只剩 net_get_generation 一处网络调用。桩头刻意**不声明**
* socket/bind/listen/accept/close/recv——若循环哪天重新伸手碰监听族 API,
* 链接期直接爆(编译期绊线,替代退役前的运行时断言)。 */
static uint32_t s_gen_seq [ 8 ] ;
static int s_gen_seq_n ;
static uint32_t s_gen_default = 1u ;
static int s_gen_calls ;
static int s_gen_longjmp_at = - 1 ; /* >0 时第 N 次调用触发 longjmp */
uint16_t net_htons ( uint16_t hostshort ) {
s_htons_last = hostshort ;
return ( uint16_t ) ( ( ( hostshort & 0xFFu ) < < 8 ) | ( hostshort > > 8 ) ) ;
}
int net_socket ( int domain , int type ) {
int i = s_socket_calls + + ;
( void ) domain ;
( void ) type ;
return ( i < s_socket_seq_n ) ? s_socket_seq [ i ] : s_socket_default ;
}
int net_bind ( int sockfd , const struct net_sockaddr * addr , int addrlen ) {
const struct net_sockaddr_in * a = ( const struct net_sockaddr_in * ) addr ;
int i = s_bind_calls + + ;
( void ) sockfd ;
( void ) addrlen ;
s_bind_family = a - > sin_family ;
s_bind_port = a - > sin_port ;
return ( i < s_bind_seq_n ) ? s_bind_seq [ i ] : 0 ;
}
int net_listen ( int sockfd ) {
int i = s_listen_calls + + ;
( void ) sockfd ;
return ( i < s_listen_seq_n ) ? s_listen_seq [ i ] : 0 ;
}
int net_accept ( int sockfd , struct net_sockaddr * addr , int * addrlen ) {
int i = s_accept_calls + + ;
( void ) sockfd ;
( void ) addr ;
( void ) addrlen ;
return ( i < s_accept_seq_n ) ? s_accept_seq [ i ] : - 1 ;
}
int net_close ( int sockfd ) {
if ( s_close_calls < 32 ) {
s_close_socks [ s_close_calls ] = sockfd ;
}
s_close_calls + + ;
return 0 ;
}
/* ---- net_drop_conn(头阻塞收口):计数 + 记录 fd ---- */
static int s_drop_calls ;
static int s_drop_socks [ 32 ] ;
int net_drop_conn ( int sockfd ) {
if ( s_drop_calls < 32 ) {
s_drop_socks [ s_drop_calls ] = sockfd ;
}
s_drop_calls + + ;
return 0 ;
}
uint32_t net_get_generation ( void ) {
int i = s_gen_calls + + ;
@@ -281,59 +132,29 @@ uint32_t net_get_generation(void) {
return ( i < s_gen_seq_n ) ? s_gen_seq [ i ] : s_gen_default ;
}
/* ---- net_recv_to:脚本化返回码 + 可推进 tick ---- */
typedef struct {
int n ; /* 返回码:>0 字节数 / 0 EOF / -1 错误 / -2 超时无数据 */
uint8_t data [ 16 ] ; /* 要"送达"的字节 */
int len ; /* data 有效长度 */
uint32_t tick_adv ; /* 返回前把假时钟推进多少 ms */
} recv_step_t ;
/* ===================== SNTP 客户机替身 =====================
* 行为断言在 test/host/sntp 独立套件里做;本套件只钉两件事:
* ① 每轮循环恰好调一次 time_sntp_run;
* ② 收到的代次 = 本轮 net_get_generation() 的返回值(断网重入语义的输入端)。 */
static int s_sntp_run_calls ;
static uint32_t s_sntp_gen_log [ 8 ] ;
static int s_sntp_gen_log_n ;
static int s_sntp_run_longjmp_at = - 1 ; /* >0 时第 N 次调用触发 longjmp */
static recv_step_t s_recv_seq [ 16 ] ;
static int s_recv_seq_n ;
static int s_recv_idx ;
static int s_recv_calls ;
static int s_recv_default = 0 ; /* 脚本用尽后的返回码(0=EOF,保证循环终止) */
static uint32_t s_recv_to_log [ 64 ] ;
static int s_recv_len_log [ 64 ] ;
void time_sntp_run ( uint32_t net_gen ) {
int i = s_sntp_run_calls + + ;
int net_recv_to ( int sockfd , void * buf , int len , uint32_t timeout_ms ) {
int i = s_recv_calls ;
( void ) sockfd ;
if ( i < 64 ) {
s_recv_to_log [ i ] = timeout_ms ;
s_recv_len_log [ i ] = len ;
if ( i < ( int ) ( sizeof ( s_sntp_gen_log ) / sizeof ( s_sntp_gen_log [ 0 ] ) ) ) {
s_sntp_gen_log [ i ] = net_gen ;
s_sntp_gen_log_n = i + 1 ;
}
s_recv_calls + + ;
if ( s_recv_idx > = s_recv_seq_n ) {
return s_recv_default ;
}
{
const recv_step_t * st = & s_recv_seq [ s_recv_idx ] ;
int copy ;
s_recv_idx + + ;
s_tick + = st - > tick_adv ;
if ( st - > n < = 0 ) {
return st - > n ;
}
copy = ( st - > n < = len ) ? st - > n : len ;
if ( copy > st - > len ) {
copy = st - > len ;
}
if ( ( copy > 0 ) & & ( buf ! = NULL ) ) {
memcpy ( buf , st - > data , ( size_t ) copy ) ;
}
return st - > n ;
if ( s_sntp_run_longjmp_at > 0 & & ( i + 1 ) > = s_sntp_run_longjmp_at ) {
longjmp ( s_jmp , 3 ) ;
}
}
/* ===================== 断言 ===================== */
static int s_total = 0 , s_passed = 0 , s_failed = 0 ;
int g_tstest_err_count = 0 ;
int g_tstest_info_count = 0 ;
# define CHECK(cond, name) do { \
s_total++; \
@@ -341,24 +162,6 @@ int g_tstest_info_count = 0;
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
/* ---- SNTP 客户机替身(time_sync.c v1.4 起循环调用 time_sntp_run;
* 真实头 App/sync/time_sntp.h 与 time_sync.c 同目录,引号包含直达声明,
* 这里只落三个空实现——行为断言在 test/host/sntp 独立套件里做) ---- */
static int s_sntp_run_calls ;
void time_sntp_run ( uint32_t net_gen ) {
( void ) net_gen ;
s_sntp_run_calls + + ;
}
uint32_t time_sntp_sync_count ( void ) {
return 0u ;
}
int32_t time_sntp_last_offset_ms ( void ) {
return 0 ;
}
/* ===================== 被测代码(原样纳入生产源) ===================== */
# include "time_sync.c"
@@ -369,8 +172,8 @@ int32_t time_sntp_last_offset_ms(void) {
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:默认 = 网络全成功、gen 恒为 1、recv 脚本为空(脚本用尽返回 EOF)、
* 事件标志恒就绪。 各用例按需覆盖。
* 函数说明:默认 = 事件标志健康就绪(屏障一次通过)、gen 恒为 1、sntp 替身直通。
* 各用例按需覆盖。
*/
static void fakes_reset ( void ) {
s_tick = 0 ;
@@ -390,64 +193,14 @@ static void fakes_reset(void) {
s_wd_bind_calls = 0 ;
s_wd_bind_id_bad = 0 ;
s_set_unix_calls = 0 ;
s_set_unix_last = 0 ;
s_set_unix_rc = 0 ;
s_post_calls = 0 ;
s_post_id = - 1 ;
s_post_last_desc [ 0 ] = ' \0 ' ;
s_post_desc_count = 0 ;
s_htons_last = 0 ;
s_socket_calls = 0 ;
s_socket_seq_n = 0 ;
s_socket_default = 5 ;
s_bind_calls = 0 ;
s_bind_seq_n = 0 ;
s_bind_port = 0 ;
s_bind_family = 0 ;
s_listen_calls = 0 ;
s_listen_seq_n = 0 ;
s_accept_calls = 0 ;
s_accept_seq_n = 0 ;
s_close_calls = 0 ;
s_drop_calls = 0 ;
s_gen_seq_n = 0 ;
s_gen_default = 1u ;
s_gen_calls = 0 ;
s_gen_longjmp_at = - 1 ;
s_recv_seq_n = 0 ;
s_recv_idx = 0 ;
s_recv_calls = 0 ;
s_recv_default = 0 ;
g_tstest_err_count = 0 ;
g_tstest_info_count = 0 ;
}
/* 装配一个 recv 脚本步 */
static void recv_set ( int idx , int n , const uint8_t * data , int len , uint32_t tick_adv ) {
memset ( & s_recv_seq [ idx ] , 0 , sizeof ( s_recv_seq [ idx ] ) ) ;
s_recv_seq [ idx ] . n = n ;
s_recv_seq [ idx ] . len = len ;
s_recv_seq [ idx ] . tick_adv = tick_adv ;
if ( ( data ! = NULL ) & & ( len > 0 ) ) {
int copy = ( len > 16 ) ? 16 : len ;
memcpy ( s_recv_seq [ idx ] . data , data , ( size_t ) copy ) ;
}
}
/* 装配一个 "TIME" + uint32 大端 时间包 */
static void make_time_packet ( uint8_t * out , uint32_t epoch ) {
out [ 0 ] = ( uint8_t ) ' T ' ;
out [ 1 ] = ( uint8_t ) ' I ' ;
out [ 2 ] = ( uint8_t ) ' M ' ;
out [ 3 ] = ( uint8_t ) ' E ' ;
out [ 4 ] = ( uint8_t ) ( epoch > > 24 ) ;
out [ 5 ] = ( uint8_t ) ( epoch > > 16 ) ;
out [ 6 ] = ( uint8_t ) ( epoch > > 8 ) ;
out [ 7 ] = ( uint8_t ) ( epoch ) ;
s_sntp_run_calls = 0 ;
s_sntp_gen_log_n = 0 ;
s_sntp_run_longjmp_at = - 1 ;
}
/* 检查 s_delays[0..n) 是否全部等于 v */
@@ -461,268 +214,100 @@ static int delays_all(uint32_t v, int n) {
return 1 ;
}
/* ===================== TC-TIMESYNC-001..006: delay_fed ===================== */
/* ===================== delay_fed:心跳分片延时 =====================
* 本模块的全部看门狗安全论证都压在这一个函数上:片长恒 ≤50ms、每片前置心跳,
* 抽样窗(100ms)内必有心跳——第 8b 轮消抖钉下的节奏,逐片断言。 */
static void tc_delay_fed ( void ) {
fakes_reset ( ) ;
time_sync_delay_fed ( 0U ) ;
CHECK ( ( s_beat_calls = = 0 ) & & ( s_delay_calls = = 0 ) , " TC-TIMESYNC-001 delay_fed(0) neither sleeps nor beats" ) ;
CHECK ( ( s_beat_calls = = 0 ) & & ( s_delay_calls = = 0 ) , " delay_fed(0) neither sleeps nor beats " ) ;
fakes_reset ( ) ;
time_sync_delay_fed ( 50U ) ;
CHECK ( ( s_beat_calls = = 1 ) & & ( s_delay_calls = = 1 ) & & ( s_delays [ 0 ] = = 50U ) ,
" TC-TIMESYNC-002 delay_fed(50) = one 50 ms slice + 1 beat" ) ;
" delay_fed(50) = one 50 ms slice + 1 beat " ) ;
fakes_reset ( ) ;
time_sync_delay_fed ( 120U ) ;
CHECK ( ( s_beat_calls = = 3 ) & & ( s_delay_calls = = 3 ) , " TC-TIMESYNC-003 delay_fed(120) splits into 3 slices" ) ;
CHECK ( ( s_beat_calls = = 3 ) & & ( s_delay_calls = = 3 ) , " delay_fed(120) splits into 3 slices " ) ;
CHECK ( ( s_delays [ 0 ] = = 50U ) & & ( s_delays [ 1 ] = = 50U ) & & ( s_delays [ 2 ] = = 20U ) ,
" TC-TIMESYNC-003b slice lengths 50/50/20 (last slice is the remainder)" ) ;
" delay_fed(120) slice lengths 50/50/20 (last slice is the remainder)" ) ;
fakes_reset ( ) ;
time_sync_delay_fed ( 2000U ) ;
CHECK ( ( s_delay_calls = = 40 ) & & ( s_beat_calls = = 40 ) & & delays_all ( 50U , 40 ) ,
" TC-TIMESYNC-004 delay_fed(2000) = 40 slices x 50 ms, one beat per slice" ) ;
" delay_fed(2000) = 40 slices x 50 ms, one beat per slice " ) ;
fakes_reset ( ) ;
time_sync_delay_fed ( 49U ) ;
CHECK ( ( s_beat_calls = = 1 ) & & ( s_delays [ 0 ] = = 49U ) , " TC-TIMESYNC-005 less than one slice (49 ms) handled as a single slice " ) ;
CHECK ( ( s_beat_calls = = 1 ) & & ( s_delays [ 0 ] = = 49U ) ,
" delay_fed(49) less than one slice handled as a single slice " ) ;
fakes_reset ( ) ;
time_sync_delay_fed ( 30000U ) ;
CHECK ( s_delay_calls = = 600 , " TC-TIMESYNC-006 total budget 30 s = 600 slices (slice cap is always 50 ms)" ) ;
CHECK ( s_delay_calls = = 600 , " delay_fed(30000) = 600 slices (slice cap is always 50 ms)" ) ;
}
/* ===================== TC-TIMESYNC-010..015: open_listener ===================== */
static void tc_open_listener ( void ) {
fakes_reset ( ) ;
{
int s = time_sync_open_listener ( ) ;
CHECK ( s = = 5 , " TC-TIMESYNC-010 open_listener success returns the socket descriptor " ) ;
CHECK ( ( s_socket_calls = = 1 ) & & ( s_bind_calls = = 1 ) & & ( s_listen_calls = = 1 ) ,
" TC-TIMESYNC-010b socket / bind / listen called once each " ) ;
CHECK ( s_htons_last = = ( uint16_t ) TIME_SYNC_PORT , " TC-TIMESYNC-010c port goes through net_htons(8888) " ) ;
CHECK ( s_bind_port = = net_htons ( ( uint16_t ) TIME_SYNC_PORT ) , " TC-TIMESYNC-010d sin_port is in network byte order " ) ;
CHECK ( s_bind_family = = ( uint16_t ) NET_AF_INET , " TC-TIMESYNC-010e sin_family = NET_AF_INET " ) ;
CHECK ( s_delay_calls = = 0 , " TC-TIMESYNC-010f no backoff delay on the success path " ) ;
}
fakes_reset ( ) ;
s_bind_seq [ 0 ] = - 1 ;
s_bind_seq_n = 1 ;
{
int s = time_sync_open_listener ( ) ;
CHECK ( s = = 5 , " TC-TIMESYNC-011 bind succeeds on retry after one failure " ) ;
CHECK ( ( s_close_calls = = 1 ) & & ( s_close_socks [ 0 ] = = 5 ) , " TC-TIMESYNC-011b bind failure closes that socket " ) ;
CHECK ( s_delay_calls = = 40 , " TC-TIMESYNC-011c bind failure backs off 2000 ms (40 slices) " ) ;
CHECK ( g_tstest_err_count = = 1 , " TC-TIMESYNC-011d bind failure leaves a DBG_ERROR trace " ) ;
}
fakes_reset ( ) ;
s_listen_seq [ 0 ] = - 1 ;
s_listen_seq_n = 1 ;
{
int s = time_sync_open_listener ( ) ;
CHECK ( s = = 5 , " TC-TIMESYNC-012 listen succeeds on retry after one failure " ) ;
CHECK ( ( s_close_calls = = 1 ) & & ( s_close_socks [ 0 ] = = 5 ) , " TC-TIMESYNC-012b listen failure closes that socket " ) ;
CHECK ( s_delay_calls = = 40 , " TC-TIMESYNC-012c listen failure backs off 2000 ms " ) ;
}
fakes_reset ( ) ;
s_socket_default = - 1 ;
{
int s = time_sync_open_listener ( ) ;
CHECK ( s = = - 1 , " TC-TIMESYNC-013 socket keeps failing -> returns -1 " ) ;
CHECK ( s_socket_calls = = 5 , " TC-TIMESYNC-013b retry cap is 5 attempts " ) ;
CHECK ( g_tstest_err_count = = 5 , " TC-TIMESYNC-013c every failure leaves its own trace (5 DBG_ERROR lines) " ) ;
CHECK ( s_delay_calls = = 50 , " TC-TIMESYNC-013d every failure backs off 500 ms (5 x 10 slices) " ) ;
}
fakes_reset ( ) ;
s_socket_seq [ 0 ] = - 1 ;
s_socket_seq [ 1 ] = - 1 ;
s_socket_seq [ 2 ] = 9 ;
s_socket_seq_n = 3 ;
{
int s = time_sync_open_listener ( ) ;
CHECK ( s = = 9 , " TC-TIMESYNC-014 3rd retry succeeds and returns the new descriptor " ) ;
CHECK ( s_socket_calls = = 3 , " TC-TIMESYNC-014b exactly 3 attempts " ) ;
CHECK ( s_delay_calls = = 20 , " TC-TIMESYNC-014c two failures back off 500 ms x 2 " ) ;
}
fakes_reset ( ) ;
s_socket_default = - 1 ;
( void ) time_sync_open_listener ( ) ;
CHECK ( s_beat_calls = = 55 , " TC-TIMESYNC-015 every retry round (5) + every delay slice (50) beats once " ) ;
CHECK ( s_beat_id_bad = = 0 , " TC-TIMESYNC-015b the beat bit is always TASK_WD_TIMESYNC " ) ;
}
/* ===================== TC-TIMESYNC-020..029: handle_conn =====================
* ⚠ 契约变更:handle_conn **不再** net_close
* (单连接模式下 conn==listen,close 会连监听一起毁);会话收尾只返回外层,
* 由 time_sync_task 在 `conn != ls`(模拟器多连接后端)时才关数据 fd。
* 故本组断言一律为 s_close_calls == 0;任务级关闭见 TC-TIMESYNC-040e。 */
static void tc_handle_conn ( void ) {
uint8_t pkt [ 16 ] ;
/* 1) 8 字节一次收齐 */
fakes_reset ( ) ;
make_time_packet ( pkt , 0x12345678u ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
s_tick = 100U ;
time_sync_handle_conn ( 7 ) ;
CHECK ( s_set_unix_calls = = 1 , " TC-TIMESYNC-020 TIME packet in one shot -> wall clock updated once " ) ;
CHECK ( s_set_unix_last = = 0x12345678u , " TC-TIMESYNC-020b epoch parsed big-endian " ) ;
CHECK ( ( s_recv_calls = = 1 ) & & ( s_recv_len_log [ 0 ] = = 16 ) , " TC-TIMESYNC-020c the first request asks for 16 bytes " ) ;
CHECK ( s_recv_to_log [ 0 ] = = 50U , " TC-TIMESYNC-020d single wait slice length = 50 ms " ) ;
CHECK ( s_close_calls = = 0 , " TC-TIMESYNC-020e after handling, the connection is **not** closed (close is the outer conn!=ls branch job) " ) ;
CHECK ( s_drop_calls = = 0 , " TC-TIMESYNC-020g a successful session is not torn down (drop is only for give-up paths) " ) ;
CHECK ( g_tstest_info_count = = 1 , " TC-TIMESYNC-020f leaves the wall clock updated trace " ) ;
/* 2) 分段送达:4 + 4 */
fakes_reset ( ) ;
make_time_packet ( pkt , 10u ) ;
recv_set ( 0 , 4 , pkt , 4 , 10U ) ;
recv_set ( 1 , 4 , pkt + 4 , 4 , 10U ) ;
s_recv_seq_n = 2 ;
time_sync_handle_conn ( 3 ) ;
CHECK ( ( s_set_unix_calls = = 1 ) & & ( s_set_unix_last = = 10u ) , " TC-TIMESYNC-021 pieces accumulate and then parse correctly " ) ;
CHECK ( s_recv_calls = = 2 , " TC-TIMESYNC-021b read twice " ) ;
CHECK ( ( s_recv_len_log [ 0 ] = = 16 ) & & ( s_recv_len_log [ 1 ] = = 12 ) ,
" TC-TIMESYNC-021c the second request asks only for the remaining 12 bytes " ) ;
CHECK ( ( s_recv_to_log [ 0 ] = = 50U ) & & ( s_recv_to_log [ 1 ] = = 50U ) , " TC-TIMESYNC-021d 50 ms per slice (budget is ample) " ) ;
/* 3) 16 字节一次到达(只取前 8) */
fakes_reset ( ) ;
make_time_packet ( pkt , 5u ) ;
recv_set ( 0 , 16 , pkt , 16 , 0U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 4 ) ;
CHECK ( ( s_set_unix_calls = = 1 ) & & ( s_set_unix_last = = 5u ) , " TC-TIMESYNC-022 an over-long packet only takes the first 8 bytes " ) ;
CHECK ( s_recv_calls = = 1 , " TC-TIMESYNC-022b stops as soon as one read is enough " ) ;
/* 4) 前缀不是 TIME */
fakes_reset ( ) ;
pkt [ 0 ] = ( uint8_t ) ' X ' ;
pkt [ 1 ] = ( uint8_t ) ' X ' ;
pkt [ 2 ] = ( uint8_t ) ' X ' ;
pkt [ 3 ] = ( uint8_t ) ' X ' ;
pkt [ 4 ] = 0U ;
pkt [ 5 ] = 0U ;
pkt [ 6 ] = 0U ;
pkt [ 7 ] = 1U ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 4 ) ;
CHECK ( s_set_unix_calls = = 0 , " TC-TIMESYNC-023 a non-TIME prefix does not update the wall clock " ) ;
CHECK ( s_close_calls = = 0 , " TC-TIMESYNC-023b a non-TIME prefix also does not close the connection " ) ;
/* 5) 对端关闭(EOF) */
fakes_reset ( ) ;
recv_set ( 0 , 0 , NULL , 0 , 0U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( ( s_set_unix_calls = = 0 ) & & ( s_recv_calls = = 1 ) , " TC-TIMESYNC-024 peer close (EOF) exits immediately without parsing " ) ;
CHECK ( s_close_calls = = 0 , " TC-TIMESYNC-024b EOF also does not close the connection (net-layer auto_relisten re-arms the listener) " ) ;
/* 6) 真错误 */
fakes_reset ( ) ;
recv_set ( 0 , - 1 , NULL , 0 , 0U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( ( s_recv_calls = = 1 ) & & ( s_set_unix_calls = = 0 ) , " TC-TIMESYNC-025 a real error exits immediately without parsing " ) ;
/* 7) 4 字节后 EOF */
fakes_reset ( ) ;
make_time_packet ( pkt , 7u ) ;
recv_set ( 0 , 4 , pkt , 4 , 0U ) ;
recv_set ( 1 , 0 , NULL , 0 , 0U ) ;
s_recv_seq_n = 2 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( s_set_unix_calls = = 0 , " TC-TIMESYNC-026 fewer than 8 bytes are not parsed " ) ;
CHECK ( s_recv_calls = = 2 , " TC-TIMESYNC-026b keeps waiting for the remaining bytes " ) ;
/* 8) 30s 预算耗尽 */
fakes_reset ( ) ;
recv_set ( 0 , - 2 , NULL , 0 , 30000U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( s_recv_calls = = 1 , " TC-TIMESYNC-027 no more reads once the budget is exhausted (only 1 read) " ) ;
CHECK ( g_tstest_info_count = = 1 , " TC-TIMESYNC-027b leaves the no TIME packet in 30s, drop conn trace " ) ;
CHECK ( s_close_calls = = 0 , " TC-TIMESYNC-027c budget exhausted still does not close the connection " ) ;
CHECK ( s_drop_calls = = 1 , " TC-TIMESYNC-027d budget exhausted drops the session on purpose (head-of-line wrap-up: net_drop_conn kills the connection, keeps the listener) " ) ;
/* 9) 剩余预算不足一片 */
fakes_reset ( ) ;
recv_set ( 0 , - 2 , NULL , 0 , 29980U ) ;
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( s_recv_calls = = 2 , " TC-TIMESYNC-028 ends after two waits " ) ;
CHECK ( ( s_recv_to_log [ 0 ] = = 50U ) & & ( s_recv_to_log [ 1 ] = = 20U ) ,
" TC-TIMESYNC-028b second slice length = the remaining 20 ms " ) ;
/* 10) 32 位 tick 回绕 */
fakes_reset ( ) ;
s_tick = 0xFFFFFF00U ;
recv_set ( 0 , - 2 , NULL , 0 , 512U ) ; /* 推进后回绕到 0x00000100 */
s_recv_seq_n = 1 ;
time_sync_handle_conn ( 6 ) ;
CHECK ( ( s_recv_calls = = 2 ) & & ( s_recv_to_log [ 0 ] = = 50U ) & & ( s_recv_to_log [ 1 ] = = 50U ) ,
" TC-TIMESYNC-029 after a 32-bit tick wraparound the budget math is still correct " ) ;
}
/* ===================== TC-TIMESYNC-040..044: time_sync_task ===================== */
/* ===================== time_sync_task:屏障 + SNTP 节拍 ===================== */
static void tc_task ( void ) {
uint8_t pkt [ 16 ] ;
/* A) 无就绪屏障(sys_evtHandle == NULL):accept 轮询 2 次失败后成功 */
/* A) 无就绪屏障(sys_evtHandle == NULL):一轮完整节拍后于第 2 轮顶部脱出 */
fakes_reset ( ) ;
make_time_packet ( pkt , 9u ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
s_accept_seq [ 0 ] = - 1 ;
s_accept_seq [ 1 ] = - 1 ;
s_accept_seq [ 2 ] = 7 ;
s_accept_seq_n = 3 ;
/* gen 调用序:iter1 顶部(1) + accept 两次失败各一次(2,3) + iter2 顶部(4) → 第 4 次跳出 */
s_gen_longjmp_at = 4 ;
s_gen_longjmp_at = 2 ; /* iter1 顶部(1) + iter2 顶部(2) -> 跳出 */
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( ( s_wd_bind_calls = = 1 ) & & ( s_wd_bind_id_bad = = 0 ) ,
" TC-TIMESYNC-040 task entry registers the watchdog beat bit (task_wd_bind once)" ) ;
CHECK ( s_evt_calls = = 0 , " TC-TIMESYNC-040b skips the readiness barrier when sys_evtHandle==NULL" ) ;
CHECK ( s_accept_calls = = 3 , " TC-TIMESYNC-040c accept polled 3 times (2 with no connection + 1 success) " ) ;
CHECK ( ( s_set_unix_calls = = 1 ) & & ( s_set_unix_last = = 9u ) , " TC-TIMESYNC-040d the time packet arrived and the wall clock was update d" ) ;
CHECK ( s_close_calls = = 1 , " TC-TIMESYNC-040e the connection is closed once " ) ;
CHECK ( s_delay_calls = = 9 , " TC-TIMESYNC-040f delay slice count = 4+4+1 (200+200+50 ms) " ) ;
CHECK ( delays_all ( 50U , 9 ) , " TC-TIMESYNC-040g every delay i s sliced at 50 ms" ) ;
CHECK ( s_beat_calls = = 13 , " TC-TIMESYNC-040h beat count = main loop 1 + backoff 8 + accepted connection 1 + wrap-up 1 + next cycle 1 " ) ;
CHECK ( s_beat_id_bad = = 0 , " TC-TIMESYNC-040i every beat sets the TIMESYNC bit " ) ;
" task entry registers the watchdog beat bit (task_wd_bind once) " ) ;
CHECK ( s_evt_calls = = 0 , " skips the readiness barrier when sys_evtHandle==NULL " ) ;
CHECK ( ( s_gen_calls = = 2 ) & & ( s_sntp_run_calls = = 1 ) ,
" one full tick: generation read once per round, SNTP driven once per roun d" ) ;
CHECK ( ( s_sntp_gen_log_n = = 1 ) & & ( s_sntp_gen_log [ 0 ] = = 1u ) ,
" time_sntp_run receives this round's generation value " ) ;
CHECK ( s_delay_calls = = 4 , " tick wait 200 ms = 4 slice s ( slice cap 50 ms unchanged) " ) ;
CHECK ( delays_all ( 50U , 4 ) , " every tick slice sleeps exactly 50 ms " ) ;
CHECK ( s_beat_calls = = 6 , " beat count = loop heads 2 (2nd beat precedes the bail-out gen) + slices 4 " ) ;
CHECK ( s_beat_id_bad = = 0 , " every beat sets the TIMESYNC bit " ) ;
/* B) 就绪屏障:2 次未就绪 + 1 次就绪 */
/* B) 代次变化原样下传:1 -> 2(断网重建后 SNTP 内部自弃旧 socket,循环本身
* 不再有任何监听簿记——桩头不声明 socket 族 API,伸手即链接不过) */
fakes_reset ( ) ;
s_gen_seq [ 0 ] = 1u ;
s_gen_seq [ 1 ] = 2u ;
s_gen_seq_n = 2 ;
s_gen_default = 2u ;
s_gen_longjmp_at = 3 ; /* 第 3 轮顶部跳出:已跑满 2 个完整节拍 */
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( s_sntp_run_calls = = 2 , " two full ticks before bail-out " ) ;
CHECK ( ( s_sntp_gen_log_n = = 2 ) & & ( s_sntp_gen_log [ 0 ] = = 1u ) & & ( s_sntp_gen_log [ 1 ] = = 2u ) ,
" generation change is handed to the SNTP client verbatim, round by round " ) ;
CHECK ( s_delay_calls = = 8 , " each round waits its own 200 ms (4+4 slices) " ) ;
CHECK ( s_beat_calls = = 11 , " beat count = (loop head + 4 slices) x 2 rounds + loop head of round 3 " ) ;
/* C) 就绪屏障:2 次未就绪 + 1 次就绪 */
fakes_reset ( ) ;
sys_evtHandle = ( osEventFlagsId_t ) 0x55 ;
s_evt_seq [ 0 ] = osFlagsErrorTimeout ;
s_evt_seq [ 1 ] = osFlagsErrorTimeout ;
s_evt_seq [ 2 ] = SYS_EVT_NET_READY ;
s_evt_seq_n = 3 ;
s_gen_longjmp_at = 1 ;
s_gen_longjmp_at = 1 ; /* 屏障通过后第 1 轮顶部跳出 */
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( s_evt_calls = = 3 , " TC-TIMESYNC-041 readiness barrier polled 3 times (2 not ready + 1 ready)" ) ;
CHECK ( s_evt_last_timeout = = 100U , " TC-TIMESYNC-041b barrier waits are sliced at 100 ms" ) ;
CHECK ( s_evt_last_flags = = SYS_EVT_NET_READY , " TC-TIMESYNC-041c waits for the NET_READY bit" ) ;
CHECK ( s_evt_last_options = = ( osFlagsWaitAny | osFlagsNoClear ) , " TC-TIMESYNC-041d the ready bit is not consumed " ) ;
CHECK ( s_beat_calls = = 3 , " TC-TIMESYNC-041e the two not-ready rounds each bea t once (+ 1 in the main loop )" ) ;
CHECK ( s_wd_bind_calls = = 1 , " TC-TIMESYNC-041f still registers the beat routing first " ) ;
CHECK ( s_evt_calls = = 3 , " readiness barrier polled 3 times (2 not ready + 1 ready) " ) ;
CHECK ( s_evt_last_timeout = = 100U , " barrier waits are sliced at 100 ms " ) ;
CHECK ( s_evt_last_flags = = SYS_EVT_NET_READY , " waits for the NET_READY bit " ) ;
CHECK ( s_evt_last_options = = ( osFlagsWaitAny | osFlagsNoClear ) ,
" the ready bit is no t c onsumed (ftpTask and friends may still see it )" ) ;
CHECK ( s_beat_calls = = 3 , " two not-ready rounds each beat once + 1 at the loop head " ) ;
CHECK ( s_sntp_run_calls = = 0 , " no SNTP round before the barrier passes " ) ;
CHECK ( s_wd_bind_calls = = 1 , " still registers the beat routing first " ) ;
/* C ) 屏障期间持续喂心跳(就绪位长期不置位) */
/* D ) 屏障期间持续喂心跳(就绪位长期不置位, 2026-10-02 台架根因的回归防线) */
fakes_reset ( ) ;
sys_evtHandle = ( osEventFlagsId_t ) 0x55 ;
s_evt_default = osFlagsErrorTimeout ;
@@ -730,93 +315,25 @@ static void tc_task(void) {
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( s_evt_calls = = 4 , " TC-TIMESYNC-042 barrier waited 4 times, none ready (loop did not hang)" ) ;
CHECK ( s_beat_calls = = 3 , " TC-TIMESYNC-042b beats once after each not-ready round (before the 4th breaks out)" ) ;
/* D) 网络代次变化 -> 关闭旧监听并重监听 */
fakes_reset ( ) ;
s_gen_seq [ 0 ] = 1u ;
s_gen_seq [ 1 ] = 2u ;
s_gen_seq_n = 2 ;
s_gen_default = 2u ;
s_gen_longjmp_at = 4 ;
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( ( s_close_calls = = 1 ) & & ( s_close_socks [ 0 ] = = 5 ) , " TC-TIMESYNC-043 closes the old listener when the network generation changes " ) ;
/* INFO 三条:两次 open_listener 成功各一条 "TCP listen on" + 一条 "network re-init, relisten" */
CHECK ( g_tstest_info_count = = 3 , " TC-TIMESYNC-043b leaves the network re-init, relisten trace (plus 2 listen-success lines) " ) ;
CHECK ( s_accept_calls = = 2 , " TC-TIMESYNC-043c polls accept only once per loop then gives up " ) ;
CHECK ( ( s_socket_calls = = 2 ) & & ( s_bind_calls = = 2 ) & & ( s_listen_calls = = 2 ) ,
" TC-TIMESYNC-043d the second loop rebuilt the listener " ) ;
/* E) 监听建立失败 -> 5 次重试后放弃并退避 2000ms */
fakes_reset ( ) ;
s_socket_default = - 1 ;
s_gen_longjmp_at = 2 ;
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( s_socket_calls = = 5 , " TC-TIMESYNC-044 open_listener gives up after 5 internal retries " ) ;
CHECK ( g_tstest_err_count = = 6 , " TC-TIMESYNC-044b 5 net_socket FAIL lines + 1 retry later line " ) ;
CHECK ( s_delay_calls = = 90 , " TC-TIMESYNC-044c backoff 5x500 ms + 2000 ms = 90 slices " ) ;
CHECK ( s_beat_calls = = 97 , " TC-TIMESYNC-044d beat count = 2 + 5 + 50 + 40 " ) ;
CHECK ( s_beat_id_bad = = 0 , " TC-TIMESYNC-044e every beat sets the TIMESYNC bit " ) ;
CHECK ( s_evt_calls = = 4 , " barrier waited 4 times, none ready (loop did not hang) " ) ;
CHECK ( s_beat_calls = = 3 , " beats once after each not-ready round (before the 4th breaks out) " ) ;
CHECK ( ( s_gen_calls = = 0 ) & & ( s_sntp_run_calls = = 0 ) ,
" network untouched until NET_READY: no generation read, no SNTP round " ) ;
}
/* ===================== 对时操作留痕四组(道G task-50) ==== =====================
* 成功应用投一条 quiet OPER;被拒不投(失败不得记成"改过时钟");
* 连续两次各一条不合并;魔术字不符不投。
* ⚠ 断言标签用语义行名:编号规范 §9 批次三已对测试用例号停发(用户裁定 2026-10-27),
* 本组是新口径下写的——追溯靠函数名 tc_oper_trace + 下面的语义标签。 */
static void tc_oper_trace ( void ) {
uint8_t pkt [ 16 ] ;
/* 1) 对时成功 → 恰一条 OPER */
/* ===================== SNTP 驻留后的循环延续 =====================
* time_sntp_run 的契约是"单次驻留 ≤50ms,调用方在片间喂心跳"——替身模拟一次
* 驻留后返回,循环必须按原节拍继续(心跳不欠账)。 */
static void tc_sntp_dwell_then_continue ( void ) {
fakes_reset ( ) ;
make_time_packet ( pkt , 0x20251027u ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
s_set_unix_rc = 0 ;
time_sync_handle_conn ( 7 ) ;
CHECK ( s_set_unix_calls = = 1 , " sync trace: clock layer applied once " ) ;
CHECK ( s_post_calls = = 1 , " sync trace on success: exactly one operation record " ) ;
CHECK ( s_post_id = = ( int ) SYSLOG_OPER , " sync trace: record goes to the operation log " ) ;
CHECK ( strcmp ( s_post_last_desc , " clock set by 8888 " ) = = 0 ,
" sync trace: wording of the same family as the 104 time-sync record " ) ;
/* 2) 对时被拒(时钟层返非 0)→ 零留痕 */
fakes_reset ( ) ;
make_time_packet ( pkt , 7u ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
s_set_unix_rc = - 1 ;
time_sync_handle_conn ( 7 ) ;
CHECK ( s_set_unix_calls = = 1 , " sync trace rejected: call still reaches the clock layer once " ) ;
CHECK ( s_post_calls = = 0 , " sync trace rejected: NO operation record written " ) ;
/* 3) 连续两次成功会话 → 两条独立记录,不合并 */
fakes_reset ( ) ;
s_set_unix_rc = 0 ;
make_time_packet ( pkt , 100u ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
recv_set ( 1 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 2 ;
time_sync_handle_conn ( 7 ) ;
time_sync_handle_conn ( 7 ) ;
CHECK ( s_set_unix_calls = = 2 , " sync trace twice: two applied syncs " ) ;
CHECK ( s_post_calls = = 2 , " sync trace twice: two independent records, not merged " ) ;
CHECK ( s_post_desc_count = = 2 , " sync trace twice: both records are the time-sync wording (no coalescing latch) " ) ;
/* 4) 前缀不是 TIME → 时钟层都没到,更不该留痕 */
fakes_reset ( ) ;
( void ) memset ( pkt , ' X ' , 8 ) ;
recv_set ( 0 , 8 , pkt , 8 , 0U ) ;
s_recv_seq_n = 1 ;
s_set_unix_rc = 0 ;
time_sync_handle_conn ( 7 ) ;
CHECK ( ( s_set_unix_calls = = 0 ) & & ( s_post_calls = = 0 ) ,
" sync trace bad magic: neither moves the clock nor writes a record " ) ;
s_sntp_run_longjmp_at = 2 ; /* 第 2 轮驻留中脱出(模拟慢收片场景收尾) */
if ( setjmp ( s_jmp ) = = 0 ) {
time_sync_task ( NULL ) ;
}
CHECK ( s_sntp_run_calls = = 2 , " loop re-enters the SNTP client every tick " ) ;
CHECK ( s_beat_calls = = 6 , " beats = (head + 4 slices) for round 1 + head of round 2 (dwell bail-out) " ) ;
CHECK ( s_delay_calls = = 4 , " round 1 waited its full 200 ms; round 2 bailed mid-tick " ) ;
CHECK ( s_beat_id_bad = = 0 , " the beat bit stays TIMESYNC across rounds " ) ;
}
/* ===================== main ===================== */
@@ -826,14 +343,10 @@ int main(void) {
printf ( " -- delay_fed (heartbeat-fed slicing) -- \n " ) ;
tc_delay_fed ( ) ;
printf ( " -- open_listener (retry / backoff ) -- \n " ) ;
tc_open_listener ( ) ;
printf ( " -- handle_conn (packet reassembly / budget) -- \n " ) ;
tc_handle_conn ( ) ;
printf ( " -- time_sync_task (barrier / accept / relisten) -- \n " ) ;
printf ( " -- time_sync_task (barrier / SNTP cadence ) -- \n " ) ;
tc_task ( ) ;
printf ( " -- operation trace on sync (task-50) -- \n " ) ;
tc_oper_trace ( ) ;
printf ( " -- sntp dwell then continue -- \n " ) ;
tc_sntp_dwell_then_continue ( ) ;
printf ( " \n === Time Sync Tests: %d/%d PASSED (failed=%d) === \n " ,
s_passed , s_total , s_failed ) ;