Files
STM32F4-Base/test/host/net_pending/net_pending_test.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +08:00

468 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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 an fd below range reports 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 an fd above range is rejected the same way");
reset_all();
rc = net_pending_register(0, g_rbuf, 10, 100u, (TaskHandle_t)&g_caller_obj);
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-005 a successful registration returns the 0x70000000 marker");
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 a pending op on the same fd is refused (one recv per socket at most)");
reset_all();
rc = net_pending_register(0, g_rbuf, 10, 100u, NULL);
CHECK((rc == -1) && (g_errno_last == NET_ERR_BUSY),
"TC-NETPEND-003 a NULL caller is refused (nothing to report back to, so do not suspend)");
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 the owning task starting it is refused (waiting for itself would deadlock 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 is recorded as an infinite wait (no deadline armed)");
g_now = 999999u;
net_pending_tick(g_socks);
CHECK((s_net_pending[0].active == 1u) && (g_notify_n == 0),
"TC-NETPEND-006b an infinite wait is never cut off by time (and no notification is sent behind the scenes)");
}
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 data arrived: the byte count is returned to the calling task through the notification value");
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 the read amount is clamped into the caller buffer (never writing past someone else's 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 an empty readable: clear the flag and keep waiting instead of handing over 0 bytes");
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 timeout: the error code goes into both the notification value and errno (available across contexts)");
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 a vanished slot reports CONNRESET (the task must not have to survive until the timeout to notice)");
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 closed with leftovers: drain the last packet before finishing (no tail data lost)");
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 a clean end reports 0 and requests a listener reopen (a single-connection socket must return to 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 a socket with auto-relisten off is never asked to rebuild the listener behind its back");
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 an abnormal release reports CONNRESET instead of 0 (the caller must tell a good end from a bad one)");
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 takes the same path as CLOSED (a stale state still gets a proper close)");
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 cancel: the error code is delivered through the notification value and the suspension is released");
net_pending_cancel(0, NET_ERR_CONNRESET);
CHECK(g_notify_n == 1, "TC-NETPEND-016b a repeated cancel is idempotent (the same task is never notified twice)");
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 notifies only the two suspended ops (used on a full network reset)");
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 only releases the suspension and sends no notification (the caller gets no fake answer)");
}
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 with nothing suspended it returns directly (the chip is not disturbed)");
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 the teardown drains the remaining bytes and returns their count (closing loses no data)");
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 with nothing left it reports CONNRESET (a close teardown must not look like a zero-byte read)");
}
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 an out-of-range fd reports 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 a NULL buffer reports 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 a non-positive length reports 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 a negative sent count reports 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 reports INVAL (nothing left to send, no need to suspend)");
rc = net_send_pending_register(0, g_wbuf, 100, 50, NULL);
CHECK(g_errno_last == NET_ERR_BUSY, "TC-NETPEND-031 a NULL caller reports 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 the owning task sending it itself reports BUSY");
rc = net_send_pending_register(0, g_wbuf, 100, 50, (TaskHandle_t)&g_caller_obj);
CHECK(rc == NET_PENDING_REGISTERED, "TC-NETPEND-032 a suspended send registers successfully");
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 only one suspended send per fd is allowed");
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 a vanished slot returns the number of bytes already sent (not an error code; the caller computes progress from it)");
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 a collapsed session logs ERROR + reports CONNRESET yet still hands back the bytes already sent");
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 a single write is clamped to NET_SEND_CHUNK_MAX (the chip send buffer upper bound)");
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 bytes finish in three chunks and the total length is returned");
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 every written round renews the deadline: while it progresses it must not be killed at 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 a stalled wait times out with TIMEDOUT (a peer that never reads must not hold the send path forever)");
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 cancelling a suspended send logs it and hands back the bytes already sent");
net_send_pending_cancel(0, NET_ERR_CONNRESET);
CHECK(g_notify_n == 1, "TC-NETPEND-039b a repeated cancel is idempotent");
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 sends no notification (the same discipline as the recv side)");
}
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;
}