test(net/udp): 主机单测补 UDP-25 两条 socket 并存的隔离(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 45s
CI / 主机侧单测 (MSVC) (push) Failing after 50s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 45s

前 24 组全都只用一条 UDP socket,漏掉的正是队列化真正要保的那半句承诺:
**等待是一条 socket 自己的事**——串行的是命令,不是等待者。

UDP-25 用 13 条断言钉住两条并存 socket 的四件事:fdB 来包不得结算也不得通知
fdA 的 parked 收包(连它的调用缓冲都不许被写一个字节);fdA 自己的包到达时按
自己那一包的表头结算并恰好通知一次;fdB parked 时 fdA 自己 recvfrom 照常拿到
数据;关停在等的 fdB 必须当场结算、槽位与出参槽都不残留。

写这组时踩到假件的一个真坑,值得记下来:假件的 get_recv_len 是"每次读消耗一格、
最后一格永远重复"的脚本队列,本组前面给同一 fd 留过旧长度,不清就会拿**旧长度配
新内容**(现象是 UDP-25j 读出 12 字节而不是 7 字节),所以每条用例前用
ch395f_fake_reset_recv_len(fd) 归零再宣布——这是测法问题,不是被测代码的问题。

全套 667/667 绿(改造前 470,本次 +13);net_udp.c 仍 237/239 行 14/14 函数,
net_socket.c 966/969 行 59/59 函数,模块级覆盖率无回退。
This commit is contained in:
2026-11-04 01:28:01 +08:00
parent f4ed8a4db2
commit 1b5e5d424c
2 changed files with 101 additions and 1 deletions
+100
View File
@@ -1456,6 +1456,105 @@ static void tc_udp24_random_soak(void)
reset_all();
}
/* ================= UDP-25 两条 UDP socket 并存:挂起不串门 ================= */
/* 前面 1~24 组全都只用一条 UDP socket,唯独漏了队列化真正要保的东西:
* **等待是一条 socket 自己的事**。两条 socket 里一条 parked 时,
* 另一条来包不得惊动它,另一条自己收发也不得被它拖住——
* 这是"串行执行、并行等待"这半句承诺的唯一形状。 */
static void tc_udp25_two_socket_park_isolation(void)
{
char buf_a[32];
char buf_b[32];
char buf_c[32];
struct net_sockaddr_in sa;
net_msg_t msg;
int fd_a;
int fd_b;
int notifies_before;
int reads_before;
int alen;
int rc;
char nm[200];
bring_up_netctx();
fd_a = udp_bind(123);
fd_b = udp_bind(456);
CHECK(fd_a >= 0 && fd_b >= 0 && fd_a != fd_b,
"UDP-25a two bound UDP sockets own different slots (per-fd state, not a module-wide flag)");
if (fd_a < 0 || fd_b < 0 || fd_a == fd_b) {
reset_all();
return;
}
/* fdA 的收包登记成挂起:此刻两条 socket 的芯片缓冲都是空的 */
(void)memset(buf_a, 0x5A, (int)sizeof(buf_a));
ch395f_fake_push_recv_len((uint8_t)fd_a, 0U);
ch395f_fake_push_recv_len((uint8_t)fd_b, 0U);
udp_arm_and_enqueue(fd_a, buf_a, (int)sizeof(buf_a), 0U, &msg);
net_process_messages();
CHECK(net_udp_pending(fd_a) == 1 && net_udp_pending(fd_b) == 0,
"UDP-25b only the socket someone actually waited on holds a pending slot");
/* fdB 来一个包:fdA 既不能被结算,也不能被通知,缓冲区一个字节都不能动 */
notifies_before = g_ns_notify_calls;
reads_before = g_chip.read_calls;
s_net_socks[fd_b].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd_b, 16U);
net_process_messages();
CHECK(net_udp_pending(fd_a) == 1,
"UDP-25c another socket's datagram does not settle my parked receive");
CHECK(g_ns_notify_calls == notifies_before,
"UDP-25d and does not wake the waiter (a stray notify turns the next real packet into a stale -2)");
CHECK(buf_a[0] == (char)0x5A,
"UDP-25e the parked caller's buffer is untouched by the other socket's traffic");
/* 现在轮到 fdA 自己的包到:必须就地结算,源地址来自它自己那一包的头部。
* 长度先把该 fd 的脚本队列清空再给一条:假件的长度是"每次读消耗一格、最后一格
* 永远重复",本组前面给 fdA 留过 0/16 之类的旧值,不清就会拿旧长度配新内容。 */
udp_stage_server_datagram("TIME12345678", 12, 51999U, "192.168.31.9");
ch395f_fake_reset_recv_len((uint8_t)fd_a);
ch395f_fake_push_recv_len((uint8_t)fd_a, (uint16_t)(8 + 12));
s_net_socks[fd_a].readable = 1U;
net_process_messages();
CHECK(net_udp_pending(fd_a) == 0 && net_udp_armed(fd_a) == 0,
"UDP-25f the socket's own datagram settles its receive and frees both slots");
CHECK(g_ns_notify_calls == notifies_before + 1 &&
g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller &&
g_ns_notify_last_val == (uint32_t)12,
"UDP-25g exactly one notify to the waiter, carrying the byte count");
CHECK(memcmp(buf_a, "TIME12345678", 12) == 0,
"UDP-25h the delivered bytes are this socket's own payload (header stripped, nothing off by eight)");
/* 先把 fdB 的芯片长度重新宣布成"空"(清掉上一段留的 16),否则它当场读走而不是挂起 */
ch395f_fake_reset_recv_len((uint8_t)fd_b);
ch395f_fake_push_recv_len((uint8_t)fd_b, 0U);
udp_arm_and_enqueue(fd_b, buf_b, (int)sizeof(buf_b), 0U, &msg);
net_process_messages();
CHECK(net_udp_pending(fd_b) == 1, "UDP-25i the second socket is now the parked one");
udp_stage_server_datagram("FD-A-OK", 7, 52000U, "192.168.31.8");
ch395f_fake_reset_recv_len((uint8_t)fd_a);
ch395f_fake_push_recv_len((uint8_t)fd_a, (uint16_t)(8 + 7));
s_net_socks[fd_a].readable = 1U;
alen = (int)sizeof(sa);
(void)memset(&sa, 0, sizeof(sa));
rc = net_recvfrom(fd_a, buf_c, (int)sizeof(buf_c), NET_MSG_DONTWAIT,
(struct net_sockaddr *)&sa, &alen);
(void)snprintf(nm, sizeof(nm),
"UDP-25j a parked socket never blocks another socket's own recvfrom (rc=%d want 7, parked still waiting=%d want 1)",
rc, net_udp_pending(fd_b));
CHECK(rc == 7 && net_udp_pending(fd_b) == 1, nm);
CHECK(net_ntohs(sa.sin_port) == 52000U,
"UDP-25k and the source address still comes from the packet it actually read");
CHECK(g_chip.read_calls > reads_before,
"UDP-25l the chip really was read for fdA (the group is not passing by doing nothing)");
(void)net_close_locked(&s_net_socks[fd_b]);
CHECK(net_udp_pending(fd_b) == 0 && net_udp_armed(fd_b) == 0,
"UDP-25m closing the parked socket settles it; nothing may outlive the fd");
reset_all();
}
/* ===================== 本分片入口 ===================== */
static void ns_run_s7(void)
@@ -1484,4 +1583,5 @@ static void ns_run_s7(void)
tc_udp22_set_peer_direct_ctx();
tc_udp23_recvfrom_direct_ctx();
tc_udp24_random_soak();
tc_udp25_two_socket_park_isolation();
}