# -*- coding: utf-8 -*- r'''把 _tests 里所有**没被套件引用**的测试跑一遍,看哪些还活着。 == 为什么 == 实测发现:_tests 目录里有 52 个 test_*.py,而 verify_all.ps1 只引用了 32 个 —— **20 个测试的结果是未知的**。 这个数字很危险:一个测试不在套件里,就永远不会有人发现它是红的。 刚才的 test_admin_delete.py 就是活例子 —— 它因为管理页换了路径 (/ -> /zhongzhuanzhan)而失败很久了,但没人知道,因为没人跑它。 这个脚本把孤儿测试逐个跑,超时可控,输出一张表: 能跑通 / 失败 / 卡住 / 需要外部资源 然后按结果决定收进套件还是删掉。 ''' from __future__ import annotations import re import subprocess import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parent.parent TESTS = ROOT / "_tests" SUITE = TESTS / "verify_all.ps1" TIMEOUT = 180 # 每个测试最多跑 3 分钟 # ⚠ 必须给每个测试一个**独立的锁端口**。 # # 用户自己的被控端(Desktop\ykxmz\ykxmz.exe)平时就开着, # 占着默认的 8767/TCP + 8766/UDP。测试实例如果也去抢, # 就会启动失败,表现成"连接被拒""登记不上", # 看起来像产品坏了,实际是端口被占。 # # 这个坑咬过很多次:test_relay_local.py 单独跑 21/21 全过, # 放在一起跑就红 —— 就是因为没隔离锁端口。 LOCK_PORT = "18767" def referenced() -> set: text = SUITE.read_text(encoding="utf-8", errors="replace") return set(re.findall(r"_tests[\\/]([\w]+\.py)", text)) def main() -> int: ref = referenced() all_tests = sorted(p.name for p in TESTS.glob("test_*.py")) orphans = [t for t in all_tests if t not in ref] print("=" * 66) print(" 套件引用了 %d 个,目录里有 %d 个,孤儿 %d 个" % (len(ref), len(all_tests), len(orphans))) print("=" * 66) # 这些明显需要外部资源(真机、手机、线上),跑不了 NEEDS_EXTERNAL = { "test_admin_live.py": "需要线上中转 + 设备", } results = [] for name in orphans: if name in NEEDS_EXTERNAL: results.append((name, "外部资源", NEEDS_EXTERNAL[name])) print(" ⊘ %-34s(%s)" % (name, NEEDS_EXTERNAL[name])) continue t0 = time.time() try: import os env = dict(os.environ) env["YKXMZ_LOCK_PORT"] = LOCK_PORT env["PYTHONIOENCODING"] = "utf-8" env["PYTHONUNBUFFERED"] = "1" proc = subprocess.run( [sys.executable, "-u", str(TESTS / name)], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=TIMEOUT, env=env) out = (proc.stdout or "") + (proc.stderr or "") dt = time.time() - t0 m = re.search(r"通过\s*(\d+)\s*项[,,]\s*失败\s*(\d+)\s*项", out) if proc.returncode == 0: detail = ("%s 项全过" % m.group(1)) if m else "退出码 0" results.append((name, "通过", detail)) print(" ✅ %-34s %5.1fs %s" % (name, dt, detail)) else: if m: detail = "失败 %s/%s 项" % ( m.group(2), int(m.group(1)) + int(m.group(2))) else: # 抓最后一行像错误的东西 tail = [l for l in out.strip().splitlines() if l.strip()] detail = (tail[-1][:60] if tail else "退出码 %d" % proc.returncode) results.append((name, "失败", detail)) print(" ❌ %-34s %5.1fs %s" % (name, dt, detail)) except subprocess.TimeoutExpired: results.append((name, "超时", ">%ds" % TIMEOUT)) print(" ⏱ %-34s 超时 >%ds(可能卡在等端口/等设备)" % (name, TIMEOUT)) except Exception as exc: # noqa: BLE001 results.append((name, "异常", str(exc)[:60])) print(" ⚠ %-34s %s" % (name, str(exc)[:60])) print() print("=" * 66) print(" 汇总") print("=" * 66) for label in ("通过", "失败", "超时", "外部资源", "异常"): group = [r for r in results if r[1] == label] if group: print(" %s(%d 个):" % (label, len(group))) for name, _s, detail in group: print(" %-34s %s" % (name, detail)) return 0 if __name__ == "__main__": sys.exit(main())