fix(test/udp): peer 换源端口上报方式并补 PC 回路自检(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 5m4s
CI / 主机侧单测 (MSVC) (push) Successful in 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m48s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s

- test/host/udp_queue_bench.py:Windows 上未 connect 的 UDP socket 调 getsockname()
  直接抛 WinError 10022(请求一个未连接套接句的名称),脚本在第一条用例就崩。
  源端口改为首次收到回显时打印——它本来就是「板端剥头/源地址判定对不对」的直接读数。
- 新增 test/host/udp_queue_bench_loopback.py:不用板子的 peer 自检,在 127.0.0.1 上放
  一个按板端阶段 16 同样三条规矩说话的假板子(原样回显、tag 0x04 只回 64 B、>1KB 按
  1024 拆片),先把步骤表 / 模式函数 / 分片预期验掉。2026-11-03 首跑 27/27 绿,
  实测拆片形状 [1024, 1] 与 [1024, 448]。
- CH395F_Test_Guide.md §12.6:运行方法补「先跑自检」这一步;>1024 B 的 UDP 拆片从
  「多收到一片就 FAIL」改成「实测观测量」(拼接后逐字节一致才算绿),并说清这是既有
  NET_SEND_CHUNK_MAX 行为、不是本轮引入;脚本表补一行。
- 板子自 19:19 起未再上电(ping 不通、SWD No target、串口静默),阶段 16 真机那一趟
  仍未跑,PR #74 的合并前提不变。
This commit is contained in:
2026-11-03 20:46:15 +08:00
parent 8a81fa9e41
commit b0a6394aa1
3 changed files with 173 additions and 30 deletions
+69 -22
View File
@@ -72,6 +72,7 @@ class Peer:
self.timeout = timeout
self.total = 0
self.failed = 0
self.ports = {}
self.t_start = time.time()
def stamp(self) -> str:
@@ -80,6 +81,17 @@ class Peer:
def log(self, msg: str) -> None:
print("{0} {1}".format(self.stamp(), msg), flush=True)
def note_port(self, sock: socket.socket, tag: int) -> None:
"""首次用到某条源套接句时把它的临时源端口打出来(真机日志要和这个对得上)。"""
key = id(sock)
if key in self.ports:
return
try:
self.ports[key] = sock.getsockname()[1]
self.log("TC 0x{0:02X} 走源端口 {1}".format(tag, self.ports[key]))
except OSError:
self.ports[key] = 0
def check(self, cond: bool, name: str) -> bool:
self.total += 1
self.log("[{0}] {1}".format("PASS" if cond else "FAIL", name))
@@ -114,31 +126,51 @@ class Peer:
.format(tag, port))
try:
got, addr = self._recv_after(sock, min(attempt, left))
self.note_port(sock, tag)
except socket.timeout:
if first_wait <= 0:
return self.check(False, "TC 0x{0:02X} 端口 {1}: 板端 {2:.0f}s 内无回显"
.format(tag, port, self.timeout))
self.log("TC 0x{0:02X}: 无回显,重发(首步剩余 {1:.0f}s)".format(tag, left))
# 一个数据报进必须只出一个数据报出:再等一小会儿看有没有第二片
again = self._recv_maybe(sock, 0.300)
extra = []
if again is not None:
extra.append(len(again[0]))
# 一个数据报进、一个数据报出——唯一例外是本步载荷超过芯片发送缓冲(1KB):
# 那是已登记的芯片限制(NET_SEND_CHUNK_MAX 本是 TCP 流的分块口径,UDP 上每块
# 各发一包,改造前后同一条路径)。>1KB 的步骤按「预期拆包」判,并把实测拆分打
# 出来:真机上哪天不拆了,这条一样会报——那就是芯片行为变了,值得知道。
chip_chunk = 1024
parts = [got]
if exp_len > chip_chunk:
while sum(len(x) for x in parts) < exp_len:
more = self._recv_maybe(sock, 1.0)
if more is None:
break
parts.append(more[0])
else:
again = self._recv_maybe(sock, 0.300)
if again is not None:
parts.append(again[0])
total = b"".join(parts)
ok_len = (len(got) == exp_len)
ok_pat = ok_len and (got == want)
self.check(ok_len, "TC 0x{0:02X} 端口 {1}: 回显 {2} 字节(期望 {3})".format(tag, port, len(got), exp_len))
ok_len = (len(total) == exp_len)
ok_pat = ok_len and (total == want)
self.check(ok_len, "TC 0x{0:02X} 端口 {1}: 回显 {2} 字节(期望 {3},收到 {4} 片)"
.format(tag, port, len(total), exp_len, len(parts)))
if not ok_len:
self.log(" 首 16 字节 = {0}".format(got[:16].hex()))
self.log(" 各片长度 = {0},首 16 字节 = {1}".format(
[len(x) for x in parts], total[:16].hex()))
self.check(ok_pat, "TC 0x{0:02X}: 载荷逐字节一致(模式 p[i]=(i*7+0x11)&0xFF)".format(tag))
if not ok_pat and ok_len:
bad = next((i for i in range(exp_len) if got[i] != want[i]), -1)
self.log(" 首个差异 @ 字节 {0}: 收到 0x{1:02X},应为 0x{2:02X}".format(bad, got[bad], want[bad]))
if extra:
self.check(False, "TC 0x{0:02X}: 一个请求收到 {1} 个额外数据报 {2}"
" —— 回显被分片了(芯片发送缓冲 = 1KB,>1KB 的分块会各发一包)"
.format(tag, len(extra), extra))
return ok_len and ok_pat and not extra
bad = next((i for i in range(exp_len) if total[i] != want[i]), -1)
self.log(" 首个差异 @ 字节 {0}: 收到 0x{1:02X},应为 0x{2:02X}"
.format(bad, total[bad], want[bad]))
if exp_len > chip_chunk:
self.check(len(parts) > 1 and len(parts[0]) == chip_chunk,
"TC 0x{0:02X}: >1KB 回显按芯片发送缓冲在 1024 处拆包(实测 {1})"
.format(tag, [len(x) for x in parts]))
elif len(parts) > 1:
self.check(False, "TC 0x{0:02X}: 未超 1KB 却收到 {1} 个数据报 {2}"
" —— 发送路径的分块口径变了"
.format(tag, len(parts) - 1, [len(x) for x in parts[1:]]))
return ok_len and ok_pat and (exp_len > chip_chunk or len(parts) == 1)
def wait_quiet(self, sock: socket.socket, seconds: float, name: str) -> bool:
"""板端超时探针期间本端必须保持静默,且不能有过期包漂进来。"""
@@ -180,6 +212,10 @@ def main() -> int:
help="对应 TC-NET-1606 的静默时长(板端探针预算 1 秒)")
ap.add_argument("--late", type=float, default=12.0,
help="对应 TC-NET-1607 的迟发时长:板端在这一步无限等包")
ap.add_argument("--port", type=int, default=0,
help="覆盖阶段 16 的首个监听端口(默认用板端 UDP_BENCH_PORT 6001)")
ap.add_argument("--port2", type=int, default=0,
help="覆盖重开 socket 后的第二个监听端口(默认用板端 UDP_BENCH_PORT2 6002)")
args = ap.parse_args()
# 板端 TC-NET-1607 把「等待时长」判据钉在 7000~25000 ms:本端迟发时刻必须落在窗内,
# 且必须先盖过板端 TC-NET-1606 那 1 秒超时探针,否则两步会互相污染。
@@ -191,12 +227,14 @@ def main() -> int:
return 2
peer = Peer(args.board, args.timeout)
sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socks = {"A": sock_a, "B": sock_b, "C": sock_c}
peer.log("对端 = {0},本机临时源端口 A={1} B={2} C={3}".format(
args.board, sock_a.getsockname()[1], sock_b.getsockname()[1], sock_c.getsockname()[1]))
# 三条独立源套接句 = 三个不同临时源端口,1604「换源端口」就是这么来的。
# 端口号不能在未 connect 时用 getsockname() 取(Windows 会 WinError 10022,
# 端口要到第一次 sendto 才分配),也不能 connect()——connect 会让内核丢掉
# 非对端的包,那正是这一步要验的东西。改成首次收包后再查,照样打得出来。
socks = {"A": socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
"B": socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
"C": socket.socket(socket.AF_INET, socket.SOCK_DGRAM)}
peer.log("对端 = {0},三条源套接句 A/B/C 的临时源端口在首次收包后打印".format(args.board))
# 板端每一步用新源端口的条件:1604(换源端口验形态恢复)及其后
step_sock = {
@@ -207,9 +245,18 @@ def main() -> int:
"TC-NET-1609c": "C", "TC-NET-1609d": "C", "TC-NET-1609e": "C",
}
# 端口覆盖:只在显式给了 --port/--port2 时替换,默认沿用板端编译期的 UDP_BENCH_PORT(_2)。
# 板端串口横幅会把自己的端口打出来,照抄那条命令时用它换掉非默认端口。
port_map = {}
if args.port:
port_map[6001] = args.port
if args.port2:
port_map[6002] = args.port2
try:
for name, tag, port, send_len, exp_len in STEPS:
s = socks[step_sock[name]]
port = port_map.get(port, port)
fw = args.first_wait if name == "TC-NET-1602" else 0.0
# 1607 之前先做 1606 的静默探针(板端自己判 -2,本端只需保持静默)
if name == "TC-NET-1607":