924ac3d428
用户裁定「统一」。口径就是 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 回填留待主工作树合并后。
468 lines
21 KiB
C
468 lines
21 KiB
C
/*
|
||
* 模块名称: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;
|
||
}
|