test(net/udp): 加 240 轮定种子随机长跑 UDP-24,配套假件补可重排收包脚本(#73)
真机台架被断电和断线卡住,这一段只推进纯软件侧:把 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:
@@ -169,6 +169,19 @@ void ch395f_fake_set_sint_sticky(uint8_t sock, uint8_t mask)
|
||||
}
|
||||
}
|
||||
|
||||
/* 重排本轮的"芯片收到 N 字节"脚本:把调用序号退回起点并清空旧值。
|
||||
* 既有脚本语义是"按调用序号取、耗尽后重复最后一个"且容量有限;一条要跑
|
||||
* 几百轮的随机化用例若每轮只追加不清空,序号会撞满容量,之后每轮读到的
|
||||
* 都是同一个陈旧长度(UDP 队列化的 soak 用例就是这么踩到的)。
|
||||
* 既有单轮用例不调用它,逐字节行为不变。 */
|
||||
void ch395f_fake_reset_recv_len(uint8_t sock)
|
||||
{
|
||||
if (sock < CH395F_FAKE_SOCK_MAX) {
|
||||
g_fake.recv_len_n[sock] = 0U;
|
||||
g_fake.recv_len_i[sock] = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ch395f_get_recv_len(uint8_t sock)
|
||||
{
|
||||
uint16_t v;
|
||||
|
||||
@@ -192,6 +192,9 @@ extern ch395f_fake_obs_t g_chip;
|
||||
|
||||
void ch395f_fake_reset(void); /* 全清并把所有 socket 设为"已关闭" */
|
||||
void ch395f_fake_push_recv_len(uint8_t sock, uint16_t v);
|
||||
/* 清空并重排该 sock 的收包长度脚本(长跑/多轮用例每轮开头调一次;
|
||||
* 既有单轮用例不需要调,行为不变) */
|
||||
void ch395f_fake_reset_recv_len(uint8_t sock);
|
||||
void ch395f_fake_set_listening(uint8_t sock);
|
||||
void ch395f_fake_set_peer(uint8_t sock, uint8_t b0, uint8_t b1, uint8_t b2,
|
||||
uint8_t b3, uint16_t peer_port); /* 预置对端六元组 */ /* 缓存:open!=CLOSED 且 tcp=LISTEN */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user