/* * 模块名称: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. **本模块是 N-17 的当事方**:登记簿 N-17 记录"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 * * 通过:全部 TC PASS(main 返回 0)。 * 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。 * * 创建日期:2026-10-10 */ #include #include #include #include /* ===================== 平台桩(先纳入,覆盖真实头) ===================== */ #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 */ /* ===================== 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; int sys_clock_set_unix(uint32_t unix_secs) { s_set_unix_calls++; s_set_unix_last = unix_secs; return 0; } /* ===================== 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(E-02 收口):计数 + 记录 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_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) 不睡眠不喂狗"); 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) = 1 片 50ms + 1 次心跳"); fakes_reset(); time_sync_delay_fed(120U); CHECK((s_beat_calls == 3) && (s_delay_calls == 3), "TC-TIMESYNC-003 delay_fed(120) 切成 3 片"); CHECK((s_delays[0] == 50U) && (s_delays[1] == 50U) && (s_delays[2] == 20U), "TC-TIMESYNC-003b 片长 50/50/20(末片为余数)"); 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 片 × 50ms,每片喂一次心跳"); fakes_reset(); time_sync_delay_fed(49U); CHECK((s_beat_calls == 1) && (s_delays[0] == 49U), "TC-TIMESYNC-005 不足一片(49ms)单片处理"); fakes_reset(); time_sync_delay_fed(30000U); CHECK(s_delay_calls == 600, "TC-TIMESYNC-006 总预算 30s = 600 片(片长上限恒 50ms)"); } /* ===================== 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 成功返回 socket 描述符"); CHECK((s_socket_calls == 1) && (s_bind_calls == 1) && (s_listen_calls == 1), "TC-TIMESYNC-010b socket / bind / listen 各一次"); CHECK(s_htons_last == (uint16_t)TIME_SYNC_PORT, "TC-TIMESYNC-010c 端口经 net_htons(8888)"); CHECK(s_bind_port == net_htons((uint16_t)TIME_SYNC_PORT), "TC-TIMESYNC-010d sin_port 为网络序"); 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 成功路径无退避延时"); } 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 失败一次后重试成功"); CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-011b bind 失败关闭该 socket"); CHECK(s_delay_calls == 40, "TC-TIMESYNC-011c bind 失败退避 2000ms(40 片)"); CHECK(g_tstest_err_count == 1, "TC-TIMESYNC-011d bind 失败留痕 DBG_ERROR"); } 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 失败一次后重试成功"); CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TIMESYNC-012b listen 失败关闭该 socket"); CHECK(s_delay_calls == 40, "TC-TIMESYNC-012c listen 失败退避 2000ms"); } fakes_reset(); s_socket_default = -1; { int s = time_sync_open_listener(); CHECK(s == -1, "TC-TIMESYNC-013 socket 连续失败 -> 返回 -1"); CHECK(s_socket_calls == 5, "TC-TIMESYNC-013b 重试上限 5 次"); CHECK(g_tstest_err_count == 5, "TC-TIMESYNC-013c 每次失败各留痕一条(5 条 DBG_ERROR)"); CHECK(s_delay_calls == 50, "TC-TIMESYNC-013d 每次失败退避 500ms(5 × 10 片)"); } 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 第 3 次重试成功并返回新描述符"); CHECK(s_socket_calls == 3, "TC-TIMESYNC-014b 恰好尝试 3 次"); CHECK(s_delay_calls == 20, "TC-TIMESYNC-014c 两次失败退避 500ms × 2"); } fakes_reset(); s_socket_default = -1; (void)time_sync_open_listener(); CHECK(s_beat_calls == 55, "TC-TIMESYNC-015 每轮重试(5) + 每片延时(50) 各喂一次心跳"); CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-015b 心跳位恒为 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 包一次收齐 -> 更新墙钟一次"); CHECK(s_set_unix_last == 0x12345678u, "TC-TIMESYNC-020b epoch 按大端解析"); CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TIMESYNC-020c 首次请求 16 字节"); CHECK(s_recv_to_log[0] == 50U, "TC-TIMESYNC-020d 单次等待片长 = 50ms"); CHECK(s_close_calls == 0, "TC-TIMESYNC-020e 处理完**不**关闭连接(close 交外层 conn!=ls 分支)"); CHECK(s_drop_calls == 0, "TC-TIMESYNC-020g 成功会话不拆(drop 只用于放弃路径)"); CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-020f 留痕 wall clock updated"); /* 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 分段送达累积后解析正确"); CHECK(s_recv_calls == 2, "TC-TIMESYNC-021b 读了两次"); CHECK((s_recv_len_log[0] == 16) && (s_recv_len_log[1] == 12), "TC-TIMESYNC-021c 第二次只请求剩余 12 字节"); CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U), "TC-TIMESYNC-021d 每片 50ms(预算充裕)"); /* 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 超长包只取前 8 字节"); CHECK(s_recv_calls == 1, "TC-TIMESYNC-022b 一次读够即停"); /* 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 非 TIME 前缀不更新墙钟"); CHECK(s_close_calls == 0, "TC-TIMESYNC-023b 非 TIME 前缀也不关闭连接"); /* 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 对端关闭(EOF)立即退出不解析"); CHECK(s_close_calls == 0, "TC-TIMESYNC-024b EOF 也不关闭连接(net 层 auto_relisten 收口监听)"); /* 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 真错误立即退出不解析"); /* 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 不足 8 字节不解析"); CHECK(s_recv_calls == 2, "TC-TIMESYNC-026b 继续等待后续字节"); /* 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 预算耗尽后不再读(只读 1 次)"); CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-027b 留痕 no TIME packet in 30s, drop conn"); CHECK(s_close_calls == 0, "TC-TIMESYNC-027c 预算耗尽也不关闭连接"); CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d 预算耗尽主动拆会话(E-02 收口:net_drop_conn 拆连接保监听)"); /* 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 两次等待后结束"); CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 20U), "TC-TIMESYNC-028b 第二次片长 = 剩余 20ms"); /* 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 32 位 tick 回绕后预算计算仍正确"); } /* ===================== 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_wd_bind 一次)"); CHECK(s_evt_calls == 0, "TC-TIMESYNC-040b sys_evtHandle==NULL 时跳过就绪屏障"); CHECK(s_accept_calls == 3, "TC-TIMESYNC-040c accept 轮询 3 次(2 次无连接 + 1 次成功)"); CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 9u), "TC-TIMESYNC-040d 收到时间包并更新墙钟"); CHECK(s_close_calls == 1, "TC-TIMESYNC-040e 连接被关闭一次"); CHECK(s_delay_calls == 9, "TC-TIMESYNC-040f 延时片数 = 4+4+1(200+200+50ms)"); CHECK(delays_all(50U, 9), "TC-TIMESYNC-040g 所有延时均按 50ms 分片"); CHECK(s_beat_calls == 13, "TC-TIMESYNC-040h 心跳次数 = 主循环1 + 退避8 + 收连接1 + 收尾1 + 次轮1"); CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-040i 所有心跳均打 TIMESYNC 位"); /* 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 就绪屏障轮询 3 次(2 次未就绪 + 1 次就绪)"); CHECK(s_evt_last_timeout == 100U, "TC-TIMESYNC-041b 屏障等待按 100ms 分片"); CHECK(s_evt_last_flags == SYS_EVT_NET_READY, "TC-TIMESYNC-041c 等待 NET_READY 位"); CHECK(s_evt_last_options == (osFlagsWaitAny | osFlagsNoClear), "TC-TIMESYNC-041d 就绪位不被消费"); CHECK(s_beat_calls == 3, "TC-TIMESYNC-041e 未就绪的两次各喂一次心跳(+主循环 1 次)"); CHECK(s_wd_bind_calls == 1, "TC-TIMESYNC-041f 仍先登记心跳路由"); /* 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 屏障等待 4 次均未就绪(循环未挂死)"); CHECK(s_beat_calls == 3, "TC-TIMESYNC-042b 每次未就绪后各喂一次心跳(第 4 次跳出前)"); /* 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 代次变化时关闭旧监听"); /* INFO 三条:两次 open_listener 成功各一条 "TCP listen on" + 一条 "network re-init, relisten" */ CHECK(g_tstest_info_count == 3, "TC-TIMESYNC-043b 留痕 network re-init, relisten(另含 2 条 listen 成功)"); CHECK(s_accept_calls == 2, "TC-TIMESYNC-043c 每次循环只轮询一次 accept 即放弃"); CHECK((s_socket_calls == 2) && (s_bind_calls == 2) && (s_listen_calls == 2), "TC-TIMESYNC-043d 第二次循环重建了监听"); /* 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 内部重试 5 次后放弃"); CHECK(g_tstest_err_count == 6, "TC-TIMESYNC-044b 5 条 net_socket FAIL + 1 条 retry later"); CHECK(s_delay_calls == 90, "TC-TIMESYNC-044c 退避 5×500ms + 2000ms = 90 片"); CHECK(s_beat_calls == 97, "TC-TIMESYNC-044d 心跳次数 = 2 + 5 + 50 + 40"); CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-044e 所有心跳均打 TIMESYNC 位"); } /* ===================== 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("\n=== Time Sync Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }