From 59e6d772247ea01e3fe4348baf7574de5471b517 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 23 Oct 2026 22:50:36 +0800 Subject: [PATCH] =?UTF-8?q?test(NET):=20net=5Fevents=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=8834=20=E6=9D=A1=EF=BC=8C=E8=A6=86=E7=9B=96=2097.8%?= =?UTF-8?q?=EF=BC=89=EF=BC=8CNET=20=E4=B8=89=E5=9D=97=E6=94=B6=E5=AE=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 假 CH395F 的第三批、也是 NET 域最后一块。net_events.c 覆盖 175/179 = 97.8%。共享 假芯片本批补齐**对端六元组**(get_remot_ipp 的 6 字节)与**链路变更计数**( link_change_count / link_up_cached 改为可预置,原来硬编码 0/1)。 钉住的契约(都是真机上会发生、却不能按需复现的那类): - 对端六元组解析:remote_ip 是**低字节在前**拼的(192.168.1.50 -> 0x3201A8C0,与 网络序相反)。钉住它是防止有人"顺手改成大端",让所有按 IP 比对/白名单静默错位。 - 陈旧中断防误判:芯片说 TCP 还 ESTABLISHED 时,迟到的 DISCONNECT 一律忽略并报 ERROR(拆活会话的代价是 104 主站看到莫名掉线);一条数据都没收就报断且芯片接收区 还有字节,判为疑似误报、只置 lost_conn_seen 延后交给对账器。 - 正常断开:优雅级沉降 + session_dead + last_peer_port 记旧端口 + 转 CLOSED + 发事件。 - 超时:ESTABLISHED 走隔离级沉降并置 released_abnormal —— 这正是 net_pending 收尾时 把挂起 recv 报成 CONNRESET 而不是 0 的那条输入(TC-NETPEND-014),**跨文件因果** 只有两边都能离线跑才可测。 - 丢中断自愈:芯片已连上而软件还写着 LISTENING 时补一次 accept;若软件已 CLOSED 则 只留痕不凭空造会话(TC-NETEV-042b 记现状)。 - 对账器:100ms 周期闸门(同一时刻不重复查芯片);链路计数**基线那趟不打日志** (0xFFFF 哨兵,否则每次上电都冒一条假"link up")、变化即留痕;五种不一致形态的 稳定计数分三档(会话死亡要连观 4 次、芯片偷重开监听 2 次、丢 CONNECT 自接 1 次); 动作后 1000ms 冷却内不重复折腾;U11 重同步(软件 CLOSED 而芯片在 LISTEN -> 直接跟 芯片,不做多余重建);不在用的槽位直接跳过。 三条初判失败的复盘(都是**我的期望值/场景写错**,实现是对的):① remote_ip 字节序 我按网络序写了期望;② 自愈场景我用了 CLOSED 态,而 accept 分支只认 LISTENING/ CONNECTING,改场景并补一条现状断言;③ 链路"基线不打日志"我测在非基线场景(s_ link_last 是函数内 static,跨场景本就延续)。对账器的 s_last/s_bad[]/s_cool[] 都是 函数内 static(外部够不着),因此每个形态**各占一个 fd**、时基单调前进,互不串扰。 覆盖率与台账:套件 24 → 25 全绿;files_tested 28 → 29;未触及 .c 38 → 37;分母 3,766 → 3,945 行;line=3472/3945(**88.0%**,连涨两批)、func=263/290(90.7%)。 恒等式 37 = 3(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETEV(游标 058), 族数 18 → 19。 自查:test-cppcheck Passed(含补齐后的假芯片);check_ids --strict 通过;doc-links 35 处(= 存量);16 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后 统一 --write 回填)与 ui-golden(simulator/tests/ref/*.bin 不入库,新 worktree 没有 黄金参考)三条失败,与本分支改动无关;提交按并行分支约定走 --no-verify。 --- docs/测试说明/主机单测覆盖率口径.md | 7 +- scripts/id_registry.json | 22 + scripts/test_coverage_baseline.json | 40 +- test/host/common/ch395f_fake.c | 29 +- test/host/common/ch395f_fake.h | 7 +- test/host/net_events/net_events_test.c | 530 +++++++++++++++++++ test/host/net_events/run_unit_test.py | 60 +++ test/host/net_events/stub/FreeRTOS.h | 25 + test/host/net_events/stub/ch395f.h | 10 + test/host/net_events/stub/cmsis_os.h | 10 + test/host/net_events/stub/dbg_log.h | 20 + test/host/net_events/stub/main.h | 11 + test/host/net_events/stub/net_latency_test.h | 9 + test/host/net_events/stub/net_socket.h | 19 + test/host/net_events/stub/task.h | 15 + test/host/net_events/stub/test_config.h | 10 + 16 files changed, 801 insertions(+), 23 deletions(-) create mode 100644 test/host/net_events/net_events_test.c create mode 100644 test/host/net_events/run_unit_test.py create mode 100644 test/host/net_events/stub/FreeRTOS.h create mode 100644 test/host/net_events/stub/ch395f.h create mode 100644 test/host/net_events/stub/cmsis_os.h create mode 100644 test/host/net_events/stub/dbg_log.h create mode 100644 test/host/net_events/stub/main.h create mode 100644 test/host/net_events/stub/net_latency_test.h create mode 100644 test/host/net_events/stub/net_socket.h create mode 100644 test/host/net_events/stub/task.h create mode 100644 test/host/net_events/stub/test_config.h diff --git a/docs/测试说明/主机单测覆盖率口径.md b/docs/测试说明/主机单测覆盖率口径.md index 42f02b3d..76197ae3 100644 --- a/docs/测试说明/主机单测覆盖率口径.md +++ b/docs/测试说明/主机单测覆盖率口径.md @@ -237,9 +237,10 @@ 收尾四条分支(不丢尾包 / 干净报 0 / 异常释放报 CONNRESET / 槽位没了报 CONNRESET)、 发送分块钳在 `NET_SEND_CHUNK_MAX=1024`(真值,非假件自造)、`deadline` 每轮续期 (长文件不被 1000ms 掐死)、停滞超时、取消与 `clear` 不发通知的纪律 | -| NET 最后一块(`net_events.c`) | ⬜ 未开始 | 假芯片与假件已就绪;它依赖最多 - (`net_sock_raw`/`net_listener_settle`/`net_pending_cancel`/`xTaskGetTickCount`) | -| 104 服务层 | ⬜ 未开始 | — | 桩最省(复用 `cmsis_os2.h` + `net_socket.h`) | +| NET 三块之三:`net_events.c` | ✅ 完成 | `test/host/net_events`(34 条) | 陈旧中断防误判 | + (芯片说还在连接就不许拆活会话)、疑似误报延后、超时标异常释放(正是 `net_pending` + 收尾报 CONNRESET 的那条输入,**跨文件因果两头都能离线跑才测得到**)、丢中断自愈, + 以及对账器的 100ms 周期闸门 / 稳定计数 4·2·1 三档 / 1000ms 冷却 / U11 重同步 | 本批照出的两处**现状**(只登记、**未改语义**;全库唯一调用点 `test/board/net_test_task.c` 传的是 NULL 与非零 tv,今天没有受害者): diff --git a/scripts/id_registry.json b/scripts/id_registry.json index 020777ec..d5c63445 100644 --- a/scripts/id_registry.json +++ b/scripts/id_registry.json @@ -1790,6 +1790,28 @@ "subitem_ok": true, "owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);2026-10-23 随覆盖率口径 §9 假 CH395F 第二批补登记", "cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌" + }, + { + "name": "TC-HOST-NETEV", + "class": "TC", + "title": "主机侧单测用例号:NET 层事件分发与周期对账(test/host/net_events)", + "pattern": "\\bTC-NETEV-[0-9]{1,4}[a-z]?\\b", + "digit_width": [ + 2, + 4 + ], + "owner_docs_glob": [ + "test/host/net_events/*" + ], + "scope": "global", + "frozen": false, + "cursor": "TC-NETEV-058", + "cursor_owner_only": true, + "check_dangling": true, + "allow_orphans": true, + "subitem_ok": true, + "owner_note": "B 层口径(2026-10-21 批 3 定:这些号在代码目录外零消费者,属主登记处 = 单测代码本身,不逐号补文档);2026-10-23 随覆盖率口径 §9 假 CH395F 第三批补登记", + "cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌" } ], "unmanaged_bare": { diff --git a/scripts/test_coverage_baseline.json b/scripts/test_coverage_baseline.json index 563836d2..4efe76e4 100644 --- a/scripts/test_coverage_baseline.json +++ b/scripts/test_coverage_baseline.json @@ -184,6 +184,14 @@ "lines_covered": 7, "lines_valid": 7 }, + "Drivers/BSP/NET/net_events.c": { + "func_rate": 1.0, + "funcs_covered": 8, + "funcs_valid": 8, + "line_rate": 0.9777, + "lines_covered": 175, + "lines_valid": 179 + }, "Drivers/BSP/NET/net_listener.c": { "func_rate": 1.0, "funcs_covered": 7, @@ -308,6 +316,13 @@ "lines_total": 112, "ok": true }, + "net_events": { + "files": { + "Drivers/BSP/NET/net_events.c": 179 + }, + "lines_total": 179, + "ok": true + }, "net_listener": { "files": { "Drivers/BSP/NET/net_listener.c": 126 @@ -440,6 +455,7 @@ "Drivers/BSP/NET/lftpd/lftpd_auth.c", "Drivers/BSP/NET/lftpd/lftpd_io.c", "Drivers/BSP/NET/lftpd/lftpd_string.c", + "Drivers/BSP/NET/net_events.c", "Drivers/BSP/NET/net_listener.c", "Drivers/BSP/NET/net_pending.c", "Drivers/BSP/NET/net_select.c", @@ -448,16 +464,16 @@ ], "totals": { "files_generated_excluded": 5, - "files_tested": 28, - "files_untested": 116, - "func_rate": 0.9043, - "funcs_covered": 255, - "funcs_valid": 282, - "line_rate": 0.8755, - "lines_covered": 3297, - "lines_valid": 3766, - "modules_ok": 24, - "modules_total": 24 + "files_tested": 29, + "files_untested": 115, + "func_rate": 0.9069, + "funcs_covered": 263, + "funcs_valid": 290, + "line_rate": 0.8801, + "lines_covered": 3472, + "lines_valid": 3945, + "modules_ok": 25, + "modules_total": 25 }, "untested_sources": [ { @@ -504,10 +520,6 @@ "code_lines": 264, "path": "Drivers/BSP/SD2506/sd2506.c" }, - { - "code_lines": 263, - "path": "Drivers/BSP/NET/net_events.c" - }, { "code_lines": 206, "path": "Drivers/BSP/CH395F/ch395f.h" diff --git a/test/host/common/ch395f_fake.c b/test/host/common/ch395f_fake.c index 11a03613..717b490f 100644 --- a/test/host/common/ch395f_fake.c +++ b/test/host/common/ch395f_fake.c @@ -31,6 +31,7 @@ void ch395f_fake_reset(void) g_fake.cached_tcp[i] = CH395F_TCP_CLOSED; g_fake.status_byte0[i] = CH395F_SOCKET_CLOSED; g_fake.status_byte1[i] = CH395F_TCP_CLOSED; + g_fake.link_up = 1U; g_fake.open_rc[i] = CH395F_ERR_SUCCESS; g_fake.listen_rc[i] = CH395F_ERR_SUCCESS; } @@ -44,6 +45,20 @@ void ch395f_fake_push_recv_len(uint8_t sock, uint16_t v) } } +void ch395f_fake_set_peer(uint8_t sock, uint8_t b0, uint8_t b1, uint8_t b2, + uint8_t b3, uint16_t peer_port) +{ + if (sock >= CH395F_FAKE_SOCK_MAX) { + return; + } + g_fake.remote[sock][0] = b0; + g_fake.remote[sock][1] = b1; + g_fake.remote[sock][2] = b2; + g_fake.remote[sock][3] = b3; + g_fake.remote[sock][4] = (uint8_t)(peer_port & 0xFFU); + g_fake.remote[sock][5] = (uint8_t)((peer_port >> 8) & 0xFFU); +} + void ch395f_fake_set_listening(uint8_t sock) { if (sock < CH395F_FAKE_SOCK_MAX) { @@ -174,9 +189,13 @@ void ch395f_set_sour_port(uint8_t sock, uint16_t port) void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { - (void)sock; - if (p_buf != NULL) { - (void)memset(p_buf, 0, 4U); + if (p_buf == NULL) { + return; + } + if (sock < CH395F_FAKE_SOCK_MAX) { + (void)memcpy(p_buf, g_fake.remote[sock], 6U); + } else { + (void)memset(p_buf, 0, 6U); } } @@ -189,10 +208,10 @@ void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) uint8_t ch395f_link_up_cached(void) { - return 1U; + return g_fake.link_up; } uint16_t ch395f_link_change_count(void) { - return 0U; + return g_fake.link_change; } diff --git a/test/host/common/ch395f_fake.h b/test/host/common/ch395f_fake.h index cfd0a516..6453b169 100644 --- a/test/host/common/ch395f_fake.h +++ b/test/host/common/ch395f_fake.h @@ -82,6 +82,9 @@ typedef struct { uint16_t recv_len_q[CH395F_FAKE_SOCK_MAX][CH395F_FAKE_QUEUE_LEN]; int recv_len_n[CH395F_FAKE_SOCK_MAX]; int recv_len_i[CH395F_FAKE_SOCK_MAX]; + uint8_t remote[CH395F_FAKE_SOCK_MAX][6]; /* get_remot_ipp 的 6 字节:ip[4]+port(小端) */ + uint16_t link_change; /* link_change_count(链路变更计数) */ + uint8_t link_up; /* link_up_cached */ uint8_t open_rc[CH395F_FAKE_SOCK_MAX]; /* open_socket 返回码 */ uint8_t listen_rc[CH395F_FAKE_SOCK_MAX]; /* tcp_listen 返回码 */ } ch395f_fake_world_t; @@ -118,7 +121,9 @@ extern ch395f_fake_obs_t g_chip; void ch395f_fake_reset(void); /* 全清并把所有 socket 设为"已关闭" */ void ch395f_fake_push_recv_len(uint8_t sock, uint16_t v); -void ch395f_fake_set_listening(uint8_t sock); /* 缓存:open!=CLOSED 且 tcp=LISTEN */ +void ch395f_fake_set_listening(uint8_t sock); +void ch395f_fake_set_peer(uint8_t sock, uint8_t b0, uint8_t b1, uint8_t b2, + uint8_t b3, uint16_t peer_port); /* 预置对端六元组 */ /* 缓存:open!=CLOSED 且 tcp=LISTEN */ /* —— 假芯片实现(签名与真实 ch395f.h 一字不改)—— */ ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn); diff --git a/test/host/net_events/net_events_test.c b/test/host/net_events/net_events_test.c new file mode 100644 index 00000000..48231fa3 --- /dev/null +++ b/test/host/net_events/net_events_test.c @@ -0,0 +1,530 @@ +/* + * 模块名称:NET 层事件分发与周期对账(net_events)主机侧单元测试 + * 模块功能:在 PC 上离线验证 Drivers/BSP/NET/net_events.c 的五个事件入口与周期对账器: + * 对端六元组解析、**陈旧中断防误判**(芯片说还在连接就不许拆会话)、疑似 + * 误报延后交给对账器、超时把会话标成异常释放(喂给 net_pending 收尾报 + * CONNRESET 的那条输入)、掉线自愈自接,以及对账器的 100ms 周期闸门、 + * 1000ms 冷却、五种不一致形态的稳定计数(死亡要连观 4 次、重开监听 2 次、 + * 自接 1 次)与 U11 的 CLOSED->LISTENING 重同步。 + * + * 被测对象:真实源文件 ../../../Drivers/BSP/NET/net_events.c(#include 原样纳入)。 + * net_events.h / net_types.h / net_pending.h / net_listener.h 全用**真实头** + * (net_event_cb_t 回调签名、NET_EVENT_* / NET_SOCK_STATE_* / + * net_listener_settle_t / NET_LISTENER_REQ_* 一改即编译期报错)。假件: + * 共享假 CH395F(本批补齐对端六元组与链路变更计数)、可控 socket 表 + * (net_sock_raw 对未在用槽位也返回项,与真实语义一致)、**可观测的事件 + * 回调**(记 fd / 事件 / arg)、记录型 net_listener_settle/request 与 + * net_pending_cancel / net_send_pending_cancel、可控时基、dbg_log 计数。 + * + * 为什么要主机侧单测(而不是只靠台架): + * 1. CH395F 的中断是**整体读即清**且单连接 socket 复用同一个号,"迟到的 DISCONNECT" + * 与"丢了 CONNECT 的已连接"在真机上都会发生,却无法按需复现。误判的代价直接落在 + * 现场设备上:把还活着的会话拆掉(104 主站看到莫名掉线),或让 socket 永远停在 + * 已连接假象里不再重开。 + * 2. 对账器的**稳定计数**(死亡 4 次、重开监听 2 次、自接 1 次)本质是"别拿一次 + * 采样就把连接折腾死"。真机一次采样 100ms,凑够 4 次要 400ms,且必须一路稳定; + * 台架上没法按毫秒喂,也看不出为什么"偶尔不重开"。主机侧把时基做成显式量后, + * 第 3 次不该动、第 4 次该动、之后 1000ms 内不许再动,全部可精确断言。 + * 3. handle_timeout_event 会置 released_abnormal=1,而 net_pending 收尾时正是靠 + * 这个标志把挂起 recv 报成 CONNRESET 而不是 0(见 test/host/net_pending 的 + * TC-NETPEND-014)。两个模块之间这条**跨文件因果**,只有两边都能离线跑时才可测。 + * + * 覆盖点:TC-NETEV-001..006 CONNECT;010..014 DISCONNECT(含两条陈旧中断防误判); + * 020/021 RECV 与 SEND;030..032 TIMEOUT;040..042 掉线自愈;050..058 周期对账 + * (周期闸门、链路变更留痕、五种不一致形态、稳定计数、冷却、in_use 兜底)。 + * + * 通过:全部 TC PASS(main 返回 0)。 + * 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。 + * + * 创建日期:2026-10-23 + */ + +#include +#include + +#include "net_events.h" +#include "net_listener.h" +#include "net_pending.h" +#include "net_socket.h" +#include "ch395f.h" + +#include "ch395f_fake.c" +#include "net_events.c" + +static int s_total; +static int s_passed; +static int s_failed; + +#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) + +/* ===================== dbg_log 计数 ===================== */ + +int g_nev_info_count; +int g_nev_error_count; +int g_nev_debug_count; + +/* ===================== 可观测的事件回调 ===================== */ + +static int g_ev_n; +static int g_ev_fd[16]; +static int g_ev_kind[16]; +static void *g_ev_arg[16]; +static int g_ev_token; + +static void ev_cb(int sockfd, net_event_t event, void *arg) +{ + if (g_ev_n < (int)(sizeof(g_ev_fd) / sizeof(g_ev_fd[0]))) { + g_ev_fd[g_ev_n] = sockfd; + g_ev_kind[g_ev_n] = (int)event; + g_ev_arg[g_ev_n] = arg; + } + g_ev_n++; +} + +/* ===================== 记录型监听与挂起取消 ===================== */ + +static int g_settle_n; +static int g_settle_sock; +static int g_settle_level; +static int g_req_n; +static int g_req_sock; +static int g_req_reason; +static int g_pcan_n; +static int g_scan_n; +static int g_pcan_err; + +void net_listener_settle(int sockfd, net_listener_settle_t level) +{ + g_settle_n++; + g_settle_sock = sockfd; + g_settle_level = (int)level; +} + +void net_listener_request(int sockfd, net_listener_req_t reason) +{ + g_req_n++; + g_req_sock = sockfd; + g_req_reason = (int)reason; +} + +void net_listener_clear(int sockfd) { (void)sockfd; } +void net_listener_tick(void) { } + +void net_pending_cancel(int sockfd, int err) +{ + (void)sockfd; + g_pcan_n++; + g_pcan_err = err; +} + +void net_send_pending_cancel(int sockfd, int err) +{ + (void)sockfd; + (void)err; + g_scan_n++; +} + +/* ===================== 可控时基、errno 与 socket 表 ===================== */ + +static uint32_t g_now; +static int g_errno_last; + +uint32_t HAL_GetTick(void) { return g_now; } + +int net_set_errno(int err) { g_errno_last = err; return err; } + +uint32_t xTaskGetTickCount(void) { return g_now; } + +static net_sock_t g_tbl[NET_MAX_SOCKETS]; + +net_sock_t *net_sock_raw(int sockfd) +{ + if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) { + return NULL; + } + /* 与真实语义一致:对账循环必须能读到 in_use == 0 的槽位,所以这里不给 NULL */ + return &g_tbl[sockfd]; +} + +net_sock_t *net_get_sock(int sockfd) +{ + net_sock_t *p = net_sock_raw(sockfd); + + if ((p == NULL) || (p->in_use == 0u)) { + return NULL; + } + return p; +} + +static void reset_all(void) +{ + int i; + + (void)memset(g_tbl, 0, sizeof(g_tbl)); + g_now = 1000u; + g_errno_last = 0; + g_ev_n = 0; + g_settle_n = 0; + g_settle_sock = -1; + g_settle_level = -1; + g_req_n = 0; + g_req_sock = -1; + g_req_reason = -1; + g_pcan_n = 0; + g_scan_n = 0; + g_pcan_err = 0; + g_nev_info_count = 0; + g_nev_error_count = 0; + g_nev_debug_count = 0; + ch395f_fake_reset(); + (void)i; +} + +/* 一个"带应用回调的在用槽位",ch395_sock 与数组下标同号 */ +static net_sock_t *mk(int fd, net_sock_state_t st, uint16_t local_port) +{ + net_sock_t *p = &g_tbl[fd]; + + p->in_use = 1u; + p->ch395_sock = (uint8_t)fd; + p->type = NET_SOCK_STREAM; + p->state = st; + p->auto_relisten = 1u; + p->local_port = local_port; + p->event_cb = ev_cb; + p->event_arg = (void *)&g_ev_token; + return p; +} + +/* ===================== CONNECT ===================== */ + +static void tc_connect(void) +{ + net_sock_t *p; + + printf("-- TC-NETEV-001..006 CONNECT --\n"); + reset_all(); + p = mk(0, NET_SOCK_STATE_LISTENING, 2404u); + ch395f_fake_set_peer(0, 192u, 168u, 1u, 50u, 51234u); + handle_connect_event(p); + /* remote_ip 是**低字节在前**拼的(remote[0] 落在最低 8 位),与网络序相反: + * 192.168.1.50 -> 0x3201A8C0。这是既有实现的字节序,钉住它是防止有人"顺手 + * 改成大端"导致所有按 IP 比对/白名单的判断静默错位。 */ + CHECK((p->remote_ip_arr[0] == 192u) && (p->remote_ip_arr[3] == 50u) && + (p->remote_ip == ((192u) | (168u << 8) | (1u << 16) | (50u << 24))) && + (p->remote_port == 51234u), + "TC-NETEV-001 对端六元组解析:ip 低字节在前拼 32 位、端口取第 5/6 字节"); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->session_dead == 0u) && + (p->lost_conn_seen == 0u) && (p->send_ready == 1u), + "TC-NETEV-002 监听口自接后转已连接并立刻可写"); + CHECK((g_chip.keepalive_calls == 1) && (g_chip.last_keepalive == 1u) && + (g_chip.int_status_calls == 1), + "TC-NETEV-003 建链即开 keepalive 并清一次挂起位(读即清)"); + CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) && + (g_ev_fd[0] == 0) && (g_ev_arg[0] == (void *)&g_ev_token), + "TC-NETEV-004 事件带 fd 与调用方 arg 回调给应用"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CONNECTING, 0u); + ch395f_fake_set_peer(0, 10u, 0u, 0u, 1u, 2404u); + handle_connect_event(p); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->send_ready == 0u) && + (g_chip.keepalive_calls == 1) && (g_ev_n == 1), + "TC-NETEV-005 主动连接分支同样转已连接,但**不动 send_ready**(与自接有别)"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CLOSED, 2404u); + handle_connect_event(p); + CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED), + "TC-NETEV-006 其它状态收到 CONNECT 中断一律忽略(不许凭空造会话)"); +} + +/* ===================== DISCONNECT ===================== */ + +static void tc_disconnect(void) +{ + net_sock_t *p; + + printf("-- TC-NETEV-010..014 DISCONNECT --\n"); + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; + g_fake.status_byte1[0] = CH395F_TCP_ESTABLISHED; + handle_disconnect_event(p); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) && + (g_nev_error_count == 1), + "TC-NETEV-010 芯片说还在连接:忽略这个陈旧 DISCONNECT(不许拆活会话)"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; + g_fake.status_byte1[0] = CH395F_TCP_CLOSED; + ch395f_fake_push_recv_len(0, 9u); /* 还有 9 字节没读走 */ + handle_disconnect_event(p); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->lost_conn_seen == 1u) && + (g_ev_n == 0) && (g_settle_n == 0), + "TC-NETEV-011 疑似误报(一条数据都没收就断):延后交给对账器,不立即拆"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + p->remote_port = 51234u; + g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; + g_fake.status_byte1[0] = CH395F_TCP_CLOSED; + handle_disconnect_event(p); + CHECK((g_settle_n == 1) && (g_settle_level == (int)NET_LISTENER_SETTLE_GRACEFUL) && + (p->session_dead == 1u) && (p->released_abnormal == 0u) && + (p->last_peer_port == 51234u) && + (p->state == NET_SOCK_STATE_CLOSED) && + (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED), + "TC-NETEV-012 正常断开:优雅沉降 + 会话标死 + 记旧端口 + 发事件"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_LISTENING, 2404u); + g_fake.status_byte0[0] = CH395F_SOCKET_OPEN; + g_fake.status_byte1[0] = CH395F_TCP_LISTEN; + handle_disconnect_event(p); + CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) && + (g_nev_info_count == 1) && (g_nev_error_count == 0), + "TC-NETEV-013 监听还在:陈旧监听 DISCONNECT 只留痕,不报 ERROR"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_LISTENING, 2404u); + g_fake.status_byte1[0] = CH395F_TCP_CLOSED; /* 芯片说不在 LISTEN */ + handle_disconnect_event(p); + CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) && (g_req_n == 0), + "TC-NETEV-013b 监听丢了也只留痕:重开交给对账器,事件层不越权"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CONNECTING, 0u); + handle_disconnect_event(p); + CHECK((g_nev_error_count == 1) && (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED), + "TC-NETEV-014 非常规状态收到 DISCONNECT:报 ERROR 但仍要通知应用"); +} + +/* ===================== RECV / SEND / TIMEOUT ===================== */ + +static void tc_recv_send_timeout(void) +{ + net_sock_t *p; + + printf("-- TC-NETEV-020/021/030..032 --\n"); + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + handle_recv_event(p); + CHECK((p->readable == 1u) && (g_ev_kind[0] == (int)NET_EVENT_DATA_RECEIVED), + "TC-NETEV-020 RECV 事件置可读并通知应用"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + p->send_ready = 1u; + handle_send_event(p); + CHECK((g_ev_kind[0] == (int)NET_EVENT_SEND_COMPLETE) && (p->send_ready == 1u), + "TC-NETEV-021 SEND 完成只发事件,字段留给写路径自己管"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CONNECTING, 0u); + handle_timeout_event(p); + CHECK((p->state == NET_SOCK_STATE_CREATED) && (g_nev_error_count == 1) && + (g_ev_kind[0] == (int)NET_EVENT_TIMEOUT), + "TC-NETEV-030 连接超时:退回 CREATED 并通知应用"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u); + p->remote_port = 777u; + handle_timeout_event(p); + CHECK((g_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE) && + (p->released_abnormal == 1u) && (p->session_dead == 1u) && + (p->last_peer_port == 777u) && (p->state == NET_SOCK_STATE_CLOSED), + "TC-NETEV-031 会话超时:隔离级沉降 + 标异常释放(net_pending 收尾据此报 CONNRESET)"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_LISTENING, 2404u); + handle_timeout_event(p); + CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_nev_error_count == 1) && + (g_ev_kind[0] == (int)NET_EVENT_TIMEOUT), + "TC-NETEV-032 监听超时只留痕:不改状态,但仍要通知等待方"); +} + +/* ===================== 掉线自愈 ===================== */ + +static void tc_lost_probe(void) +{ + net_sock_t *p; + + printf("-- TC-NETEV-040..042 lost-CONNECT probe --\n"); + reset_all(); + p = mk(0, NET_SOCK_STATE_CLOSED, 2404u); + p->lost_conn_seen = 1u; + g_fake.cached_tcp[0] = CH395F_TCP_CLOSED; + net_lost_connect_probe(p); + CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0), + "TC-NETEV-040 芯片不在已连接态:只清标记,不自作主张建会话"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CLOSED, 2404u); + p->lost_conn_seen = 1u; + p->last_peer_port = 51234u; + g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED; + ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 51234u); + net_lost_connect_probe(p); + CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0), + "TC-NETEV-041 对端口与上次相同:认定是自己没看到 CONNECT,不自接第二遍"); + + /* 自愈的适用面是软件还写着 LISTENING、芯片其实已经连上——accept 分支只认 + * LISTENING/CONNECTING。若软件态已被别处改成 CLOSED,本函数打完 INFO 后什么都 + * 不会发生(现状留痕,见第二断言)。 */ + reset_all(); + p = mk(0, NET_SOCK_STATE_LISTENING, 2404u); + p->lost_conn_seen = 1u; + p->last_peer_port = 1u; + g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED; + ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u); + net_lost_connect_probe(p); + CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) && + (p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 55555u) && + (p->lost_conn_seen == 0u), + "TC-NETEV-042 丢了 CONNECT 中断时自愈:补一次 accept,会话不至于永久失联"); + + reset_all(); + p = mk(0, NET_SOCK_STATE_CLOSED, 2404u); + g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED; + ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u); + net_lost_connect_probe(p); + CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED), + "TC-NETEV-042b 现状:软件已 CLOSED 时自愈接不上(只留痕,不凭空造会话)"); +} + +/* ===================== 周期对账 ===================== */ + +static void recon_step(uint32_t now) +{ + g_now = now; + net_reconcile_tick(); +} + +static void tc_reconcile(void) +{ + net_sock_t *p; + int infos; + int c; + + printf("-- TC-NETEV-050..058 reconcile --\n"); + /* 对账器里的 s_last / s_bad[] / s_cool[] 是函数内 static(外部够不着), + * 所以每个形态**各占一个 fd**、时间统一单调前进,互不串扰。 */ + reset_all(); + infos = g_nev_info_count; + recon_step(1000u); /* priming:链路计数记基线 */ + CHECK(g_nev_info_count == infos, + "TC-NETEV-051a 上电第一趟不因链路计数打日志(0xFFFF 哨兵只记基线)"); + c = g_chip.cached_calls; + recon_step(1000u); /* 同一时刻再调一次 */ + CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms 周期闸门:同一时刻不重复查芯片"); + + reset_all(); + p = mk(1, NET_SOCK_STATE_ESTABLISHED, 2404u); + g_fake.cached_open[1] = CH395F_SOCKET_CLOSED; + g_fake.cached_tcp[1] = CH395F_TCP_CLOSED; + infos = g_nev_info_count; + recon_step(1100u); + recon_step(1200u); + recon_step(1300u); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) && + (g_nev_info_count == infos), + "TC-NETEV-052 会话死亡要**连观 4 次**才动手:前三次保持原状"); + recon_step(1400u); + CHECK((p->state == NET_SOCK_STATE_CLOSED) && (p->session_dead == 1u) && + (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED), + "TC-NETEV-052b 第 4 次稳定后走断开收尾"); + p->state = NET_SOCK_STATE_ESTABLISHED; /* 假装又活了,验证冷却 */ + recon_step(1500u); + recon_step(1600u); + recon_step(1700u); + recon_step(1800u); + CHECK(g_ev_n == 1, "TC-NETEV-052c 动作后 1000ms 冷却内不再重复折腾(哪怕稳定计数够了)"); + + reset_all(); + p = mk(2, NET_SOCK_STATE_ESTABLISHED, 2404u); + g_fake.cached_open[2] = CH395F_SOCKET_OPEN; + g_fake.cached_tcp[2] = CH395F_TCP_LISTEN; /* 芯片自己重开监听了,却没发 DISCONNECT */ + recon_step(2900u); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_pcan_n == 0), + "TC-NETEV-053 芯片偷偷重开监听:需要 2 次稳定,第一次只累计"); + recon_step(3000u); + CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_pcan_n == 1) && + (g_scan_n == 1) && (g_pcan_err == NET_ERR_CONNRESET), + "TC-NETEV-053b 第 2 次稳定:重同步成监听并**取消两条挂起**(否则任务永远等不到回)"); + + reset_all(); + p = mk(3, NET_SOCK_STATE_LISTENING, 2404u); + p->last_peer_port = 1u; + g_fake.cached_tcp[3] = CH395F_TCP_ESTABLISHED; /* 连上了却没 CONNECT 中断 */ + ch395f_fake_set_peer(3, 172u, 16u, 0u, 5u, 40000u); + recon_step(4100u); + CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 40000u) && + (g_ev_kind[0] == (int)NET_EVENT_CONNECTED), + "TC-NETEV-054 连接完成但中断丢了:1 次即补 accept"); + + reset_all(); + p = mk(4, NET_SOCK_STATE_LISTENING, 2404u); + g_fake.cached_open[4] = CH395F_SOCKET_CLOSED; /* 监听没了 */ + g_fake.cached_tcp[4] = CH395F_TCP_CLOSED; + recon_step(5200u); + recon_step(5300u); + CHECK((g_req_n == 1) && (g_req_sock == 4) && + (g_req_reason == (int)NET_LISTENER_REQ_RECON), + "TC-NETEV-055 监听丢了:2 次稳定后请求重开监听(不自已在事件层里开)"); + + reset_all(); + p = mk(5, NET_SOCK_STATE_CLOSED, 2404u); + g_fake.cached_open[5] = CH395F_SOCKET_OPEN; /* 芯片还在 LISTEN */ + g_fake.cached_tcp[5] = CH395F_TCP_LISTEN; + p->remote_port = 999u; + p->lost_conn_seen = 1u; + recon_step(6400u); + CHECK((p->state == NET_SOCK_STATE_LISTENING) && (p->last_peer_port == 999u) && + (p->lost_conn_seen == 0u) && (g_req_n == 0), + "TC-NETEV-056 U11 重同步:软件 CLOSED 而芯片在 LISTEN -> 直接跟芯片,不多余重开"); + + reset_all(); + p = mk(6, NET_SOCK_STATE_CLOSED, 2404u); + g_fake.cached_open[6] = CH395F_SOCKET_CLOSED; + recon_step(7500u); + recon_step(7600u); + CHECK((g_req_n == 1) && (g_req_sock == 6), + "TC-NETEV-057 软件 CLOSED 且芯片也不在监听:请求重开(重建交回监听层)"); + + reset_all(); + (void)memset(&g_tbl[7], 0, sizeof(g_tbl[7])); /* 槽位不在用 */ + infos = g_nev_info_count; + recon_step(8700u); + CHECK(g_nev_info_count == infos, "TC-NETEV-058 不在用的槽位直接跳过(不产生任何动作)"); + + /* 链路日志:s_link_last 初值 0xFFFF 是哨兵——第一次看到计数只记基线、不打日志 + * (否则每次上电都会冒一条"链路 up"的假事件);此后计数一变就必须留痕。 + * 本文件开头那次 priming 就是"基线"那一趟(TC-NETEV-051a),这里是"变化"那一趟。 */ + reset_all(); + g_fake.link_change = 7u; + infos = g_nev_info_count; + recon_step(9800u); + CHECK(g_nev_info_count == (infos + 1), + "TC-NETEV-051b 链路变更计数一变就留痕(基线那趟已由 051a 验证不打)"); +} + +int main(void) +{ + printf("=== NET Events Tests ===\n"); + + tc_connect(); + tc_disconnect(); + tc_recv_send_timeout(); + tc_lost_probe(); + tc_reconcile(); + + printf("\n=== NET Events Tests: %d/%d PASSED (failed=%d) ===\n", + s_passed, s_total, s_failed); + return (s_failed == 0) ? 0 : 1; +} diff --git a/test/host/net_events/run_unit_test.py b/test/host/net_events/run_unit_test.py new file mode 100644 index 00000000..beba0e8c --- /dev/null +++ b/test/host/net_events/run_unit_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""NET 层事件分发与周期对账(net_events)主机侧单元测试运行器(L0.5,纯标准库)。 + +做什么: + 用本机 MSVC 编译 test/host/net_events/net_events_test.c(以 #include 纳入生产源 + Drivers/BSP/NET/net_events.c 与共享假芯片 test/host/common/ch395f_fake.c),在 PC + 上离线验证五个事件入口与周期对账器:对端六元组解析、陈旧中断防误判、疑似误报延后、 + 超时标异常释放、丢中断自愈,以及对账器的周期闸门、稳定计数(4/2/1 三档)、1000ms + 冷却、U11 重同步与五种不一致形态。工具链与判定委托 unit_test_harness.py。 + +为什么要主机侧单测(而不是只用台架): + CH395F 中断整体读即清、单连接 socket 复用同号,"迟到的 DISCONNECT"和"丢了 CONNECT + 的已连接"真机上都会发生却不能按需复现;误判的代价直接落在现场设备上(拆掉活会话, + 或让 socket 永久停在已连接假象里不再重开)。对账器的稳定计数本质是"别拿一次采样 + 就把连接折腾死",100ms 一轮、要连观 4 次才动手,台架无法按毫秒喂;主机侧把时基 + 做成显式量后,第 3 次不该动、第 4 次该动、之后 1000ms 内不许再动都能精确断言。 + 另外 handle_timeout_event 置的 released_abnormal 是 net_pending 收尾报 CONNRESET 的 + 输入(见 test/host/net_pending 的 TC-NETPEND-014),这条跨文件因果只有两边都能 + 离线跑才可测。 + +桩的边界:net_events.h / net_types.h / net_pending.h / net_listener.h 用真实头; + net_socket.h 是桩,但 net_sock_raw 保留"未在用槽位也返回项"的真实语义(对账循环 + 必须能读 in_use==0);ch395f.h 只转发到共享假芯片(本批补齐对端六元组与链路变更 + 计数);事件回调做成可观测假件——应用层能不能收到、收到的是什么事件,是本模块 + 唯一的对外契约。刻意不定义 NET_BENCH_CMD_LOG(压测日志不是被测对象)。 + +用法(仓库根目录): + python test/host/net_events/run_unit_test.py + +退出码:0=PASS / 1=测试失败 / 2=环境错误。 + +设计依据:docs/网络/网络自愈架构设计.md §4.10、docs/网络/Net层阻塞模型架构设计.md、 +docs/测试说明/主机单测覆盖率口径.md §9 +创建日期:2026-10-23 +""" +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE))) +sys.path.insert(0, os.path.join(ROOT, "test", "host", "common")) + +from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402 + +BUILD = TestBuild( + name="NET Events Tests", + src=os.path.join(HERE, "net_events_test.c"), + include_dirs=[ + os.path.join(HERE, "stub"), + os.path.join(ROOT, "test", "host", "common"), + os.path.join(ROOT, "Drivers", "BSP", "NET"), + HERE, + ], + summary_marker="=== NET Events Tests:", +) + +if __name__ == "__main__": + sys.exit(run_host_unit_test(BUILD)) + diff --git a/test/host/net_events/stub/FreeRTOS.h b/test/host/net_events/stub/FreeRTOS.h new file mode 100644 index 00000000..813954d8 --- /dev/null +++ b/test/host/net_events/stub/FreeRTOS.h @@ -0,0 +1,25 @@ +/* + * 模块名称:主机侧单测桩 - FreeRTOS.h(net_events 模块专用) + * 模块功能:宿主等价类型(TaskHandle_t 等),因为真实 net_pending.h 的原型绕不开。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef FREERTOS_H +#define FREERTOS_H + +#include +#include + +typedef void *TaskHandle_t; +typedef long BaseType_t; +typedef unsigned long UBaseType_t; + +typedef enum { + eNoAction = 0, + eSetBits = 1, + eIncrement = 2, + eSetValueWithOverwrite = 3, + eSetValueWithoutOverwrite = 4 +} eNotifyAction; + +#endif /* FREERTOS_H */ diff --git a/test/host/net_events/stub/ch395f.h b/test/host/net_events/stub/ch395f.h new file mode 100644 index 00000000..a6eb26e5 --- /dev/null +++ b/test/host/net_events/stub/ch395f.h @@ -0,0 +1,10 @@ +/* + * 模块名称:主机侧单测桩 - ch395f.h(net_events 模块入口) + * 模块功能:只做转发,真身在 test/host/common/ch395f_fake.h/.c(NET 域共享假芯片)。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef CH395F_H +#define CH395F_H +#include "ch395f_fake.h" +#endif /* CH395F_H */ diff --git a/test/host/net_events/stub/cmsis_os.h b/test/host/net_events/stub/cmsis_os.h new file mode 100644 index 00000000..d9550e64 --- /dev/null +++ b/test/host/net_events/stub/cmsis_os.h @@ -0,0 +1,10 @@ +/* + * 模块名称:主机侧单测桩 - cmsis_os.h(net_events 模块专用) + * 模块功能:空壳;net_events.c include 它但不用 CMSIS API(事件直接同步分发)。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef CMSIS_OS_H +#define CMSIS_OS_H +#include +#endif /* CMSIS_OS_H */ diff --git a/test/host/net_events/stub/dbg_log.h b/test/host/net_events/stub/dbg_log.h new file mode 100644 index 00000000..a7e544d0 --- /dev/null +++ b/test/host/net_events/stub/dbg_log.h @@ -0,0 +1,20 @@ +/* + * 模块名称:主机侧单测桩 - dbg_log.h(net_events 模块专用) + * 模块功能:DBG_* 计数。注意 net_events.c 在 include 本头之前把 DBG_LINE_BUF_MAX + * 抬到 128U(AGENTS:深调用链模块必须抬,否则最长日志行会被截断), + * 本桩不关心缓冲大小,只数条数。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef DBG_LOG_H +#define DBG_LOG_H + +extern int g_nev_info_count; +extern int g_nev_error_count; +extern int g_nev_debug_count; + +#define DBG_INFO(fmt, ...) do { (void)(fmt); g_nev_info_count++; } while (0) +#define DBG_DEBUG(fmt, ...) do { (void)(fmt); g_nev_debug_count++; } while (0) +#define DBG_ERROR(fmt, ...) do { (void)(fmt); g_nev_error_count++; } while (0) + +#endif /* DBG_LOG_H */ diff --git a/test/host/net_events/stub/main.h b/test/host/net_events/stub/main.h new file mode 100644 index 00000000..9791fb66 --- /dev/null +++ b/test/host/net_events/stub/main.h @@ -0,0 +1,11 @@ +/* + * 模块名称:主机侧单测桩 - main.h(net_events 模块专用) + * 模块功能:只给 HAL_GetTick(对账周期 100ms 与冷却 1000ms 两条时间轴的唯一致度源)。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef MAIN_H +#define MAIN_H +#include +uint32_t HAL_GetTick(void); +#endif /* MAIN_H */ diff --git a/test/host/net_events/stub/net_latency_test.h b/test/host/net_events/stub/net_latency_test.h new file mode 100644 index 00000000..a8821ad6 --- /dev/null +++ b/test/host/net_events/stub/net_latency_test.h @@ -0,0 +1,9 @@ +/* + * 模块名称:主机侧单测桩 - net_latency_test.h(net_events 模块专用) + * 模块功能:空壳。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef NET_LATENCY_TEST_H +#define NET_LATENCY_TEST_H +#endif /* NET_LATENCY_TEST_H */ diff --git a/test/host/net_events/stub/net_socket.h b/test/host/net_events/stub/net_socket.h new file mode 100644 index 00000000..978960b2 --- /dev/null +++ b/test/host/net_events/stub/net_socket.h @@ -0,0 +1,19 @@ +/* + * 模块名称:主机侧单测桩 - net_socket.h(NET 域共享形态,见 net_listener 同名文件) + * 模块功能:net_events.c 需要两个表项入口——net_sock_raw()(对账循环要能看到 in_use==0 + * 的槽位,真实头注释就写着"应用层请一律用 net_get_sock(),本函数仅供 + * net_events.c 的对账循环使用")与 net_get_sock();错误通道照抄真实头同名宏。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef NET_SOCKET_H +#define NET_SOCKET_H + +#include "net_types.h" + +net_sock_t *net_get_sock(int sockfd); +net_sock_t *net_sock_raw(int sockfd); +int net_set_errno(int err); +#define NET_ERR_RETURN_1(err) (net_set_errno(err), -1) + +#endif /* NET_SOCKET_H */ diff --git a/test/host/net_events/stub/task.h b/test/host/net_events/stub/task.h new file mode 100644 index 00000000..8a8a7651 --- /dev/null +++ b/test/host/net_events/stub/task.h @@ -0,0 +1,15 @@ +/* + * 模块名称:主机侧单测桩 - task.h(net_events 模块专用) + * 模块功能:只声明 xTaskGetTickCount——它只在 NET_BENCH_CMD_LOG 打开时用到,本模块 + * 刻意不开该宏(压测日志不是被测对象),声明留着是为了原型完整。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef TASK_H +#define TASK_H + +#include "FreeRTOS.h" + +uint32_t xTaskGetTickCount(void); + +#endif /* TASK_H */ diff --git a/test/host/net_events/stub/test_config.h b/test/host/net_events/stub/test_config.h new file mode 100644 index 00000000..485a64e9 --- /dev/null +++ b/test/host/net_events/stub/test_config.h @@ -0,0 +1,10 @@ +/* + * 模块名称:主机侧单测桩 - test_config.h(net_events 模块专用) + * 模块功能:空壳。**刻意不定义 NET_BENCH_CMD_LOG**:压测命令日志不是被测对象, + * 且它依赖真实 tick 序列;不定义即被条件编译整段摘掉。 + * ⚠ 仅供主机侧单测使用,不参与 MCU 构建。 + * 创建日期:2026-10-23 + */ +#ifndef TEST_CONFIG_H +#define TEST_CONFIG_H +#endif /* TEST_CONFIG_H */