Files
STM32F4-Base/test/host/iec104_serv/run_unit_test.py
T
wandering1 957ec64645 test(APP/104): iec104_serv 模块(28 条,84.2%)——P1 十三块全部进分母
P1 收官批次。新增 test/host/iec104_serv/,iec104_serv.c 覆盖 117/139 = 84.2%。
做法:把生产源整个 #include 进 TU,直接驱动它的 7 个 static 辅助函数——**不跑
iec104_serv_task 的无限主循环**(那是 never-returning 的任务体,不适合单测;事件组
相关支路用 sys_evtHandle=NULL 走"不可用"分支)。链路层与 pts 层给假件:链路协议本身
有 test/host/iec104 的 97 条专测,本模块只管服务怎么活、怎么死、怎么重生。

钉住的契约:
- 建监听:2404 端口过 htons、绑 NET_INADDR_ANY;socket/bind/listen 任一失败都要把
  已经拿到的号关掉(漏关就是每次重试漏一个 socket 号,CH395F 只有 8 个)。
- **网络代次一变就拆掉旧连接与旧监听并重建**:现场改 IP 后端口必须在新地址上继续
  服务,这条不重建就是"设备换了 IP 之后 104 主站再也连不上"。
- accept 门控:监听未就绪不 accept;表项已失效时把监听号丢掉、下一轮重建。
- 单连接 socket 的断线语义:对端 FIN 时只关数据口,**监听必须留着**;数据号与监听
  号同一个时绝不关(关了就端口失联)。recv 返 -1 且 errno 是 WOULDBLOCK 是正常空转,
  不能当成错误丢连接;真错误则丢弃并**当场重建监听**,不留空窗。
- 判死用**连续**短写计数:一次成功即复位。写成累计就会把偶发拥塞误判成断线,主站
  看到的就是莫名掉线(连到 3 次才判死,I104_SEND_FAIL_MAX)。
- 32 分频探活:无可读数据时不是每轮都去收(省 CPU 预算),到点才收一次;对端 FIN 后
  复位计数器。协议状态掉回 IDLE 也要丢并重生(否则服务挂着但永不服务)。
- 时钟桥接两个溢出边界:epoch 秒超过 32 位一律拒绝且不碰时钟(截断写进 RTC 就是乱值);
  未对时(sys_clock_get 返回 UINT32_MAX)回 UINT64_MAX 哨兵而不是 0(否则对端会以为
  设备时间是 1970-01-01)。

主机侧兼容处理(写进桩注释):真实 App/util/sys_clock.h 会 include sd2506.h(RTC 芯片
驱动)一路拉到 HAL,PC 上编不动,故桩掉 sys_clock.h 但**两个签名与真实头逐字一致**;
104 的头散在 App 与 Drivers/BSP 的多个子目录(iec104_serv.h/iec104_link.h/iec104_pts.h/
task_watchdog.h/sys_events.h/prot_set.h/sd2506.h…),逐个手写 include 目录必漏且报错会
落在被测源上、极易误读成生产代码坏了——运行器改为**按目录实际是否含 .h 自动收集**。

自查:初跑红 2 条,都是我的场景问题(分频到点那轮 recv 返回 0 会走对端 FIN 支路,
应给 -1 + WOULDBLOCK;"重建失败丢监听号"必须同时让表项失效),改正后 28/28。

P1 收官合计:12 个新模块、**308 条断言**;套件 16 → **28** 全绿;files_tested 19 → **32**;
未触及 .c 47 → **34**;恒等式 34 = 0(P1 剩) + 5(P2) + 29(P3);分母 2,893 → **4,684** 行;
line=4020/4684(85.8%)、func=300/333(90.1%)。百分比"降"是摊薄不是回退(lftpd.c 64.3%、
lftpd_io.c 53.5% 这类只测主体的文件也进了分母),按 §5 口径该盯 files_tested/files_untested。
亮点:lftpd_inet.c **127/127 = 100%**、net_listener.c 99.2%、net_events.c 97.8%。
编号补建 TC-HOST-I104SRV(游标 041),TC-HOST 族数 20 → 22。§9 的落地进度表补齐并
统一为 NET 拆出的四块 ①②③④。

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

78 lines
3.8 KiB
Python
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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""IEC 104 上行服务生命周期(iec104_serv)主机侧单元测试运行器(L0.5,纯标准库)。
做什么:
用本机 MSVC 编译 test/host/iec104_serv/iec104_serv_test.c(以 #include 纳入生产源
App/task/iec104_serv.c),离线驱动它的全部 static 辅助函数(**不跑 never-returning
的任务主循环**),验证监听建立与失败回收、网络代次一变就重建监听、accept 门控与
表项失效自愈、单连接 socket 的断线语义、连续短写判死、活性分频探活、协议掉回 IDLE
时丢弃重生,以及时钟桥接的两个溢出边界。
为什么要主机侧单测(而不是只用台架):
104 主站掉线重连是现场最常见故障形态,但台架无法按需制造"改 IP 时正好有连接"
"对端 FIN 与 RST 的区别""连续三次短写";这几条处理错的后果都是端口永久失联或
会话卡死,只能等主站侧超时才发现。判死用**连续**短写计数(成功即复位),写成累计
就会把偶发拥塞误判成断线;时钟桥接的 >4G 秒必须拒绝、未对时必须回哨兵而不是 0;
活性分频写错就成了每轮多一次系统调用或 TESTFR 迟迟不发。
桩的边界:iec104_link.h / iec104_pts.h / prot_set.h / sys_clock.h / sys_events.h /
task_watchdog.h / net_types.h 用真实头(iec104_state_t、cfg 的函数指针签名、
TASK_WD_IEC104=7、SYS_EVT_* 位值一改即编译期报错);链路层与 pts 层给假件——链路
协议本身有 test/host/iec104 的 97 条专测,本模块只管服务怎么活、怎么死、怎么重生;
socket 面给可控假件(连 socket 号、发送返回、错误码、代次、门控都能注入)。
用法(仓库根目录):
python test/host/iec104_serv/run_unit_test.py
退出码:0=PASS / 1=测试失败 / 2=环境错误。
设计依据:docs/子系统设计/*104* 设计文档、AGENTS.md 模块约定(单连接 socket 断开语义、
错误码经任务通知值回传)、docs/测试说明/主机单测覆盖率口径.md §9
创建日期: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
def _header_dirs(base):
"""收集 base 下所有**真的放着 .h** 的目录。
104 服务要 include 的 iec104_serv.h / iec104_link.h / iec104_pts.h /
task_watchdog.h / sys_clock.h / sys_events.h / prot_set.h 散在 App 的多个
子目录里,逐个手写列表一定会漏(漏一个就是 C1083,而且报错落在被测源上,
很容易被误读成生产代码坏了)。这里按目录实际内容自动收集,多给几个 /I
对编译没有代价,却把"漏目录"这类脆错误消掉。
"""
dirs = []
for root, _sub, files in os.walk(base):
if any(f.endswith(".h") for f in files):
dirs.append(root)
return sorted(dirs)
BUILD = TestBuild(
name="IEC104 SERV Tests",
src=os.path.join(HERE, "iec104_serv_test.c"),
include_dirs=[
os.path.join(HERE, "stub"), # 必须先于真头目录(覆盖 net_socket.h 等)
] + _header_dirs(os.path.join(ROOT, "App")) +
# sys_clock.h 会拉 sd2506.h(Drivers/BSP/SD2506),BSP 的头也散在多个子目录里,
# 同样按实际内容收集。注意 stub 目录排在最前,桩依然优先于这些真头。
_header_dirs(os.path.join(ROOT, "Drivers", "BSP")) + [
os.path.join(ROOT, "Drivers", "BSP", "NET"),
HERE,
],
summary_marker="=== IEC104 SERV Tests:",
)
if __name__ == "__main__":
sys.exit(run_host_unit_test(BUILD))