0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
168 lines
9.1 KiB
Python
168 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""NET 套接字核心(net_socket)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||
|
||
做什么:
|
||
用本机 MSVC 编译 test/host/net_socket/net_socket_test.c(以 #include 纳入生产源
|
||
Drivers/BSP/NET/net_socket.c + 共享假芯片 test/host/common/ch395f_fake.c +
|
||
本目录的假内核 ns_rtos.c 与兄弟模块桩 ns_peers.c),在 PC 上离线验证:
|
||
跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽、
|
||
监听门控与 closed-with-readable、net_poll 的中断分派与"只读自己 SINT",
|
||
以及读写路径的短写 / 挂起登记 / EOF / 命令被拒回滚。
|
||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||
|
||
先跑常量闸(本 runner 独有的第二道检查,见 check_fake_chip_constants 注释),
|
||
不干净直接不编译——编译错误会把"假件值漂移"这件事埋得很深。
|
||
|
||
为什么要主机侧单测(而不是只用台架):
|
||
net_socket.c 是本产品**唯一的 SPI 串行化点**:所有 net_* 公开 API 把请求按值拷进
|
||
消息队列,由 netTask 串行执行,结果只能经 xTaskNotify 的 32 位通知值回来
|
||
(net_msg_t 是按值入队的,netTask 改自己那份副本,调用方永远看不到——AGENTS
|
||
模块约定明写"勿给 net_msg_t 加回传字段")。这条链的正确性取决于两个任务的
|
||
交错时序,真机上"通知丢了"表现为永久挂死并被看门狗复位,日志里只剩一条
|
||
[ERR];而错误码本身还按任务隔离,跨任务覆写只会让排障读到别人的码。
|
||
把队列/通知/时基做成显式量后,"登记了挂起却仍发即时回执"这种缺陷在 PC 上
|
||
一次就能钉死。
|
||
|
||
桩的边界(每一层为什么这么切):
|
||
- 真实头:net_socket.h / net_types.h / net_config.h / net_pending.h /
|
||
net_listener.h / net_events.h / cmsis_os2.h(只依赖 stdint+stddef,宿主可编)
|
||
→ 字段名、枚举取值、队列原型一改即编译期报错;
|
||
- 假芯片:test/host/common/ch395f_fake.h/.c(NET 域共享件,本批只做**纯新增**扩展:
|
||
net_socket.c 这一层才会调的初始化下发类 + 全局中断 0x19 + sock_reset 复位机 +
|
||
cmd_done 判据;既有宏/字段/函数一字未动);
|
||
- 假内核:本目录 ns_rtos.c(osMessageQueue 环形队列 + 任务通知 + 时基 +
|
||
临界区计数);**阻塞等待 = 调度点**:xTaskNotifyWait(portMAX_DELAY) 里以
|
||
netTask 身份跑一轮 net_process_messages(),于是单次公开 API 调用能跑完整条
|
||
入队→分派→通知值回传→调用方恢复 errno 的链;
|
||
- 兄弟模块桩:本目录 ns_peers.c(net_listener/net_pending/net_events 的边界契约
|
||
+ 调用记录)——这三个模块各有自己的主机单测(34/34/43 条),本套只测
|
||
net_socket.c 自己的决策,并把"该不该登记、登记哪个 reason"变成可断言面;
|
||
- main.h / FreeRTOS.h / task.h / dbg_log.h / test_config.h / net_latency_test.h
|
||
是本目录桩;刻意不定义 TEST_LATENCY_PROBE 与 NET_BENCH_CMD_LOG。
|
||
- 不需要 #undef s_addr:本套不 include ff.h(唯一会经 Windows 支路拉进 winsock.h
|
||
并把 s_addr 定义成 S_un.S_addr 的来源),真实 net_types.h 用的是自有的
|
||
struct net_in_addr 镜像类型,不受污染。
|
||
|
||
用法(仓库根目录):
|
||
python test/host/net_socket/run_unit_test.py
|
||
|
||
退出码:0=PASS / 1=测试失败(含常量闸不过)/ 2=环境错误。
|
||
|
||
设计依据:docs/网络/网络自愈架构设计.md §4.10、docs/测试说明/主机单测覆盖率口径.md §9
|
||
创建日期:2026-10-24
|
||
"""
|
||
import os
|
||
import re
|
||
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 必须最先(覆盖 ch395f.h / FreeRTOS.h / task.h / main.h / dbg_log.h);
|
||
# CMSIS_RTOS_V2 提供**真实** cmsis_os2.h;common 提供共享假芯片;NET 目录给真实
|
||
# net_socket.h / net_types.h / net_pending.h / net_listener.h / net_events.h;HERE 放本模块头。
|
||
# Drivers/BSP 放**最后**(只补 app_assert.h 这一个真头,其余同名头仍由 stub 先命中):
|
||
# net_socket.c 的 S12 断言试点(2 处 APP_ASSERT)要包含它。这里刻意**用真头不造桩**——
|
||
# 桩与真头漂移正是本 runner「常量闸」要防的那类自洽假绿(stub/dbg_log.h 那种必须做桩
|
||
# 的是平台层,断言宏不是平台层:它无状态、开关默认关,真头在宿主下只剩一条 #define)。
|
||
# 依据 docs/任务规划/测试与架构专项.md §2.1。
|
||
BUILD = TestBuild(
|
||
name="NET Socket Tests",
|
||
src=os.path.join(HERE, "net_socket_test.c"),
|
||
include_dirs=[
|
||
os.path.join(HERE, "stub"),
|
||
os.path.join(ROOT, "Middlewares", "Third_Party", "FreeRTOS", "Source",
|
||
"CMSIS_RTOS_V2"),
|
||
os.path.join(ROOT, "test", "host", "common"),
|
||
os.path.join(ROOT, "Drivers", "BSP", "NET"),
|
||
os.path.join(ROOT, "Drivers", "BSP"),
|
||
HERE,
|
||
],
|
||
# /wd4244:生产源里 ch395f_set_*(sockfd, ...) 一律传 int fd 给 uint8_t 形参,
|
||
# ARMCC 不报、MSVC /W4 每条都报一遍(20+ 条)——那是宿主与原编译器的诊断差异,
|
||
# 不是本批要盯的问题,屏蔽以保持门禁输出可读(不改生产代码去"消警")。
|
||
extra_flags=["/wd4244", "/wd4100"],
|
||
summary_marker="=== NET Socket Tests:",
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 常量闸:真头与假件的**同名** CH395F_* 常量数值必须逐一对齐
|
||
# ---------------------------------------------------------------------------
|
||
#
|
||
# 为什么必须有这根闸(2026-10-24 实测漂移):假件头自称"值抄自真实头,勿顺手改",
|
||
# 实际 CH395F_SOCKET_OPEN 写成 0x01(真值 0x05)、CH395F_TCP_ESTABLISHED 写成 0x05
|
||
# (真值 0x04)。host 编译里生产码看到的也是假宏,宏对宏自洽 → 三套全绿也照不出来。
|
||
# 谁一旦写**数字**断言或从假件抄常量,就拿到了与真芯片不同的值。
|
||
#
|
||
# 为什么只能文本核对:两个头在 host 编译里是互斥的(stub/ch395f.h 转发到假件),
|
||
# 跨头 _Static_assert 不可能同时看到两份定义 → 编译期闸不存在。
|
||
#
|
||
# 只做同名常量的数值比对,且**只比对两边都是纯十六进制/十进制字面量的**:
|
||
# 宏链(CH395F_SEND_BUF_SIZE_BYTES 那种引用其它宏的)跳过——它们由结构而不是值约束。
|
||
_DEFINE_RE = re.compile(r"^\s*#\s*define\s+(CH395F_[A-Z0-9_]+)\s+([^\s/]+)")
|
||
_NUM_RE = re.compile(r"^(0[xX][0-9a-fA-F]+|[0-9]+)(U|UL|ULL)?$")
|
||
|
||
|
||
def parse_consts(path):
|
||
"""{CH395F_xxx: int},只收字面量取值的宏;文件不存在返回 None。"""
|
||
if not os.path.isfile(path):
|
||
return None
|
||
out = {}
|
||
try:
|
||
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
||
for line in f:
|
||
m = _DEFINE_RE.match(line)
|
||
if not m:
|
||
continue
|
||
name, val = m.group(1), m.group(2)
|
||
if not _NUM_RE.match(val):
|
||
continue
|
||
digits = re.sub(r"[uUlL]+$", "", val)
|
||
try:
|
||
num = int(digits, 0)
|
||
except ValueError:
|
||
continue
|
||
out.setdefault(name, (num, line.rstrip("\n")))
|
||
except OSError:
|
||
return None
|
||
return out
|
||
|
||
|
||
def check_fake_chip_constants(verbose=True):
|
||
"""返回不一致的 (名字, 真头值, 假件值) 列表;两侧任一头读不到按环境问题报告。"""
|
||
real = parse_consts(os.path.join(ROOT, "Drivers", "BSP", "CH395F", "ch395f.h"))
|
||
fake = parse_consts(os.path.join(HERE, os.pardir, "common", "ch395f_fake.h"))
|
||
if real is None:
|
||
return None
|
||
if fake is None:
|
||
return None
|
||
drift = []
|
||
for name, (val, _line) in sorted(real.items()):
|
||
if name in fake and fake[name][0] != val:
|
||
drift.append((name, val, fake[name][0]))
|
||
if verbose and drift:
|
||
print("[DRIFT] 假件常量与真实 ch395f.h 不一致(host 编译里宏对宏自洽,照不出来):",
|
||
file=sys.stderr)
|
||
for name, rv, fv in drift:
|
||
print(" {} 真=0x{:02X} 假=0x{:02X}".format(name, rv, fv), file=sys.stderr)
|
||
return drift
|
||
|
||
|
||
if __name__ == "__main__":
|
||
drift = check_fake_chip_constants()
|
||
if drift is None:
|
||
print("[ENV] 常量闸无法读取真头或假件头(路径不对?)", file=sys.stderr)
|
||
sys.exit(2)
|
||
if drift:
|
||
print("[FAIL] 常量闸拦下 {} 处漂移:修 test/host/common/ch395f_fake.h 到真值"
|
||
"(出处见 Drivers/BSP/CH395F/ch395f.h 与 docs/芯片手册/CH395F.md)"
|
||
.format(len(drift)), file=sys.stderr)
|
||
sys.exit(1)
|
||
print("[OK] 常量闸:假件 CH395F_* 常量与真实 ch395f.h 同名项全对齐")
|
||
sys.exit(run_host_unit_test(BUILD))
|