/* * 模块名称:NET 层挂起 recv / 挂起 send(net_pending)主机侧单元测试 * 模块功能:在 PC 上离线验证 Drivers/BSP/NET/net_pending.c 的两条挂起队列:注册闸门 * (越界/重复/执行任务自己发起)、**错误码经任务通知值回传**这条跨上下文 * 契约、收数据到达与截断、超时、会话塌掉的四种收尾分支(不丢尾包 / 干净 * 结束报 0 / 异常释放报 CONNRESET / 槽位消失报 CONNRESET)、settle_close 的 * 收尾语义,以及发送侧的分块上限、停滞判定、deadline 续期与取消语义。 * * 被测对象:真实源文件 ../../../Drivers/BSP/NET/net_pending.c(#include 原样纳入)。 * net_pending.h / net_types.h / net_config.h / net_listener.h 全用**真实头** * (TaskHandle_t 入参、NET_PENDING_REGISTERED、NET_SEND_TIMEOUT_MS、 * net_listener_req_t 枚举一改就编译期报错)。假件: * - 假 CH395F 芯片(test/host/common/ch395f_fake.h/.c,含与真实头同构转抄 * 的缓冲尺寸宏链,NET_SEND_CHUNK_MAX 因此是真值 1024 而非假件自造); * - **可观测的 xTaskNotify**(记目标句柄与通知值)与固定句柄的 * xTaskGetCurrentTaskHandle; * - 记录型 net_listener_request;可控时基;可控 socket 表数组(tick 入参); * 可控"芯片此刻还有多少字节可读";dbg_log 计数;net_set_errno 记录。 * * 为什么要主机侧单测(而不是只靠台架): * 1. AGENTS 模块约定写着:net_* 公开 API 的错误码经**任务通知值**回传,因为 net_msg_t * 是**按值拷入队列**的,netTask 改的是自己那份副本,调用方永远看不到;也**勿给 * net_msg_t 加回传字段**(加了只会让调用方读到栈垃圾)。这条契约在真机上表现为 * "偶发拿到 0 或正数却不知道为什么",日志无痕;在 PC 上必须把 xTaskNotify 做成 * 可观测的才谈得上验证——台架根本读不到通知值。 * 2. 挂起 recv 的收尾有**四条**分支(还有尾包 / 干净结束报 0 / 异常释放报 CONNRESET / * 槽位没了报 CONNRESET),差一条就是"对端已断而我方任务还挂着"或"最后一包数据 * 被丢掉"。真机要凑齐"关闭瞬间芯片接收区还剩字节"这种窗口极难。 * 3. 发送侧分块上限 = min(芯片发送缓冲, SPI DMA 上界):一次写太多会踩芯片 FIFO, * 表现为"大包发送偶发数据错位";而 deadline 每写一轮就续期,写错就会把**长文件 * 在传输中途掐死**(FTP 上传大文件最容易撞)。两者都要能精确数着字节测。 * * 覆盖点:TC-NETPEND-001..006 注册闸门;007..018 到达/截断/超时/四条收尾/取消与清理; * 019..021 settle_close;030..040 挂起发送(参数校验、分块、续期、停滞、取消)。 * * 通过:全部 TC PASS(main 返回 0)。 * 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。 * * 创建日期:2026-10-23 */ #include #include #include "net_pending.h" #include "net_listener.h" #include "net_socket.h" #include "ch395f.h" #include "ch395f_fake.c" #include "net_pending.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) int g_npd_info_count; int g_npd_error_count; int g_npd_debug_count; /* ============ 可观测的任务通知(本模块最重要的一条契约靠它才测得到) ============ */ static int g_owner_obj; static int g_caller_obj; static int g_notify_n; static void *g_notify_target[16]; static uint32_t g_notify_value[16]; TaskHandle_t xTaskGetCurrentTaskHandle(void) { return (TaskHandle_t)&g_owner_obj; } BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction) { (void)eAction; if (g_notify_n < (int)(sizeof(g_notify_target) / sizeof(g_notify_target[0]))) { g_notify_target[g_notify_n] = (void *)xTaskToNotify; g_notify_value[g_notify_n] = ulValue; } g_notify_n++; return 1; } /* ============ 记录型监听请求 ============ */ static int g_lis_req_n; static int g_lis_req_sock; static int g_lis_req_reason; void net_listener_request(int sockfd, net_listener_req_t reason) { g_lis_req_n++; g_lis_req_sock = sockfd; g_lis_req_reason = (int)reason; } void net_listener_settle(int sockfd, net_listener_settle_t level) { (void)sockfd; (void)level; } void net_listener_clear(int sockfd) { (void)sockfd; } void net_listener_tick(void) { } /* ============ 可控时基与 errno ============ */ static uint32_t g_now; static int g_errno_last; static int g_errno_calls; uint32_t HAL_GetTick(void) { return g_now; } int net_set_errno(int err) { g_errno_last = err; g_errno_calls++; return err; } /* ============ 可控 socket 表(tick 的入参) ============ */ static net_sock_t g_socks[NET_MAX_SOCKETS]; static uint8_t g_rbuf[64]; static uint8_t g_wbuf[4096]; static void mk_sock(int fd, net_sock_state_t st) { g_socks[fd].in_use = 1u; g_socks[fd].state = st; g_socks[fd].type = NET_SOCK_STREAM; g_socks[fd].auto_relisten = 1u; g_socks[fd].local_port = 2404u; } static void reset_all(void) { int i; (void)memset(g_socks, 0, sizeof(g_socks)); (void)memset(g_rbuf, 0, sizeof(g_rbuf)); g_now = 1000u; g_errno_last = 0; g_errno_calls = 0; g_notify_n = 0; g_lis_req_n = 0; g_lis_req_sock = -1; g_lis_req_reason = -1; g_npd_info_count = 0; g_npd_error_count = 0; g_npd_debug_count = 0; ch395f_fake_reset(); /* 被测模块自己的 static 必须复位:两张挂起表 + 属主句柄 (net_listener 批次在这里红过 9 条,同样的坑不踩第二遍)。 */ for (i = 0; i < NET_MAX_SOCKETS; i++) { s_net_pending[i].active = 0u; s_net_send[i].active = 0u; } s_owner_task = (TaskHandle_t)&g_owner_obj; } static void tc_recv_register(void) { int rc; printf("-- TC-NETPEND-001..006 recv register --\n"); reset_all(); rc = net_pending_register(-1, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj); CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001 下越界 fd 报 BADF"); rc = net_pending_register(NET_MAX_SOCKETS, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj); CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-001b 上越界同样拦下"); reset_all(); rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj); CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-005 注册成功回 0x70000000 标记"); rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj); CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY), "TC-NETPEND-002 同 fd 已有挂起就拒绝(一条 socket 只挂一个 recv)"); reset_all(); rc = net_pending_register(0, g_rbuf, 10, 100u, NULL); CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY), "TC-NETPEND-003 caller 为空拒绝(没有回传目标就别挂起)"); rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_owner_obj); CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY), "TC-NETPEND-004 属主任务自己发起被拒(自己等自己 = 死锁 netTask)"); reset_all(); /* 必须先把槽位置成"在用且已建立":否则 tick 第一条兜底(in_use == 0 → * CONNRESET)就会把挂起收掉,测的就不是时间轴了。 */ mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_pending_register(0, g_rbuf, 10, 0u, (TaskHandle_t)&g_caller_obj); CHECK((s_net_pending[0].infinite == 1u) && (s_net_pending[0].deadline == 0u), "TC-NETPEND-006 to_ms=0 记为无限等(不置 deadline)"); g_now = 999999u; net_pending_tick(g_socks); CHECK((s_net_pending[0].active == 1u) && (g_notify_n == 0), "TC-NETPEND-006b 无限等不被时间掐掉(也不会偷偷发通知)"); } static void tc_recv_paths(void) { printf("-- TC-NETPEND-007..018 recv completion --\n"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); g_socks[0].readable = 1u; (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); ch395f_fake_push_recv_len(0, 5u); net_pending_tick(g_socks); CHECK((g_notify_n == 1) && (g_notify_value[0] == 5u) && (g_notify_target[0] == (void *)&g_caller_obj) && (g_socks[0].recv_bytes == 5u) && (g_socks[0].readable == 0u) && (s_net_pending[0].active == 0u), "TC-NETPEND-007 数据到达:字节数经通知值回传给发起任务"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); g_socks[0].readable = 1u; (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); ch395f_fake_push_recv_len(0, 100u); net_pending_tick(g_socks); CHECK((g_notify_value[0] == 10u) && (g_chip.last_read_len == 10u), "TC-NETPEND-008 读量钳在调用方缓冲内(绝不越界写别人的 buf)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); g_socks[0].readable = 1u; (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); net_pending_tick(g_socks); CHECK((g_notify_n == 0) && (g_socks[0].readable == 0u) && (s_net_pending[0].active == 1u), "TC-NETPEND-009 空 readable:清标志继续等,不误交 0 字节"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj); g_now = 1100u; net_pending_tick(g_socks); CHECK((g_notify_value[0] == (uint32_t)NET_ERR_TIMEDOUT) && (g_errno_last == NET_ERR_TIMEDOUT) && (s_net_pending[0].active == 0u), "TC-NETPEND-010 超时:错误码同时进通知值与 errno(跨上下文拿得到)"); reset_all(); (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); g_socks[0].in_use = 0u; net_pending_tick(g_socks); CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET, "TC-NETPEND-011 槽位消失报 CONNRESET(不能让任务熬到超时才发现)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); ch395f_fake_push_recv_len(0, 12u); net_pending_tick(g_socks); CHECK((g_notify_value[0] == 12u) && (g_socks[0].recv_bytes == 12u) && (g_lis_req_n == 0), "TC-NETPEND-012 关闭但有残留:先把最后一包读走再收尾(不丢尾包)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); net_pending_tick(g_socks); CHECK((g_notify_value[0] == 0u) && (g_lis_req_n == 1) && (g_lis_req_sock == 0) && (g_lis_req_reason == (int)NET_LISTENER_REQ_PENDING), "TC-NETPEND-013 干净结束:报 0 并请求重开监听(单连接 socket 必须回 LISTEN)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); g_socks[0].auto_relisten = 0u; (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); net_pending_tick(g_socks); CHECK((g_notify_value[0] == 0u) && (g_lis_req_n == 0), "TC-NETPEND-013b 不自动重开的 socket 不会被偷偷要求重建监听"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); g_socks[0].released_abnormal = 1u; (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); net_pending_tick(g_socks); CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET, "TC-NETPEND-014 异常释放报 CONNRESET 而不是 0(调用方要能分辨好坏收尾)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); g_socks[0].session_dead = 1u; (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); net_pending_tick(g_socks); CHECK((g_notify_n == 1) && (g_lis_req_n == 1), "TC-NETPEND-015 session_dead 与 CLOSED 同路(状态滞后时仍能收口)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); net_pending_cancel(0, NET_ERR_CONNRESET); CHECK((g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET) && (g_errno_last == NET_ERR_CONNRESET) && (s_net_pending[0].active == 0u), "TC-NETPEND-016 取消:错误码经通知值送达并解除挂起"); net_pending_cancel(0, NET_ERR_CONNRESET); CHECK(g_notify_n == 1, "TC-NETPEND-016b 重复取消幂等(不给同一任务发两次通知)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); mk_sock(2, NET_SOCK_STATE_ESTABLISHED); (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); (void)net_pending_register(2, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); net_pending_cancel_all(NET_ERR_CONNRESET); CHECK((g_notify_n == 2) && (s_net_pending[0].active == 0u) && (s_net_pending[2].active == 0u), "TC-NETPEND-017 cancel_all 只通知挂着的那两个(网络整体复位时用)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_pending_register(0, g_rbuf, 10, 500u, (TaskHandle_t)&g_caller_obj); net_pending_clear(0); CHECK((s_net_pending[0].active == 0u) && (g_notify_n == 0), "TC-NETPEND-018 clear 只解除挂起、不发通知(不给调用方假答复)"); } static void tc_settle_close(void) { int before; printf("-- TC-NETPEND-019..021 settle_close --\n"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); before = g_chip.read_calls + g_chip.get_status_calls; net_pending_settle_close(0, &g_socks[0]); CHECK((g_chip.read_calls + g_chip.get_status_calls) == before, "TC-NETPEND-019 没挂起时直接返回(不打扰芯片)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); ch395f_fake_push_recv_len(0, 7u); net_pending_settle_close(0, &g_socks[0]); CHECK((g_notify_value[0] == 7u) && (g_socks[0].recv_bytes == 7u) && (g_socks[0].readable == 0u), "TC-NETPEND-020 收尾时把剩余字节读走并交还字节数(收口不丢数据)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); (void)net_pending_register(0, g_rbuf, 20, 500u, (TaskHandle_t)&g_caller_obj); net_pending_settle_close(0, &g_socks[0]); CHECK(g_notify_value[0] == (uint32_t)NET_ERR_CONNRESET, "TC-NETPEND-021 无残留则报 CONNRESET(关闭收尾不该被当成读到 0 字节)"); } static void tc_send_paths(void) { int rc; printf("-- TC-NETPEND-030..040 send pending --\n"); reset_all(); rc = net_send_pending_register(-1, g_wbuf, 100, 0, (TaskHandle_t)&g_caller_obj); CHECK((rc == -1) && (g_errno_last == NET_ERR_BADF), "TC-NETPEND-030 越界 fd 报 BADF"); rc = net_send_pending_register(0, NULL, 100, 0, (TaskHandle_t)&g_caller_obj); CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030b buf 为空报 INVAL"); rc = net_send_pending_register(0, g_wbuf, 0, 0, (TaskHandle_t)&g_caller_obj); CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030c len 非正报 INVAL"); rc = net_send_pending_register(0, g_wbuf, 100, -1, (TaskHandle_t)&g_caller_obj); CHECK(g_errno_last == NET_ERR_INVAL, "TC-NETPEND-030d sent 为负报 INVAL"); rc = net_send_pending_register(0, g_wbuf, 100, 100, (TaskHandle_t)&g_caller_obj); CHECK((g_errno_last == NET_ERR_INVAL) && (s_net_send[0].active == 0u), "TC-NETPEND-030e sent>=len 报 INVAL(已发完的不必挂)"); rc = net_send_pending_register(0, g_wbuf, 100, 50, NULL); CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031 caller 为空报 BUSY"); rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_owner_obj); CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031b 属主自己发报 BUSY"); rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_caller_obj); CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-032 挂起发送注册成功"); rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_caller_obj); CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031c 同 fd 只允许一条挂起发送"); reset_all(); (void)net_send_pending_register(0, g_wbuf, 3000, 700, (TaskHandle_t)&g_caller_obj); g_socks[0].in_use = 0u; net_send_pending_tick(g_socks); CHECK((g_notify_n == 1) && (g_notify_value[0] == 700u), "TC-NETPEND-033 槽位消失回**已发字节数**(不是错误码,调用方据此算进度)"); reset_all(); mk_sock(0, NET_SOCK_STATE_CLOSED); (void)net_send_pending_register(0, g_wbuf, 3000, 700, (TaskHandle_t)&g_caller_obj); net_send_pending_tick(g_socks); CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_CONNRESET) && (g_notify_value[0] == 700u), "TC-NETPEND-034 会话塌了:留 ERROR + 报 CONNRESET,但仍把已发字节交回去"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); g_socks[0].send_ready = 1u; (void)net_send_pending_register(0, g_wbuf, 3000, 0, (TaskHandle_t)&g_caller_obj); net_send_pending_tick(g_socks); CHECK((g_chip.write_calls == 1) && (g_chip.last_read_len == (uint16_t)NET_SEND_CHUNK_MAX) && (g_socks[0].send_bytes == (uint32_t)NET_SEND_CHUNK_MAX) && (g_socks[0].send_ready == 0u) && (s_net_send[0].active == 1u), "TC-NETPEND-035 单次写入钳在 NET_SEND_CHUNK_MAX(芯片发送缓冲上界)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_send_pending_register(0, g_wbuf, 3000, 0, (TaskHandle_t)&g_caller_obj); g_socks[0].send_ready = 1u; net_send_pending_tick(g_socks); g_socks[0].send_ready = 1u; net_send_pending_tick(g_socks); g_socks[0].send_ready = 1u; net_send_pending_tick(g_socks); CHECK((g_notify_value[0] == 3000u) && (s_net_send[0].active == 0u) && (g_chip.write_calls == 3), "TC-NETPEND-036 分三块发完 3000 字节后交还总长度"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_send_pending_register(0, g_wbuf, 3000, 0, (TaskHandle_t)&g_caller_obj); g_now = 1900u; g_socks[0].send_ready = 1u; net_send_pending_tick(g_socks); g_now = 2800u; g_socks[0].send_ready = 1u; net_send_pending_tick(g_socks); CHECK((g_npd_error_count == 0) && (s_net_send[0].active == 1u) && (g_chip.write_calls == 2), "TC-NETPEND-038 每写一轮续期 deadline:还在推进就不该被 1000ms 掐死"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_send_pending_register(0, g_wbuf, 3000, 0, (TaskHandle_t)&g_caller_obj); g_now = 2000u; net_send_pending_tick(g_socks); CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_TIMEDOUT) && (g_notify_value[0] == 0u), "TC-NETPEND-037 停滞超时报 TIMEDOUT(对端不收时不能永久占着发送通道)"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_send_pending_register(0, g_wbuf, 100, 40, (TaskHandle_t)&g_caller_obj); net_send_pending_cancel(0, NET_ERR_CONNRESET); CHECK((g_npd_error_count == 1) && (g_errno_last == NET_ERR_CONNRESET) && (g_notify_value[0] == 40u) && (s_net_send[0].active == 0u), "TC-NETPEND-039 取消挂起发送:留痕 + 交还已发字节"); net_send_pending_cancel(0, NET_ERR_CONNRESET); CHECK(g_notify_n == 1, "TC-NETPEND-039b 重复取消幂等"); reset_all(); mk_sock(0, NET_SOCK_STATE_ESTABLISHED); (void)net_send_pending_register(0, g_wbuf, 100, 40, (TaskHandle_t)&g_caller_obj); net_send_pending_clear(0); CHECK((s_net_send[0].active == 0u) && (g_notify_n == 0), "TC-NETPEND-040 clear 不发通知(与 recv 侧同一条纪律)"); } int main(void) { printf("=== NET Pending Tests ===\n"); tc_recv_register(); tc_recv_paths(); tc_settle_close(); tc_send_paths(); printf("\n=== NET Pending Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }