Files
STM32F4-Base/test/host/common/ch395f_fake.h
T
wandering1 0a3b932395 test(NET): 假 CH395F 芯片落地 + net_listener 模块(34 条,覆盖 99.2%)
按 §9 规格做假芯片的第一批消费者。新增 test/host/common/ch395f_fake.{h,c}:15 个
函数的**真实原型逐字照抄**(含 ch395f_sock_state_t{open,tcp_state} 与 ERR_SUCCESS/
PROTO_TYPE_TCP/SOCKET_CLOSED/TCP_CLOSED/TCP_LISTEN 的真实取值),脚本化模型沿用本仓
既有假件风格:读函数按调用序号取返回队列(耗尽回落最后一个值)、写函数记次数与
最近入参、外加一条**调用顺序日志**让时序契约可断言。两条芯片怪癖必须复刻:
① get_sock_int_status **整体读即清**、无按位清除手段(AGENTS 模块约定 B3 实证坑,
不复刻就照不出"顺手全量清一遍把别的在用 Socket 门焊死");② *_cached 是**缓存值**
而非现场值,"监听还活着"的判据是 (open != CLOSED && tcp_state == LISTEN)。
各模块 stub/ch395f.h **只做转发** include 共享件(规格里拍板的方案):三份假芯片各自
漂移会让"假件与被测代码同时错"再也报不出来,签名与常量只能有一份定义。

新增 test/host/net_listener/,34 条断言,net_listener.c 覆盖 125/126 = **99.2%**。
钉住的契约(真机上一闪而过、台架凑不齐的组合):
- 四道互相牵制的时间闸门:30ms 退避(20ms 不重试、30ms 重试)、隔离级 400ms 与
  优雅级 100ms(399ms/99ms 不放行、400ms/100ms 放行)、**800ms 上限只留痕且不放弃
  请求**(留痕只打一次,到 2000ms 预算耗尽才报 ERROR 交对账器)。排期登记里那句
  "沉降窗 800ms 上限只留痕"至此第一次有了可执行的验证。
- 该不该重建:芯片仍报 LISTEN 就不重建且清空请求;有挂起 recv/send 时让路但**保留
  请求**(挂起结束自动补做);五种条件静默放弃(非 LISTENING/CLOSED 态、
  auto_relisten=0、非 STREAM、local_port=0、in_use=0)。
- 失败不留半成品:open 失败关回去并回 BUSY;listen 失败也必须 close_socket。
- 重建后簿记:转 LISTENING、清 send_ready/recv_bytes/remote_ip(_arr)/remote_port、旧
  remote_port 记进 last_peer_port、清 lost_conn_seen/released_abnormal;源端口与协议
  取自表项本身(监听端口不能漂)。
- 芯片时序:清接收 → 关断 → 读即清 → 读状态(顺序不可换);清理接收区单次读钳到
  256B、最多 16 轮、读到空即停。越界 fd 全程静默且不打扰芯片;请求去重保留首个 reason。

测试自查修了两处(都不是生产问题但值得记):
① reset_all 只复位假世界、**没复位被测模块自己的 static**(s_relisten_req/_next/_dead/
_quarantine/_settle_*/_capped),一上来红了 9 条;改用模块自带的 net_listener_clear()
清,顺带验证了 clear 的完整性。
② 沉降窗边界必须在**独立场景**测:放行判定与 30ms 退避共用 s_relisten_next,同一条
链里"恰好 400ms"那轮 tick 会被上一轮设的退避槽挡掉。这本身是个该知道的事实——
**hold 到点后最坏还要再等一个退避周期(实测 ≤30ms)才真正放行**,已写进用例注释。
另留痕一处死分支:net_relisten_locked 的 NET_ERR_BADF 从 tick **不可达**(tick 在调用
前就因表项为空放弃),TC-NETLIS-026 记着,新增调用点时要重新审视。

