test(net/udp): 加 240 轮定种子随机长跑 UDP-24,配套假件补可重排收包脚本(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 45s
CI / 主机侧单测 (MSVC) (push) Failing after 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 46s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s

真机台架被断电和断线卡住,这一段只推进纯软件侧:把 UDP 数据面的随机序列
扫起来,并修掉随机长跑逼出的两条"测法自误"。

- ns_s7_udp.c 新增 UDP-24:固定种子 20261104 的 240 轮随机交互,每轮随机
  载荷长度 1~112(112 = 假件收包流 128 B 上限减 8 字节信息头)、源端口、
  源 IP、SERVER/CLIENT 形态、显式对端 sendto 与按记账对端 sendto,每 17 轮
  close + 重 bind 一次。断言收在六族不变量上(长度等于芯片宣布值、既不吞
  信息头也不丢真字节、源地址按形态分别取自表头/对端记账、udp_mode 与命令
  一致、close/重 bind 后出参槽与挂起槽不残留、每次 sendto 交给芯片恰好 len
  字节),并要求 ≥90% 轮次端到端干净(实测 240/240)。
- 长跑逼出的两处都是测法问题,不是被测码 bug,但只有长跑才逼得出:
  1) 连续 sendto 不注入 SENBUF_FREE 时,第二次发送会合法转挂起,而本套的
     挂起 send 替身从不完成,于是 FATAL 掐死整套件(连汇总行都没有)。改法
     照 440i/440j:set_sint_sticky(SENBUF_FREE) + push_glob + g_ns_int_pin_set=0,
     注释点名 TRAP-NET-04,背靠背发送的真机面留给 TC-NET-1609。
  2) 假件 ch395f_get_recv_len 是"按调用序号取、耗尽后重复最后一个"的脚本且
     容量有限,240 轮只追加不清空,序号撞满容量后每轮读到同一个陈旧长度
     (症状:len 在随机而 n 恒为 28,前 24 轮全绿,最容易误判成产品 bug)。
     补加法助手 ch395f_fake_reset_recv_len(sock),只归零该 sock 的脚本长度与
     序号,既有单轮用例不调用它、行为一字未变。
- 覆盖率口径第 4 轮同步:用例面记到 24 组、全套 654/654(改造前 470),
  新增一条"随机长跑逼出两条测法自误"的教训(长跑对假件的第一要求是
  状态可重排,不是能追加)。

