2e608118ce
task-49 盘点盘出的唯一供给缺口,task-50 实现(道H 交付)。为什么这算缺陷而不是「维护工装不值得留痕」:
对时改变的是**其后每一条记录**的时标,而 104 侧的对时**已经留痕** —— 同一类行为两种结果,这个不对称
本身就是缺陷。FTP 上传不留痕则维持原状(那是传输动作,与「装置时钟被改了」不同类),本笔
刻意不扩面到它。
实现:`time_sync_handle_conn` 里 `sys_clock_set_unix` 的返回值原先是**裸丢弃**,现改为接住判
0,==0 才
投 `SYSLOG_OPER`(走 quiet 口,netTask 侧纪律,栈成本已在注释写明);**被拒零投**(不把失败记成改过时
钟);无合并锁存(连续两次对时留两条,不折叠);**epoch 语义零触碰、零时区补偿** —— 时基口径仍在用
户手里,本笔不替产品决定。
新用例四组十二断言:成功恰一条 / 被拒不落痕 / 连续两次两条 / 魔术字不符皆零。假件把 rc 做成可脚本,
并专门钉「文案出现次数」以证不合并;`sys_log.h` 用真实头(纯 stdint,走 task_watchdog 先例),零桩漂移面。
**停发口径的第一批**:新断言**不配 TC 号**,改用语义化用例名 + 函数名追溯,注释里标了口径出处 —— 裁定
能不能落地看的就是第一批,这条留在这里给后来者当样例。
证据:time_sync 92/92(存量 82 零回归);@build **0 错 0 警**,Code=235268 RO=26308 RW
=1572 ZI=136924
(与上一笔的差值是 728d6067 合账所致,不是本笔);栈**以表为证**:timeSyncTask 链 1272→1272 B 零增
量,own frame 152 不变,handle_conn 200 / post_quiet 40 / post 264 逐函数帧全等 ——
按今天刚定稿的栈判
据,「零增量」这句话必须是表,不能是论证。id-check --strict 0(零新号零裸形)、乱码/嵌套/BOM 净。
台账:`ram_baseline.py --update` 已记当日行(IRAM1 61.9% / CCM 87.9%)。覆盖率基线**留给 L
ead 重建** ——
上一版基线是在 time_sync 套件编译不过的破树下打的,少算了该套件的覆盖。
台账同步:本笔代码改了体积(Code=235268),RAM 预算台账受管区与文档快照区按 12:46 那次权威构建回填(ram_baselin
e --write 加 check_mem_numbers --write)。代码与数字不同笔,就是下一次 mem 钩子假红的来源——上一笔 d
ocs 提交被 mem-numbers 与 mem-budget 双双拦下,正是这个原因。
824 lines
33 KiB
C
824 lines
33 KiB
C
/*
|
||
* 模块名称: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)。
|
||
*
|
||
* 被测对象:真实源文件 ../../../App/sync/time_sync.c(以 #include 原样纳入,
|
||
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
|
||
* - FreeRTOS/task.h -> vTaskDelay / xTaskGetTickCount(可控时钟)
|
||
* - net_socket.h -> socket/bind/listen/accept/close/recv_to/
|
||
* get_generation/htons(全部脚本化)
|
||
* - 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 计数
|
||
*
|
||
* 为什么要主机侧单测(而不是只用板端套件):
|
||
* 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,毫秒级完成。
|
||
*
|
||
* 覆盖点(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" 四组
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-10
|
||
*/
|
||
|
||
#include <setjmp.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
|
||
#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 契约,假件签名与之逐字对齐 */
|
||
|
||
/* ===================== FreeRTOS 桩 ===================== */
|
||
static TickType_t s_tick;
|
||
#define DELAY_LOG_MAX 800
|
||
static uint32_t s_delays[DELAY_LOG_MAX];
|
||
static int s_delay_calls;
|
||
|
||
void vTaskDelay(TickType_t xTicksToDelay) {
|
||
if (s_delay_calls < DELAY_LOG_MAX) {
|
||
s_delays[s_delay_calls] = (uint32_t)xTicksToDelay;
|
||
}
|
||
s_delay_calls++;
|
||
}
|
||
|
||
TickType_t xTaskGetTickCount(void) {
|
||
return s_tick;
|
||
}
|
||
|
||
/* ===================== CMSIS-RTOS 事件标志桩 ===================== */
|
||
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 int s_evt_calls;
|
||
static int s_evt_longjmp_at = -1; /* >0 时第 N 次调用触发 longjmp */
|
||
static uint32_t s_evt_last_flags;
|
||
static uint32_t s_evt_last_options;
|
||
static uint32_t s_evt_last_timeout;
|
||
|
||
static jmp_buf s_jmp;
|
||
|
||
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout) {
|
||
int i = s_evt_calls;
|
||
(void)ef_id;
|
||
|
||
s_evt_last_flags = flags;
|
||
s_evt_last_options = options;
|
||
s_evt_last_timeout = timeout;
|
||
s_evt_calls++;
|
||
|
||
if (s_evt_longjmp_at > 0 && (i + 1) >= s_evt_longjmp_at) {
|
||
longjmp(s_jmp, 2); /* 跳出 time_sync_task 的屏障循环(用例收尾) */
|
||
}
|
||
if (i < s_evt_seq_n) {
|
||
return s_evt_seq[i];
|
||
}
|
||
return s_evt_default;
|
||
}
|
||
|
||
/* ===================== 看门狗桩 ===================== */
|
||
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;
|
||
|
||
void task_wd_beat(task_wd_id_t id) {
|
||
s_beat_calls++;
|
||
if (id != TASK_WD_TIMESYNC) {
|
||
s_beat_id_bad++;
|
||
}
|
||
}
|
||
|
||
void task_wd_bind(task_wd_id_t id) {
|
||
s_wd_bind_calls++;
|
||
if (id != TASK_WD_TIMESYNC) {
|
||
s_wd_bind_id_bad++;
|
||
}
|
||
}
|
||
|
||
/* ===================== sys_clock 桩 ===================== */
|
||
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];
|
||
|
||
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++;
|
||
|
||
if (s_gen_longjmp_at > 0 && (i + 1) >= s_gen_longjmp_at) {
|
||
longjmp(s_jmp, 1); /* 跳出 time_sync_task 的主循环(用例收尾) */
|
||
}
|
||
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;
|
||
|
||
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];
|
||
|
||
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;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
/* ===================== 断言 ===================== */
|
||
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++; \
|
||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||
} while (0)
|
||
|
||
/* ===================== 被测代码(原样纳入生产源) ===================== */
|
||
#include "time_sync.c"
|
||
|
||
/* ===================== 工具 ===================== */
|
||
|
||
/*
|
||
* 函数功能:把全部桩复位到"健康"初值
|
||
* 入口参数:无
|
||
* 返 回 值:无
|
||
* 限定条件:无
|
||
* 函数说明:默认 = 网络全成功、gen 恒为 1、recv 脚本为空(脚本用尽返回 EOF)、
|
||
* 事件标志恒就绪。各用例按需覆盖。
|
||
*/
|
||
static void fakes_reset(void) {
|
||
s_tick = 0;
|
||
s_delay_calls = 0;
|
||
|
||
sys_evtHandle = NULL;
|
||
s_evt_seq_n = 0;
|
||
s_evt_default = SYS_EVT_NET_READY; /* 健康默认:就绪 */
|
||
s_evt_calls = 0;
|
||
s_evt_longjmp_at = -1;
|
||
s_evt_last_flags = 0;
|
||
s_evt_last_options = 0;
|
||
s_evt_last_timeout = 0;
|
||
|
||
s_beat_calls = 0;
|
||
s_beat_id_bad = 0;
|
||
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_delays[0..n) 是否全部等于 v */
|
||
static int delays_all(uint32_t v, int n) {
|
||
int k;
|
||
for (k = 0; k < n; k++) {
|
||
if (s_delays[k] != v) {
|
||
return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* ===================== TC-TIMESYNC-001..006:delay_fed ===================== */
|
||
|
||
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");
|
||
|
||
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");
|
||
|
||
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_delays[0] == 50U) && (s_delays[1] == 50U) && (s_delays[2] == 20U),
|
||
"TC-TIMESYNC-003b 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");
|
||
|
||
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");
|
||
|
||
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)");
|
||
}
|
||
|
||
/* ===================== 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 ===================== */
|
||
|
||
static void tc_task(void) {
|
||
uint8_t pkt[16];
|
||
|
||
/* A) 无就绪屏障(sys_evtHandle == NULL):accept 轮询 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;
|
||
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 updated");
|
||
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 is 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");
|
||
|
||
/* B) 就绪屏障: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;
|
||
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 beat once (+ 1 in the main loop)");
|
||
CHECK(s_wd_bind_calls == 1, "TC-TIMESYNC-041f still registers the beat routing first");
|
||
|
||
/* C) 屏障期间持续喂心跳(就绪位长期不置位) */
|
||
fakes_reset();
|
||
sys_evtHandle = (osEventFlagsId_t)0x55;
|
||
s_evt_default = osFlagsErrorTimeout;
|
||
s_evt_longjmp_at = 4;
|
||
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");
|
||
}
|
||
|
||
/* ===================== 对时操作留痕四组(道G task-50) =========================
|
||
* 成功应用投一条 quiet OPER;被拒不投(失败不得记成"改过时钟");
|
||
* 连续两次各一条不合并;魔术字不符不投。
|
||
* ⚠ 断言标签用语义行名:编号规范 §9 批次三已对测试用例号停发(用户裁定 2026-10-27),
|
||
* 本组是新口径下写的——追溯靠函数名 tc_oper_trace + 下面的语义标签。 */
|
||
static void tc_oper_trace(void) {
|
||
uint8_t pkt[16];
|
||
|
||
/* 1) 对时成功 → 恰一条 OPER */
|
||
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");
|
||
}
|
||
|
||
/* ===================== main ===================== */
|
||
|
||
int main(void) {
|
||
printf("=== Time Sync Tests ===\n");
|
||
|
||
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");
|
||
tc_task();
|
||
printf("-- operation trace on sync (task-50) --\n");
|
||
tc_oper_trace();
|
||
|
||
printf("\n=== Time Sync Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|