test(NET): net_pending 模块(43 条,覆盖 87.1%),钉住通知值回传契约

假 CH395F 的第二批消费者。新增 test/host/net_pending/,net_pending.c 覆盖
169/194 = 87.1%。共享假芯片补了**缓冲尺寸宏链的同构转抄**(RAM_BLOCK 512 /
SEND_BLOCKS 2 → SEND_BUF 1024;RECV_BLOCKS 4 → RECV_BUF 2048 → SPI_DMA_BUF 2052
→ SPI_DMA_MAX_PAYLOAD 2048),于是 net_pending.h 里的 NET_SEND_CHUNK_MAX 是**真值
1024**,分块断言测的是芯片上界而不是假件自己发明的数字。

本模块的核心契约是跨上下文回传:AGENTS 模块约定写着 net_* 的错误码经**任务通知值**
回传(net_msg_t 按值拷入队列,netTask 改的是自己那份副本,调用方永远看不到;也勿给
net_msg_t 加回传字段,否则调用方读到栈垃圾)。台架读不到通知值,所以把 xTaskNotify
做成可观测假件(记目标句柄与通知值)之后,这条契约才第一次被真正验证。

钉住的契约:
- 注册闸门:越界 fd 报 BADF;同 fd 已有挂起报 BUSY;caller 为空报 BUSY;**属主任务
  自己发起被拒**(自己等自己等于死锁 netTask);to_ms=0 记为无限等(不置 deadline)。
- 到达与截断:读量钳在调用方缓冲内(绝不越界写别人的 buf);readable 但芯片没数据时
  只清标志继续等,不误交 0 字节。
- 收尾四条分支:还有残留先把最后一包读走再收尾(**不丢尾包**);干净结束报 0 并按
  auto_relisten 请求重开监听(不自动重开的不被偷偷要求重建);released_abnormal 报
  CONNRESET 而不是 0(调用方要能分辨好坏收尾);session_dead 与 CLOSED 同路。
- settle_close:没挂起时直接返回不打扰芯片;有残留交还字节数;无残留报 CONNRESET。
- 挂起发送:参数校验(buf 空 / len<=0 / sent<0 / sent>=len 报 INVAL);单次写入钳在
  NET_SEND_CHUNK_MAX;**deadline 每写一轮就续期**(还在推进就不该被 1000ms 掐死,
  FTP 上传大文件最容易撞这条);停滞不前进才报 TIMEDOUT;槽位消失或会话塌掉都**交还
  已发字节数**(不是错误码,调用方据此算进度);cancel 留痕且幂等;**clear 不发通知**
  (与 recv 侧同一条纪律,不给调用方假答复)。

自查:42/43 首跑,唯一红的是**我的测试构造缺陷**——无限等那条没先把槽位置成在用,
被 in_use==0 兜底分支提前收口,测不到时间轴;补 mk_sock 后 43/43。reset_all 从上一
批的教训直接带过来(连被测模块的 static 挂起表与属主句柄一起复位),这次没再红。

覆盖率与台账:套件 23 → 24 全绿;files_tested 27 → 28;未触及 .c 39 → 38;分母
3,572 → 3,766 行;line=3297/3766(87.5%)、func=255/282(90.4%)。恒等式
38 = 4(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETPEND(游标 040),
族数 17 → 18;注册表仍只做增量插入。

自查门禁:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量);
12 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。
--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填)
与 ui-golden(ref/*.bin 不入库)三条失败,与本分支改动无关。
This commit is contained in:
2026-10-23 22:40:00 +08:00
parent 0a3b932395
commit b4441f0e1e
12 changed files with 702 additions and 17 deletions
+467
View File
@@ -0,0 +1,467 @@
/*
* 模块名称: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 <stdio.h>
#include <string.h>
#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;
}