桩的口径:net_listener.h/net_types.h/net_config.h/net_pending.h 用**真实头**(net_sock_t
字段与各枚举一改即编译期报错);net_socket.h 是桩,但 net_set_errno 与 NET_ERR_RETURN_1
照抄真实头同名宏(net_socket.h:528/532);真实 net_pending.h 原型带 TaskHandle_t,故
宿主 FreeRTOS.h 补一个 void* typedef(只出现在签名里,被测代码从不传它)。

覆盖率与台账:套件 22 → 23 全绿;files_tested 26 → 27;未触及 .c 40 → 39;分母
3,446 → 3,572 行;line=3128/3572(87.6%,本批净涨因为新进来的文件几乎全绿)、
func=243/266(91.3%)。恒等式 39 = 5(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建
TC-HOST-NETLIS(游标 026),族数 16 → 17;注册表仍只做增量插入。

自查:test-cppcheck Passed(连假芯片一起过);check_ids --strict 通过;doc-links 35 处
(= 存量);新增文件 BOM/CR/U+FFFD/行尾逐项净。**本增量零生产代码改动**,未跑 @build。
--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填)
与 ui-golden(ref/*.bin 不入库,新 worktree 天生没有黄金参考)三条失败,与本分支无关。
2026-10-23 23:44:50 +08:00

128 lines
6.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:假 CH395F 芯片(NET 域主机侧单测共享件)
* 模块功能:给 NET 域的主机侧单测一个**可脚本化的假芯片**。真实 ch395f.h 拉 HAL 与
* SPI 寄存器定义,PC 上编不动;而 net_listener / net_pending / net_events
* 的行为恰恰完全由"芯片此刻报什么状态"决定,必须有假件才能测。
*
* ⚠ 仅供 test/host/** 使用,不参与 MCU 构建。
*
* 为什么值得共享一份(而不是每模块自带桩):三份假芯片各自漂移时,"假件与被测代码
* 同时错"这类问题再也不会报出来。各模块的 stub/ch395f.h 只做一件事——include 本文件,
* 于是签名与常量只有一份定义。
*
* 常量与类型必须与 Drivers/BSP/CH395F/ch395f.h 逐字一致(2026-10-23 从真实头抄录):
* CH395F_ERR_SUCCESS 0x00 / CH395F_PROTO_TYPE_TCP 0x03 / CH395F_SOCKET_CLOSED 0x00
* CH395F_TCP_CLOSED 0x00 / CH395F_TCP_LISTEN 0x01;ch395f_sock_state_t 为
* { uint8_t open; uint8_t tcp_state; } 两字节。
*
* 必须复刻的两条芯片怪癖(不复刻就等于把被测代码的坑假件掉):
* 1. ch395f_get_sock_int_status() 是**整体读即清**、芯片没有按位清除手段。本假件
* 返回该 socket 的挂起位图并同步清零。AGENTS 模块约定里 B3 的实证坑("顺手
* 全量清一遍"把别的在用 Socket 的门焊死)靠这条才照得出来。
* 2. *_cached 系列返回的是**缓存值**而不是现场值:open 状态与 TCP 状态分开、且
* "监听还活着"的判据是 (open != CLOSED && tcp_state == LISTEN)。测试据此能构造
* "缓存陈旧"的形态。
*
* 脚本化模型(沿用本仓既有假件风格:nand_port 的真头+假 diskio、lftpd_io 的可注入假堆):
* - 每个读函数一条按调用序号取的返回队列,队列耗尽后回落到**最后一个值**(便于
* 写"一直可读""一直关不掉"这类持续状态);
* - 每个写/控制函数一条调用记录(次数 + 最近一次入参),另给逐函数调用计数,
* 让"这一轮根本不该碰芯片"成为可断言的事;
* - g_chip.log[] 记录**调用顺序**(短名),用于断言"先清接收、再关、再读即清"这类
* 时序契约;
* - ch395f_fake_reset() 一次全清。
*
* 创建日期:2026-10-23
*/
#ifndef CH395F_FAKE_H
#define CH395F_FAKE_H
#include <stdint.h>
/* —— 与真实 ch395f.h 逐字一致的常量(值抄自真实头,勿"顺手改")—— */
#define CH395F_ERR_SUCCESS 0x00U
#define CH395F_PROTO_TYPE_TCP 0x03U
#define CH395F_SOCKET_CLOSED 0x00U
#define CH395F_SOCKET_OPEN 0x01U
#define CH395F_TCP_CLOSED 0x00U
#define CH395F_TCP_LISTEN 0x01U
#define CH395F_TCP_ESTABLISHED 0x05U
#define CH395F_FAKE_SOCK_MAX 8 /* 与 NET_MAX_SOCKETS 同量级,测试自用 */
#define CH395F_FAKE_QUEUE_LEN 24
#define CH395F_FAKE_LOG_LEN 96
typedef struct {
uint8_t open;
uint8_t tcp_state;
} ch395f_sock_state_t;
/* —— 可预置的芯片侧世界 —— */
typedef struct {
uint8_t cached_open[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().open */
uint8_t cached_tcp[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().tcp_state */
uint8_t status_byte0[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 sock 状态 */
uint8_t status_byte1[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 TCP 状态 */
uint8_t int_status[CH395F_FAKE_SOCK_MAX]; /* 读即清的挂起位图 */
uint16_t recv_len_q[CH395F_FAKE_SOCK_MAX][CH395F_FAKE_QUEUE_LEN];
int recv_len_n[CH395F_FAKE_SOCK_MAX];
int recv_len_i[CH395F_FAKE_SOCK_MAX];
uint8_t open_rc[CH395F_FAKE_SOCK_MAX]; /* open_socket 返回码 */
uint8_t listen_rc[CH395F_FAKE_SOCK_MAX]; /* tcp_listen 返回码 */
} ch395f_fake_world_t;
typedef struct {
/* 调用计数("不该碰芯片"的断言面) */
int open_calls;
int close_calls;
int listen_calls;
int int_status_calls;
int get_status_calls;
int read_calls;
int write_calls;
int set_proto_calls;
int set_port_calls;
int cached_calls;
/* 最近一次入参 */
uint8_t last_sock;
uint16_t last_read_len;
uint16_t last_port;
uint8_t last_proto;
uint8_t last_keepalive;
int keepalive_calls;
/* 调用顺序日志(短名首字母):
* 'c'=close 'i'=int_status(读即清) 's'=get_socket_status 'r'=read_recv_buf
* 'R'=get_recv_len 'o'=open_socket 'l'=tcp_listen 'p'=set_sour_port
* 'P'=set_proto_type */
char log[CH395F_FAKE_LOG_LEN];
int log_n;
} ch395f_fake_obs_t;
extern ch395f_fake_world_t g_fake;
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);
void ch395f_fake_set_listening(uint8_t sock); /* 缓存:open!=CLOSED 且 tcp=LISTEN */
/* —— 假芯片实现(签名与真实 ch395f.h 一字不改)—— */
ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn);
ch395f_sock_state_t ch395f_get_sock_state(uint8_t sn);
void ch395f_get_socket_status(uint8_t sock, uint8_t *status);
uint8_t ch395f_get_sock_int_status(uint8_t sock);
uint16_t ch395f_get_recv_len(uint8_t sock);
void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len);
void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len);
uint8_t ch395f_open_socket(uint8_t sock);
uint8_t ch395f_close_socket(uint8_t sock);
uint8_t ch395f_tcp_listen(uint8_t sock);
void ch395f_set_proto_type(uint8_t sock, uint8_t proto);
void ch395f_set_sour_port(uint8_t sock, uint16_t port);
void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf);
void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable);
uint8_t ch395f_link_up_cached(void);
uint16_t ch395f_link_change_count(void);
#endif /* CH395F_FAKE_H */