①退出码为唯一权威判据: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 空)。
79 lines
3.4 KiB
GDScript
79 lines
3.4 KiB
GDScript
# tests/unit/test_kit.gd — 单元测试协议样板单点
|
|
# 覆盖:各套件 _check/_problems 样板(计数器 + PASS/FAIL 行 + 汇总退出)+
|
|
# 致命类用例 BAD 表目录夹具。
|
|
# 用法(--script 模式无 autoload/全局标识符——禁 class_name 裸引用,使用方 preload):
|
|
# const Kit := preload("res://tests/unit/test_kit.gd")
|
|
# Kit.check("断言描述", cond) # 计数 + 即时 PASS/FAIL 行
|
|
# Kit.check_quiet(cond, "描述") # 计数 + 失败收集(finish 时统一汇总——原 _problems 风格,原参序)
|
|
# quit(Kit.finish("suite_name")) # 汇总输出 + 返回退出码(0 全绿 / 1 有败 / 2 零断言)
|
|
# 计数器为 static(套件 = 独立 Godot 子进程恰一份生命周期,见 run_all.gd 执行形态);
|
|
# 单进程跑多套件须先 Kit.reset()。
|
|
extends RefCounted
|
|
|
|
static var checks: int = 0
|
|
static var failures: int = 0
|
|
static var problems: Array = []
|
|
|
|
|
|
# 主通道:计数 + 即时 PASS/FAIL 行(config_db/rng_service/damage_math/system_contracts/
|
|
# damage_controller/ui_font_asset/terrain_tile_sets 等套件原 _check 同款输出,描述在前)
|
|
static func check(desc: String, ok: bool) -> void:
|
|
checks += 1
|
|
if ok:
|
|
print(" [PASS] " + desc)
|
|
else:
|
|
failures += 1
|
|
push_error(" [FAIL] " + desc)
|
|
|
|
|
|
# 静默收集通道:失败只记入 problems(即时零刷屏),finish 统一汇总。
|
|
# 参序沿原函数式套件 treasure_items/breakpoint_resume 的 check(cond, msg)——条件在前,与主通道相反。
|
|
static func check_quiet(ok: bool, desc: String) -> void:
|
|
checks += 1
|
|
if not ok:
|
|
failures += 1
|
|
problems.append(desc)
|
|
|
|
|
|
# 汇总退出:返回退出码(调用方 `quit(Kit.finish(...))`)。pass_line = 成功尾行覆盖
|
|
#(保留原套件 PASS 摘要文案;缺省 = "all green (N checks)" 计数口径)。
|
|
# 失败路径:push_error 计数汇总 + problems 逐条 FAIL 行(print + push_error 双通道——原两风格并档)。
|
|
static func finish(suite: String, pass_line: String = "") -> int:
|
|
# 断言数下限(三硬闸之三):零断言的套件必须判红——「跑了一遍没崩」不等于测过。
|
|
# 退出码用 2(而非 1)与「断言失败」区分:致命类套件 manifest 期望 1,
|
|
# 零断言若也返回 1 会被 expect 协议当成预期结果放过。
|
|
if checks == 0:
|
|
push_error("[%s] 零断言——套件未执行任何断言(疑似脚本错误中断或误挂空壳),判红" % suite)
|
|
return 2
|
|
if failures > 0:
|
|
push_error("[%s] %d/%d 项失败" % [suite, failures, checks])
|
|
for p in problems:
|
|
print("[%s] FAIL — %s" % [suite, p])
|
|
push_error("[%s] FAIL — %s" % [suite, p])
|
|
return 1
|
|
print("[%s] %s" % [suite, pass_line if pass_line != "" else "all green (%d checks)" % checks])
|
|
return 0
|
|
|
|
|
|
# BAD 表夹具共享(原 reward_dangling_treasure/treasure_target_violation 各自复制的 _copy_tables):
|
|
# 整目录拷贝 src_dir → bad_dir(目录已建)。BAD_DIR 常量仍由各用例自持(名字互异——独立进程并行不互踩)。
|
|
static func copy_tables(src_dir: String, bad_dir: String) -> bool:
|
|
DirAccess.make_dir_recursive_absolute(bad_dir)
|
|
var dir := DirAccess.open(src_dir)
|
|
if dir == null:
|
|
return false
|
|
dir.list_dir_begin()
|
|
var f := dir.get_next()
|
|
while f != "":
|
|
if not dir.current_is_dir():
|
|
if dir.copy(src_dir + "/" + f, bad_dir + "/" + f) != OK:
|
|
return false
|
|
f = dir.get_next()
|
|
return true
|
|
|
|
|
|
static func reset() -> void:
|
|
checks = 0
|
|
failures = 0
|
|
problems = []
|