658bc40ccc
P1 的 NET 四块开了第一块:新增 test/host/net_select/,21 条断言全部 MSVC 编译
通过并 PASS。net_select.c 一个芯片函数都不调,所以它是四块里唯一不需要假芯片的,
先把它拿下,顺带把 NET 域的桩骨架立起来。
钉住的契约:
- 就绪位回写:入参集合里未就绪的 fd 必须被清掉,只留就绪的(POSIX 语义);未请求
的集合不被写回、可以传 NULL。
- 状态准入:LISTENING 即使 readable 也不算可读(accept 语义不在这层);ESTABLISHED
+ send_ready 才可写;CONNECTING 时 send_ready 不作数。
- 边界:nfds 为 0 / 负 / 超过 NET_MAX_SOCKETS 都返回 -1(不许扫出 socket 表外)。
- 超时算术:tv_usec 是**整除截断**(1999us 当 1ms 而不是 2ms);timeout=NULL 时
无限等并由对端事件唤醒;poll 的 timeout=0 首轮即退、每轮先清 revents。
用可控 tick(每问一次 +1)+ 可控 osDelay(计数、可指定轮数后造一个就绪 socket)
把这些变成确定性轮数,台架上根本构造不出"没人来连时该怎么退"。
照出两处**死路径**(只登记,未改语义;全库唯一调用点 test/board/net_test_task.c
传的是 NULL 与非零 tv,今天没有受害者):
1. net_select 的 timeout = {0,0} 换算出 timeout_ms = 0,而退出条件写的是
if ((timeout_ms > 0) && (已用时 >= timeout_ms)) → **超时 0 永不触发退出**,退化成
无限等;POSIX 在这里必须立刻返回 0。同文件 net_poll_events 写的 timeout >= 0
是对的,两条并排放着正好是对照。风险面:谁拿它当"非阻塞查一遍",netTask 就
回不到主循环,而 netTask 在 IWDG required_mask 里。
2. exceptfds 是恒为空的死参数:循环体开头 p_sock == NULL → continue 先跳过,
check_socket_error 的 NULL 分支从 select 里到不了。按 POSIX 直觉用它收异常的人
会永远什么都收不到。
两处都用"现状桩"钉成改了会响(用例名里写明不是期望行为)。另修掉我自己写错的一条
用例:给 timeout=NULL 又没有就绪 fd,直接死循环——已在假 osDelay 里加了 500 轮
保险(跑飞就 exit(2)),因为挂死的测试会把 exe 文件名占住、让后续构建报 LNK1104。
桩的边界写死在文件头:net_select.h / net_types.h / net_config.h 用真实头(net_sock_t
字段名与 NET_SOCK_STATE_* 一改即编译期报错);main.h / cmsis_os.h / FreeRTOS.h /
task.h 是本目录桩;新建的 ch395f.h 是**空壳**,只服务不调芯片函数的文件——
net_pending / net_events / net_listener 直接调十余处 ch395f_*,要的是可脚本化返回的
假芯片,不要把这个空壳当成"NET 域桩已就绪"。
覆盖率:套件 21 → 22 全绿;files_tested 25 → 26;未触及 .c 41 → 40;分母 3,332 →
3,446 行;net_select.c 97/114(85.1%)。恒等式更新为 40 = 6(P1 剩) + 5(P2) + 29(P3)。
编号按 B 层口径补建 TC-HOST-NETSEL(游标 019),TC-HOST 族现共 16 族;注册表仍只
做增量插入。
自查: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 天生没有黄金参考)三条失败,与本分支无关。
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""NET 层 select/poll(net_select)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||
|
||
做什么:
|
||
用本机 MSVC 编译 test/host/net_select/net_select_test.c(以 #include 纳入生产源
|
||
Drivers/BSP/NET/net_select.c),在 PC 上离线验证就绪位回写、socket 状态对可读/
|
||
可写的准入、非法 fd 处理、tv_sec/tv_usec 超时换算、poll 的 revents 语义。
|
||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||
|
||
为什么要主机侧单测(而不是只用台架):
|
||
netTask 是串行 socket 栈的唯一执行者,且在 IWDG required_mask 里——这里的等待
|
||
逻辑算错,表现是"网络整体停摆 + 喂不上狗复位",而且日志里什么都没有。台架无法
|
||
构造"没人来连时该怎么退"(对端不可控);把 HAL_GetTick 与 osDelay 做成可控量后,
|
||
"第几轮该退出"能精确复现,包括 1999us 该按 1ms 还是 2ms 这种算术。
|
||
|
||
桩的边界:net_select.h / net_types.h / net_config.h 用真实头(net_sock_t 字段名与
|
||
NET_SOCK_STATE_* 一改就编译期报错);main.h / cmsis_os.h / FreeRTOS.h / task.h /
|
||
ch395f.h 是本目录桩——net_select.c 一个芯片函数都不调,所以 ch395f.h 是空壳。
|
||
⚠ 那个空壳不代表 NET 域桩已就绪:net_listener / net_pending / net_events 直接调
|
||
十多个 ch395f_* 且语义在 socket 状态机里,需要的是能脚本化返回的假芯片。
|
||
|
||
用法(仓库根目录):
|
||
python test/host/net_select/run_unit_test.py
|
||
|
||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||
|
||
设计依据:docs/网络/Net层阻塞模型架构设计.md、docs/测试说明/主机单测覆盖率口径.md §9 P1
|
||
创建日期:2026-10-23
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||
|
||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||
|
||
# 顺序敏感:stub 必须在 Drivers/BSP/NET 之前,才能用桩覆盖 net_socket.h 与 ch395f.h;
|
||
# net_select.h / net_types.h / net_config.h 仍从真实目录取。
|
||
BUILD = TestBuild(
|
||
name="NET Select Tests",
|
||
src=os.path.join(HERE, "net_select_test.c"),
|
||
include_dirs=[
|
||
os.path.join(HERE, "stub"),
|
||
os.path.join(ROOT, "Drivers", "BSP", "NET"),
|
||
HERE,
|
||
],
|
||
summary_marker="=== NET Select Tests:",
|
||
)
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(run_host_unit_test(BUILD))
|
||
|