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 回填留待主工作树合并后。
531 lines
21 KiB
C
531 lines
21 KiB
C
/*
|
||
* 模块名称:NET 层事件分发与周期对账(net_events)主机侧单元测试
|
||
* 模块功能:在 PC 上离线验证 Drivers/BSP/NET/net_events.c 的五个事件入口与周期对账器:
|
||
* 对端六元组解析、**陈旧中断防误判**(芯片说还在连接就不许拆会话)、疑似
|
||
* 误报延后交给对账器、超时把会话标成异常释放(喂给 net_pending 收尾报
|
||
* CONNRESET 的那条输入)、掉线自愈自接,以及对账器的 100ms 周期闸门、
|
||
* 1000ms 冷却、五种不一致形态的稳定计数(死亡要连观 4 次、重开监听 2 次、
|
||
* 自接 1 次)与 U11 的 CLOSED->LISTENING 重同步。
|
||
*
|
||
* 被测对象:真实源文件 ../../../Drivers/BSP/NET/net_events.c(#include 原样纳入)。
|
||
* net_events.h / net_types.h / net_pending.h / net_listener.h 全用**真实头**
|
||
* (net_event_cb_t 回调签名、NET_EVENT_* / NET_SOCK_STATE_* /
|
||
* net_listener_settle_t / NET_LISTENER_REQ_* 一改即编译期报错)。假件:
|
||
* 共享假 CH395F(本批补齐对端六元组与链路变更计数)、可控 socket 表
|
||
* (net_sock_raw 对未在用槽位也返回项,与真实语义一致)、**可观测的事件
|
||
* 回调**(记 fd / 事件 / arg)、记录型 net_listener_settle/request 与
|
||
* net_pending_cancel / net_send_pending_cancel、可控时基、dbg_log 计数。
|
||
*
|
||
* 为什么要主机侧单测(而不是只靠台架):
|
||
* 1. CH395F 的中断是**整体读即清**且单连接 socket 复用同一个号,"迟到的 DISCONNECT"
|
||
* 与"丢了 CONNECT 的已连接"在真机上都会发生,却无法按需复现。误判的代价直接落在
|
||
* 现场设备上:把还活着的会话拆掉(104 主站看到莫名掉线),或让 socket 永远停在
|
||
* 已连接假象里不再重开。
|
||
* 2. 对账器的**稳定计数**(死亡 4 次、重开监听 2 次、自接 1 次)本质是"别拿一次
|
||
* 采样就把连接折腾死"。真机一次采样 100ms,凑够 4 次要 400ms,且必须一路稳定;
|
||
* 台架上没法按毫秒喂,也看不出为什么"偶尔不重开"。主机侧把时基做成显式量后,
|
||
* 第 3 次不该动、第 4 次该动、之后 1000ms 内不许再动,全部可精确断言。
|
||
* 3. handle_timeout_event 会置 released_abnormal=1,而 net_pending 收尾时正是靠
|
||
* 这个标志把挂起 recv 报成 CONNRESET 而不是 0(见 test/host/net_pending 的
|
||
* TC-NETPEND-014)。两个模块之间这条**跨文件因果**,只有两边都能离线跑时才可测。
|
||
*
|
||
* 覆盖点:TC-NETEV-001..006 CONNECT;010..014 DISCONNECT(含两条陈旧中断防误判);
|
||
* 020/021 RECV 与 SEND;030..032 TIMEOUT;040..042 掉线自愈;050..058 周期对账
|
||
* (周期闸门、链路变更留痕、五种不一致形态、稳定计数、冷却、in_use 兜底)。
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-23
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "net_events.h"
|
||
#include "net_listener.h"
|
||
#include "net_pending.h"
|
||
#include "net_socket.h"
|
||
#include "ch395f.h"
|
||
|
||
#include "ch395f_fake.c"
|
||
#include "net_events.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)
|
||
|
||
/* ===================== dbg_log 计数 ===================== */
|
||
|
||
int g_nev_info_count;
|
||
int g_nev_error_count;
|
||
int g_nev_debug_count;
|
||
|
||
/* ===================== 可观测的事件回调 ===================== */
|
||
|
||
static int g_ev_n;
|
||
static int g_ev_fd[16];
|
||
static int g_ev_kind[16];
|
||
static void *g_ev_arg[16];
|
||
static int g_ev_token;
|
||
|
||
static void ev_cb(int sockfd, net_event_t event, void *arg)
|
||
{
|
||
if (g_ev_n < (int)(sizeof(g_ev_fd) / sizeof(g_ev_fd[0]))) {
|
||
g_ev_fd[g_ev_n] = sockfd;
|
||
g_ev_kind[g_ev_n] = (int)event;
|
||
g_ev_arg[g_ev_n] = arg;
|
||
}
|
||
g_ev_n++;
|
||
}
|
||
|
||
/* ===================== 记录型监听与挂起取消 ===================== */
|
||
|
||
static int g_settle_n;
|
||
static int g_settle_sock;
|
||
static int g_settle_level;
|
||
static int g_req_n;
|
||
static int g_req_sock;
|
||
static int g_req_reason;
|
||
static int g_pcan_n;
|
||
static int g_scan_n;
|
||
static int g_pcan_err;
|
||
|
||
void net_listener_settle(int sockfd, net_listener_settle_t level)
|
||
{
|
||
g_settle_n++;
|
||
g_settle_sock = sockfd;
|
||
g_settle_level = (int)level;
|
||
}
|
||
|
||
void net_listener_request(int sockfd, net_listener_req_t reason)
|
||
{
|
||
g_req_n++;
|
||
g_req_sock = sockfd;
|
||
g_req_reason = (int)reason;
|
||
}
|
||
|
||
void net_listener_clear(int sockfd) { (void)sockfd; }
|
||
void net_listener_tick(void) { }
|
||
|
||
void net_pending_cancel(int sockfd, int err)
|
||
{
|
||
(void)sockfd;
|
||
g_pcan_n++;
|
||
g_pcan_err = err;
|
||
}
|
||
|
||
void net_send_pending_cancel(int sockfd, int err)
|
||
{
|
||
(void)sockfd;
|
||
(void)err;
|
||
g_scan_n++;
|
||
}
|
||
|
||
/* ===================== 可控时基、errno 与 socket 表 ===================== */
|
||
|
||
static uint32_t g_now;
|
||
static int g_errno_last;
|
||
|
||
uint32_t HAL_GetTick(void) { return g_now; }
|
||
|
||
int net_set_errno(int err) { g_errno_last = err; return err; }
|
||
|
||
uint32_t xTaskGetTickCount(void) { return g_now; }
|
||
|
||
static net_sock_t g_tbl[NET_MAX_SOCKETS];
|
||
|
||
net_sock_t *net_sock_raw(int sockfd)
|
||
{
|
||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||
return NULL;
|
||
}
|
||
/* 与真实语义一致:对账循环必须能读到 in_use == 0 的槽位,所以这里不给 NULL */
|
||
return &g_tbl[sockfd];
|
||
}
|
||
|
||
net_sock_t *net_get_sock(int sockfd)
|
||
{
|
||
net_sock_t *p = net_sock_raw(sockfd);
|
||
|
||
if ((p == NULL) || (p->in_use == 0u)) {
|
||
return NULL;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
static void reset_all(void)
|
||
{
|
||
int i;
|
||
|
||
(void)memset(g_tbl, 0, sizeof(g_tbl));
|
||
g_now = 1000u;
|
||
g_errno_last = 0;
|
||
g_ev_n = 0;
|
||
g_settle_n = 0;
|
||
g_settle_sock = -1;
|
||
g_settle_level = -1;
|
||
g_req_n = 0;
|
||
g_req_sock = -1;
|
||
g_req_reason = -1;
|
||
g_pcan_n = 0;
|
||
g_scan_n = 0;
|
||
g_pcan_err = 0;
|
||
g_nev_info_count = 0;
|
||
g_nev_error_count = 0;
|
||
g_nev_debug_count = 0;
|
||
ch395f_fake_reset();
|
||
(void)i;
|
||
}
|
||
|
||
/* 一个"带应用回调的在用槽位",ch395_sock 与数组下标同号 */
|
||
static net_sock_t *mk(int fd, net_sock_state_t st, uint16_t local_port)
|
||
{
|
||
net_sock_t *p = &g_tbl[fd];
|
||
|
||
p->in_use = 1u;
|
||
p->ch395_sock = (uint8_t)fd;
|
||
p->type = NET_SOCK_STREAM;
|
||
p->state = st;
|
||
p->auto_relisten = 1u;
|
||
p->local_port = local_port;
|
||
p->event_cb = ev_cb;
|
||
p->event_arg = (void *)&g_ev_token;
|
||
return p;
|
||
}
|
||
|
||
/* ===================== CONNECT ===================== */
|
||
|
||
static void tc_connect(void)
|
||
{
|
||
net_sock_t *p;
|
||
|
||
printf("-- TC-NETEV-001..006 CONNECT --\n");
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
|
||
ch395f_fake_set_peer(0, 192u, 168u, 1u, 50u, 51234u);
|
||
handle_connect_event(p);
|
||
/* remote_ip 是**低字节在前**拼的(remote[0] 落在最低 8 位),与网络序相反:
|
||
* 192.168.1.50 -> 0x3201A8C0。这是既有实现的字节序,钉住它是防止有人"顺手
|
||
* 改成大端"导致所有按 IP 比对/白名单的判断静默错位。 */
|
||
CHECK((p->remote_ip_arr[0] == 192u) && (p->remote_ip_arr[3] == 50u) &&
|
||
(p->remote_ip == ((192u) | (168u << 8) | (1u << 16) | (50u << 24))) &&
|
||
(p->remote_port == 51234u),
|
||
"TC-NETEV-001 peer tuple parsing: ip assembled little-endian into 32 bits, port from bytes 5/6");
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->session_dead == 0u) &&
|
||
(p->lost_conn_seen == 0u) && (p->send_ready == 1u),
|
||
"TC-NETEV-002 a listened socket becomes established after self-accept and is writable at once");
|
||
CHECK((g_chip.keepalive_calls == 1) && (g_chip.last_keepalive == 1u) &&
|
||
(g_chip.int_status_calls == 1),
|
||
"TC-NETEV-003 on connect, enable keepalive and clear the pending bit once (read clears)");
|
||
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
|
||
(g_ev_fd[0] == 0) && (g_ev_arg[0] == (void *)&g_ev_token),
|
||
"TC-NETEV-004 the event calls back into the app with fd and the caller arg");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
|
||
ch395f_fake_set_peer(0, 10u, 0u, 0u, 1u, 2404u);
|
||
handle_connect_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->send_ready == 0u) &&
|
||
(g_chip.keepalive_calls == 1) && (g_ev_n == 1),
|
||
"TC-NETEV-005 the active-connect branch also becomes established but does NOT touch send_ready (differs from self-accept)");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
|
||
handle_connect_event(p);
|
||
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
|
||
"TC-NETEV-006 a CONNECT interrupt in any other state is ignored (never invent a session)");
|
||
}
|
||
|
||
/* ===================== DISCONNECT ===================== */
|
||
|
||
static void tc_disconnect(void)
|
||
{
|
||
net_sock_t *p;
|
||
|
||
printf("-- TC-NETEV-010..014 DISCONNECT --\n");
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
|
||
g_fake.status_byte1[0] = CH395F_TCP_ESTABLISHED;
|
||
handle_disconnect_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
|
||
(g_nev_error_count == 1),
|
||
"TC-NETEV-010 the chip still says connecting: ignore this stale DISCONNECT (never tear down a live session)");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
|
||
g_fake.status_byte1[0] = CH395F_TCP_CLOSED;
|
||
ch395f_fake_push_recv_len(0, 9u); /* 还有 9 字节没读走 */
|
||
handle_disconnect_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->lost_conn_seen == 1u) &&
|
||
(g_ev_n == 0) && (g_settle_n == 0),
|
||
"TC-NETEV-011 suspected false alarm (dropped before any data): defer to the reconciler instead of tearing down");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
p->remote_port = 51234u;
|
||
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
|
||
g_fake.status_byte1[0] = CH395F_TCP_CLOSED;
|
||
handle_disconnect_event(p);
|
||
CHECK((g_settle_n == 1) && (g_settle_level == (int)NET_LISTENER_SETTLE_GRACEFUL) &&
|
||
(p->session_dead == 1u) && (p->released_abnormal == 0u) &&
|
||
(p->last_peer_port == 51234u) &&
|
||
(p->state == NET_SOCK_STATE_CLOSED) &&
|
||
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
|
||
"TC-NETEV-012 normal disconnect: graceful settle + mark session dead + remember old port + post event");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
|
||
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
|
||
g_fake.status_byte1[0] = CH395F_TCP_LISTEN;
|
||
handle_disconnect_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) &&
|
||
(g_nev_info_count == 1) && (g_nev_error_count == 0),
|
||
"TC-NETEV-013 listen still alive: a stale listen DISCONNECT is only logged, not reported as ERROR");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
|
||
g_fake.status_byte1[0] = CH395F_TCP_CLOSED; /* 芯片说不在 LISTEN */
|
||
handle_disconnect_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) && (g_req_n == 0),
|
||
"TC-NETEV-013b even a lost listener is only logged: reopening belongs to the reconciler, the event layer must not overreach");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
|
||
handle_disconnect_event(p);
|
||
CHECK((g_nev_error_count == 1) && (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
|
||
"TC-NETEV-014 DISCONNECT in an unusual state: report ERROR but still notify the application");
|
||
}
|
||
|
||
/* ===================== RECV / SEND / TIMEOUT ===================== */
|
||
|
||
static void tc_recv_send_timeout(void)
|
||
{
|
||
net_sock_t *p;
|
||
|
||
printf("-- TC-NETEV-020/021/030..032 --\n");
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
handle_recv_event(p);
|
||
CHECK((p->readable == 1u) && (g_ev_kind[0] == (int)NET_EVENT_DATA_RECEIVED),
|
||
"TC-NETEV-020 a RECV event sets readable and notifies the application");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
p->send_ready = 1u;
|
||
handle_send_event(p);
|
||
CHECK((g_ev_kind[0] == (int)NET_EVENT_SEND_COMPLETE) && (p->send_ready == 1u),
|
||
"TC-NETEV-021 SEND completion only posts the event; the fields stay owned by the write path");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
|
||
handle_timeout_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_CREATED) && (g_nev_error_count == 1) &&
|
||
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
|
||
"TC-NETEV-030 connect timeout: fall back to CREATED and notify the application");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
p->remote_port = 777u;
|
||
handle_timeout_event(p);
|
||
CHECK((g_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE) &&
|
||
(p->released_abnormal == 1u) && (p->session_dead == 1u) &&
|
||
(p->last_peer_port == 777u) && (p->state == NET_SOCK_STATE_CLOSED),
|
||
"TC-NETEV-031 session timeout: quarantine-level settle + mark released abnormally (net_pending reports CONNRESET from it)");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
|
||
handle_timeout_event(p);
|
||
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_nev_error_count == 1) &&
|
||
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
|
||
"TC-NETEV-032 listen timeout is only logged: state unchanged but waiters are still notified");
|
||
}
|
||
|
||
/* ===================== 掉线自愈 ===================== */
|
||
|
||
static void tc_lost_probe(void)
|
||
{
|
||
net_sock_t *p;
|
||
|
||
printf("-- TC-NETEV-040..042 lost-CONNECT probe --\n");
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
|
||
p->lost_conn_seen = 1u;
|
||
g_fake.cached_tcp[0] = CH395F_TCP_CLOSED;
|
||
net_lost_connect_probe(p);
|
||
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
|
||
"TC-NETEV-040 the chip is not established: only clear the flag, do not create a session on our own");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
|
||
p->lost_conn_seen = 1u;
|
||
p->last_peer_port = 51234u;
|
||
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
|
||
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 51234u);
|
||
net_lost_connect_probe(p);
|
||
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
|
||
"TC-NETEV-041 peer port identical to last time: assume we missed CONNECT, do not accept a second time");
|
||
|
||
/* 自愈的适用面是软件还写着 LISTENING、芯片其实已经连上——accept 分支只认
|
||
* LISTENING/CONNECTING。若软件态已被别处改成 CLOSED,本函数打完 INFO 后什么都
|
||
* 不会发生(现状留痕,见第二断言)。 */
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
|
||
p->lost_conn_seen = 1u;
|
||
p->last_peer_port = 1u;
|
||
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
|
||
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u);
|
||
net_lost_connect_probe(p);
|
||
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
|
||
(p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 55555u) &&
|
||
(p->lost_conn_seen == 0u),
|
||
"TC-NETEV-042 self-heal after a lost CONNECT interrupt: make up one accept so the session is not stranded forever");
|
||
|
||
reset_all();
|
||
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
|
||
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
|
||
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u);
|
||
net_lost_connect_probe(p);
|
||
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
|
||
"TC-NETEV-042b current state: self-heal cannot accept once the software side is CLOSED (logged only, no invented session)");
|
||
}
|
||
|
||
/* ===================== 周期对账 ===================== */
|
||
|
||
static void recon_step(uint32_t now)
|
||
{
|
||
g_now = now;
|
||
net_reconcile_tick();
|
||
}
|
||
|
||
static void tc_reconcile(void)
|
||
{
|
||
net_sock_t *p;
|
||
int infos;
|
||
int c;
|
||
|
||
printf("-- TC-NETEV-050..058 reconcile --\n");
|
||
/* 对账器里的 s_last / s_bad[] / s_cool[] 是函数内 static(外部够不着),
|
||
* 所以每个形态**各占一个 fd**、时间统一单调前进,互不串扰。 */
|
||
reset_all();
|
||
infos = g_nev_info_count;
|
||
recon_step(1000u); /* priming:链路计数记基线 */
|
||
CHECK(g_nev_info_count == infos,
|
||
"TC-NETEV-051a the first pass after power-up logs nothing for the link counter (the 0xFFFF sentinel only records the baseline)");
|
||
c = g_chip.cached_calls;
|
||
recon_step(1000u); /* 同一时刻再调一次 */
|
||
CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms period gate: do not query the chip twice at the same instant");
|
||
|
||
reset_all();
|
||
p = mk(1, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
g_fake.cached_open[1] = CH395F_SOCKET_CLOSED;
|
||
g_fake.cached_tcp[1] = CH395F_TCP_CLOSED;
|
||
infos = g_nev_info_count;
|
||
recon_step(1100u);
|
||
recon_step(1200u);
|
||
recon_step(1300u);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
|
||
(g_nev_info_count == infos),
|
||
"TC-NETEV-052 a dead session needs 4 consecutive observations before acting: the first three change nothing");
|
||
recon_step(1400u);
|
||
CHECK((p->state == NET_SOCK_STATE_CLOSED) && (p->session_dead == 1u) &&
|
||
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
|
||
"TC-NETEV-052b once the 4th observation is stable, run the disconnect teardown");
|
||
p->state = NET_SOCK_STATE_ESTABLISHED; /* 假装又活了,验证冷却 */
|
||
recon_step(1500u);
|
||
recon_step(1600u);
|
||
recon_step(1700u);
|
||
recon_step(1800u);
|
||
CHECK(g_ev_n == 1, "TC-NETEV-052c no repeat action inside the 1000ms cooldown after acting (even when the stable count is met)");
|
||
|
||
reset_all();
|
||
p = mk(2, NET_SOCK_STATE_ESTABLISHED, 2404u);
|
||
g_fake.cached_open[2] = CH395F_SOCKET_OPEN;
|
||
g_fake.cached_tcp[2] = CH395F_TCP_LISTEN; /* 芯片自己重开监听了,却没发 DISCONNECT */
|
||
recon_step(2900u);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_pcan_n == 0),
|
||
"TC-NETEV-053 the chip reopened the listener behind our back: needs 2 stable passes, the first one only accumulates");
|
||
recon_step(3000u);
|
||
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_pcan_n == 1) &&
|
||
(g_scan_n == 1) && (g_pcan_err == NET_ERR_CONNRESET),
|
||
"TC-NETEV-053b second stable pass: resync to listen and cancel both pending waits (otherwise a task would wait forever)");
|
||
|
||
reset_all();
|
||
p = mk(3, NET_SOCK_STATE_LISTENING, 2404u);
|
||
p->last_peer_port = 1u;
|
||
g_fake.cached_tcp[3] = CH395F_TCP_ESTABLISHED; /* 连上了却没 CONNECT 中断 */
|
||
ch395f_fake_set_peer(3, 172u, 16u, 0u, 5u, 40000u);
|
||
recon_step(4100u);
|
||
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 40000u) &&
|
||
(g_ev_kind[0] == (int)NET_EVENT_CONNECTED),
|
||
"TC-NETEV-054 connect completed but the interrupt was lost: a single pass is enough to make up the accept");
|
||
|
||
reset_all();
|
||
p = mk(4, NET_SOCK_STATE_LISTENING, 2404u);
|
||
g_fake.cached_open[4] = CH395F_SOCKET_CLOSED; /* 监听没了 */
|
||
g_fake.cached_tcp[4] = CH395F_TCP_CLOSED;
|
||
recon_step(5200u);
|
||
recon_step(5300u);
|
||
CHECK((g_req_n == 1) && (g_req_sock == 4) &&
|
||
(g_req_reason == (int)NET_LISTENER_REQ_RECON),
|
||
"TC-NETEV-055 listener lost: request a reopen after 2 stable passes (the event layer does not open it itself)");
|
||
|
||
reset_all();
|
||
p = mk(5, NET_SOCK_STATE_CLOSED, 2404u);
|
||
g_fake.cached_open[5] = CH395F_SOCKET_OPEN; /* 芯片还在 LISTEN */
|
||
g_fake.cached_tcp[5] = CH395F_TCP_LISTEN;
|
||
p->remote_port = 999u;
|
||
p->lost_conn_seen = 1u;
|
||
recon_step(6400u);
|
||
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (p->last_peer_port == 999u) &&
|
||
(p->lost_conn_seen == 0u) && (g_req_n == 0),
|
||
"TC-NETEV-056 U11 resync: software CLOSED while the chip is LISTEN -> follow the chip, no redundant reopen");
|
||
|
||
reset_all();
|
||
p = mk(6, NET_SOCK_STATE_CLOSED, 2404u);
|
||
g_fake.cached_open[6] = CH395F_SOCKET_CLOSED;
|
||
recon_step(7500u);
|
||
recon_step(7600u);
|
||
CHECK((g_req_n == 1) && (g_req_sock == 6),
|
||
"TC-NETEV-057 software CLOSED and the chip is not listening either: request a reopen (the listener layer rebuilds it)");
|
||
|
||
reset_all();
|
||
(void)memset(&g_tbl[7], 0, sizeof(g_tbl[7])); /* 槽位不在用 */
|
||
infos = g_nev_info_count;
|
||
recon_step(8700u);
|
||
CHECK(g_nev_info_count == infos, "TC-NETEV-058 unused slots are skipped outright (no action at all)");
|
||
|
||
/* 链路日志:s_link_last 初值 0xFFFF 是哨兵——第一次看到计数只记基线、不打日志
|
||
* (否则每次上电都会冒一条"链路 up"的假事件);此后计数一变就必须留痕。
|
||
* 本文件开头那次 priming 就是"基线"那一趟(TC-NETEV-051a),这里是"变化"那一趟。 */
|
||
reset_all();
|
||
g_fake.link_change = 7u;
|
||
infos = g_nev_info_count;
|
||
recon_step(9800u);
|
||
CHECK(g_nev_info_count == (infos + 1),
|
||
"TC-NETEV-051b any change of the link-change counter is logged (the baseline pass is already covered by 051a)");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
printf("=== NET Events Tests ===\n");
|
||
|
||
tc_connect();
|
||
tc_disconnect();
|
||
tc_recv_send_timeout();
|
||
tc_lost_probe();
|
||
tc_reconcile();
|
||
|
||
printf("\n=== NET Events Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|