验证:net_socket 套件 654/654 绿;全量覆盖率 47/47 退出码 0,net_udp.c
237/239 行 99.2%、14/14 函数,net_socket.c 966/969 行 99.7%、59/59 函数,
总行 8372/9290(90.1%),无 REGRESS;cppcheck(--hook-stage manual)退出码 0;
check_doc_links --all 通过(213 个文档,无法判定 0);pre-commit 对本批文件
全绿。基线 JSON 无变化(本轮只加测试,未动生产码),故不随本提交改动。
This commit is contained in:
2026-11-04 00:46:08 +08:00
parent fc0d3e4b5c
commit 9608b7a8ea
4 changed files with 221 additions and 1 deletions
+203
View File
@@ -1254,6 +1254,208 @@ static void tc_udp23_recvfrom_direct_ctx(void)
reset_all();
}
/* ---------- UDP-24:随机化 soak(纯软件,不需要板子) ---------- */
/*
* 函数功能:用固定种子的同余序列生成 240 轮随机 UDP 交互,每轮随机「载荷长度
* 1..112 / 源端口 / 源 IP / 本轮形态 SERVER 或 CLIENT / 回显是否带显式
* dest_addr / 是否顺带做一次 close+重开」,并断言一组**不变量**。
* 函数说明:UDP-1..23 是一条条钉死的具体契约,本组补的是另一件事——任意合法
* 序列跑完之后系统仍停在合法状态。这类断言最能暴露"某条出口忘了清槽 /
* 剥头判定被上一条命令带跑 / 长度取错源"的缺陷,而它们恰好在真机上最难
* 复现(本次改造修掉的 DesIP 形态缺陷就是这一类)。
* 种子固定 = 失败可原样复现(LCG 参数 k=1664525、c=1013904223)。
* 断言按"族"计数并打印第一个坏轮次,不逐轮 CHECK:240 轮 × 6 条会把
* 套件的用例计数彻底淹没,而失败信息一条也不会少。
* 112 = 假芯片可读流上限 128 B 减去服务器模式的 8 字节信息头。
*/
#define UDP_SOAK_ROUNDS 240
#define UDP_SOAK_MAX_DATA 112
static uint32_t udp_soak_rand(uint32_t *st)
{
*st = (*st * 1664525U) + 1013904223U;
return *st;
}
static void tc_udp24_random_soak(void)
{
uint32_t st = 20261104U;
uint8_t payload[UDP_SOAK_MAX_DATA];
uint8_t rx[UDP_SOAK_MAX_DATA + 8];
struct net_sockaddr_in peer;
struct net_sockaddr_in from;
const char *ips[2];
int bad_len = 0, bad_bytes = 0, bad_src = 0, bad_mode = 0, bad_slot = 0, bad_send = 0;
int first_bad[6];
int i;
int fd;
uint32_t w_bytes_before;
uint32_t w_calls_before;
int round_ok = 0;
char nm[192];
ips[0] = "10.20.30.40";
ips[1] = "192.168.7.9";
first_bad[0] = first_bad[1] = first_bad[2] = -1;
first_bad[3] = first_bad[4] = first_bad[5] = -1;
bring_up();
fd = udp_bind(6001);
if (fd < 0) {
CHECK(0, "UDP-24 soak could not bind a socket");
reset_all();
return;
}
ch395f_fake_enable_write_capture();
/* 真芯片每收完一包 WRITE_SEND_BUF 会拉 SENBUF_FREE(TRAP-NET-04:不等这个事件
* 就再写,写会被丢)。本套件的挂起发送是 ns_peers 的替身、不会自己结算,所以
* 按 440i/440j 的既有做法把它钉成电平:让每轮 sendto 都走同步写路径,
* "连续两包背靠背发"这件事交给阶段 16 的真机 TC-NET-1609 去证。 */
ch395f_fake_set_sint_sticky(fd, CH395F_SINT_STAT_SENBUF_FREE);
ch395f_fake_push_glob((uint32_t)1U << (fd + 4));
g_ns_int_pin_set = 0;
(void)memset(&peer, 0, sizeof(peer));
peer.sin_family = NET_AF_INET;
peer.sin_addr.s_addr = net_inet_addr("119.28.220.214");
peer.sin_port = net_htons(123U);
for (i = 0; i < UDP_SOAK_ROUNDS; i++) {
int len = 1 + (int)(udp_soak_rand(&st) % (uint32_t)UDP_SOAK_MAX_DATA);
uint16_t sport = (uint16_t)(1024U + (udp_soak_rand(&st) % 60000U));
const char *sip = ips[udp_soak_rand(&st) & 1U];
int as_client = ((int)(udp_soak_rand(&st) % 3U) == 0);
int j;
int n;
int alen;
int clean = 1;
for (j = 0; j < len; j++) {
payload[j] = (uint8_t)((j * 7 + i * 3 + 0x11) & 0xFF);
}
/* 形态由本轮决定,不沿用上轮遗留:SERVER 用 set_peer(NULL) 轻切回,
* CLIENT 显式指对端。剥不剥 8 字节必须只由这个判定决定。 */
if (net_udp_set_peer(fd, as_client ? &peer : NULL) != 0) {
if (first_bad[3] < 0) { first_bad[3] = i; }
bad_mode++;
continue;
}
if (net_udp_get_mode(fd) != (as_client ? NET_UDP_MODE_CLIENT : NET_UDP_MODE_SERVER)) {
if (first_bad[3] < 0) { first_bad[3] = i; }
bad_mode++;
continue;
}
(void)memset(rx, 0xA5, sizeof(rx));
alen = (int)sizeof(from);
/* 每轮重排长度脚本:假件的收包长度是"按调用序号取"的脚本,240 轮只追加
* 不清空会让序号撞满容量、之后每轮都读到同一个陈旧长度。 */
ch395f_fake_reset_recv_len(0);
if (as_client) {
/* 客户端模式:芯片不再前置信息头,整段就是载荷 */
ch395f_fake_set_recv_stream(payload, (uint16_t)len);
ch395f_fake_push_recv_len(0, (uint16_t)len);
} else {
udp_script_server_datagram((const char *)payload, len, sport, sip);
}
n = net_recvfrom_to(fd, rx, len, 800U, (struct net_sockaddr *)&from, &alen);
if (n != len) {
if (first_bad[0] < 0) { first_bad[0] = i; }
bad_len++;
clean = 0;
} else if (memcmp(rx, payload, (size_t)len) != 0) {
if (first_bad[1] < 0) { first_bad[1] = i; }
bad_bytes++;
clean = 0;
} else if (alen != (int)sizeof(from) ||
from.sin_family != NET_AF_INET) {
if (first_bad[2] < 0) { first_bad[2] = i; }
bad_src++;
clean = 0;
} else {
uint32_t want_ip = as_client ? peer.sin_addr.s_addr : net_inet_addr(sip);
uint16_t want_port = as_client ? net_ntohs(peer.sin_port) : sport;
if (from.sin_addr.s_addr != want_ip || net_ntohs(from.sin_port) != want_port) {
if (first_bad[2] < 0) { first_bad[2] = i; }
bad_src++;
clean = 0;
}
}
/* 出参槽与挂起槽必须在每条出口后归零:泄漏 = 这个 fd 以后永久 BUSY */
if (net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0) {
if (first_bad[4] < 0) { first_bad[4] = i; }
bad_slot++;
clean = 0;
}
/* 回显:SERVER 轮必须带显式 dest_addr(BSD 语义),CLIENT 轮走对端记账,
* 两条发送路径都被随机覆盖到。 */
w_bytes_before = g_fx.write_bytes;
w_calls_before = g_chip.write_calls;
if (as_client) {
n = net_sendto(fd, payload, len, 0, NULL, 0);
} else {
n = net_sendto(fd, payload, len, 0, (const struct net_sockaddr *)&peer,
(int)sizeof(peer));
}
if (n != len || g_fx.write_bytes - w_bytes_before != (uint32_t)len ||
g_chip.write_calls <= w_calls_before ||
net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0) {
if (first_bad[5] < 0) { first_bad[5] = i; }
bad_send++;
clean = 0;
}
/* 每 17 轮做一次 close + 重开:把 net_close_locked / net_socket 上的
* net_udp_settle_close / net_udp_clear 挂进随机序列里,重开后必须还能收。 */
if ((i % 17) == 16) {
(void)net_close(fd);
fd = udp_bind(6001);
if (fd < 0 || net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0 ||
net_udp_get_mode(fd) != NET_UDP_MODE_SERVER) {
if (first_bad[4] < 0) { first_bad[4] = i; }
bad_slot++;
clean = 0;
}
if (fd < 0) {
break;
}
}
if (clean != 0) {
round_ok++;
}
}
/* CHECK 只收 (条件, 名字):数字用 snprintf 拼进名字,失败时一眼看到第几轮坏的 */
(void)snprintf(nm, sizeof(nm), "UDP-24a soak recv length always equals the chip-announced length (bad=%d, first round=%d, rounds=%d)",
bad_len, first_bad[0], UDP_SOAK_ROUNDS);
CHECK(bad_len == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24b payload bytes neither lose the 8-byte info header nor lose real payload: SERVER strips, CLIENT never strips (bad=%d, first round=%d)",
bad_bytes, first_bad[1]);
CHECK(bad_bytes == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24c source address is right in both forms - header-derived in SERVER, peer-derived in CLIENT (bad=%d, first round=%d)",
bad_src, first_bad[2]);
CHECK(bad_src == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24d set_peer keeps the mode exactly as commanded (bad=%d, first round=%d)",
bad_mode, first_bad[3]);
CHECK(bad_mode == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24e neither the out-param slot nor the pending slot is ever left behind, including after close and rebind (bad=%d, first round=%d)",
bad_slot, first_bad[4]);
CHECK(bad_slot == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24f every sendto hands the chip exactly len bytes, explicit-dest and peer-bookkeeping alike (bad=%d, first round=%d)",
bad_send, first_bad[5]);
CHECK(bad_send == 0, nm);
(void)snprintf(nm, sizeof(nm), "UDP-24g at least 90 percent of the random rounds are clean end to end (clean=%d/%d)",
round_ok, UDP_SOAK_ROUNDS);
CHECK(round_ok * 10 >= UDP_SOAK_ROUNDS * 9, nm);
net_close(fd);
reset_all();
}
/* ===================== 本分片入口 ===================== */
static void ns_run_s7(void)
@@ -1281,4 +1483,5 @@ static void ns_run_s7(void)
tc_udp21_bind_and_recvfrom_to_exits();
tc_udp22_set_peer_direct_ctx();
tc_udp23_recvfrom_direct_ctx();
tc_udp24_random_soak();
}