①退出码为唯一权威判据:probe_gate.sh 八件探针(轻闸 4 + full 4)判定统一走 verdict 函数, 退出码≠0 即红,PASS 串降级为附加判据(ui_click_probe 本就走退出码语义,签名标记传它自己的 click_probe 串,不强加 PASS)。 ②脚本错误扫描闸件 tools/error_scan.py(标记 import 自 run_smoke.py,禁第二份清单): probe_gate 逐探针段扫日志;run_all 侧因 --script 子进程的 stdout 管道恒空(OS.execute 在本机 Godot/Windows 组合下拿不到输出,且 GUI 子系统 exe 的 stdout/stderr 句柄无效),改为整条命令 交给系统命令解释器重定向落盘再读,退出码原样回传——引擎二进制取 *_console 版(GODOT_BIN 优先)。 ③断言数下限:Kit.finish 在 checks==0 时返回专用退出码 2(不是 1——致命类套件期望 1,返回 1 会被 expect 协议放过);run_all 汇总校验实跑套件数 == manifest 条目数。 ④example_test.gd 退出生产 manifest(协议活样板,文件保留;同类先例 fog_lantern 摘除)。 变异验证(三反例 T0 判绿、T1 后全部判红):零断言套件 exit=2 判红/脚本错误套件命中 SCRIPT ERROR 判红/探针打印 PASS 后 quit(1) 判红「PASS 串不抵」。 本卡附带裁决落地(均已用户裁准): · fx_grant_gp 补进行来源断言豁免表(grant_segment 的专长 GP 入账行无棋盘来源格,每局触发 7 次 断言失败却因脚本错误不改退出码长期漏过;迭代 21 同款修过 fx_reveal)。 · manifest 增 negative 字段,仅 reward_dangling_treasure 一件(坏表是其测试目标,错误扫描误伤)。 · config_db_test 的 rows 定型数组赋值改 rows.assign——原写法触发 Invalid assignment 中断整个函数, 敌人表行集三条纪律 10 条断言从未执行过(31 处 check 实跑 21 条);修复后 26 条全跑,其中两条正向 断言见红,根因是夹具缺 depth-0 锚行(validate 的 anchor 规则),补锚行后全绿。 · system_contracts_test 的 RngStub/UnitContainerStub 空桩换真实实例(定型成员赋值报 type 空)。
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
r"""error_scan.py — 脚本错误扫描闸件(判定层通用,供本地闸各段复用)
|
|
|
|
用法: python tools/error_scan.py <日志文件> [...]
|
|
背景: GDScript 运行时脚本错误(SCRIPT ERROR / Parse Error)不改进程退出码、不计失败数,
|
|
故闸只能设在外部——本件把 run_smoke.py 的同一套判定抽出来给探针段与套件段共用。
|
|
错误标记与 tools/run_smoke.py 的 ERROR_MARKERS 同源(直接 import,禁复制第二份清单)。
|
|
退出码:0=无命中;1=命中脚本错误标记;2=用法错或日志不可读。
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from run_smoke import ERROR_MARKERS # noqa: E402 单一事实源,禁在本件另写一份
|
|
|
|
|
|
def main() -> int:
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
args = sys.argv[1:]
|
|
if not args:
|
|
print("[error_scan] 用法: python tools/error_scan.py <日志文件> [...]")
|
|
return 2
|
|
hits = []
|
|
for a in args:
|
|
p = Path(a)
|
|
if not p.is_file():
|
|
print("[error_scan] FAIL — 日志不可读: %s" % a)
|
|
return 2
|
|
for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
s = ln.strip()
|
|
if s and any(k in s for k in ERROR_MARKERS):
|
|
hits.append((p.name, s))
|
|
if hits:
|
|
print("[error_scan] FAIL — 脚本错误标记命中 %d 行(此类错误不改退出码,"
|
|
"且会中断所在函数的剩余断言)" % len(hits))
|
|
for name, s in hits[:5]:
|
|
print(" - [%s] %s" % (name, s[:160]))
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|