Files
gplan-2/game/tests/unit/contracts/source_guard_test.gd
T

125 lines
6.3 KiB
GDScript
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.
# tests/unit/contracts/source_guard_test.gd — 仓库自身源码契约单元测试宿主
# 断言面 = 两套(批 B 断言迁移自 smoke_all.gd,G12 施工组;被测对象=仓库自身源文件,
# 主会话裁决五新设 source_guard 子目录归「契约」语义):
# 类型闸(project.godot warning 配置在位 + 审计器闸机接线)
# 卫生件 grep 闸(产品码字符串 Callable/死代码/GP 影子账残留零命中 + 层序落位单口)
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;被测面是仓库文件直读
# (FileAccess/DirAccess/project.godot 设置),零表夹具零 RNG。
# 运行:godot --headless --path game --script res://tests/unit/contracts/source_guard_test.gd
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
# V1d 层序单口豁免(组件内部自身布局重排,非根容器层序):文件 → 允许 move_child 的函数名。
# 登记面封闭——未登记的函数内出现 move_child 一律判红,新增文件默认受管。
const LAYER_REORDER_EXEMPT: Dictionary = {
"res://presentation/views/ui_kit.gd": "add_footer",
}
func _init() -> void:
# 套执行序沿源编排序(_split_tail 尾段调用序)
_smoke_type_gate()
_smoke_hygiene_gates()
quit(Kit.finish("source_guard_test"))
# ---- 类型闸断言(V7——project.godot warning 配置在位〔源码断言〕+审计器脚本在位;
# 审计器归零判定走 static_scan S9 闸机,此处兜配置面)----
func _smoke_type_gate() -> void:
# 判据出处: design/engineering/code-conventions.md:41 (封装边界——类型闸源码扫描组)
var pj: String = FileAccess.get_file_as_string("res://project.godot")
Kit.check("V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕",
pj.contains("gdscript/warnings/untyped_declaration=1"))
var scan_src: String = FileAccess.get_file_as_string("res://tests/static_scan.sh")
Kit.check("V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕",
scan_src.contains("audit_type_hints.py"))
# ---- 卫生件 grep 闸(V8——死代码/字符串 Callable/影子账残留零命中;源码扫描面)----
func _smoke_hygiene_gates() -> void:
# 判据出处: design/engineering/code-conventions.md:54 (模态覆盖层层级与登记纪律——卫生闸组)
var callables := 0
for d in ["res://core", "res://presentation", "res://data"]:
var dir := DirAccess.open(d)
if dir == null:
continue
dir.list_dir_begin()
var f := dir.get_next()
while f != "":
if dir.current_is_dir() and not f.begins_with("."):
var sub := DirAccess.open(d + "/" + f)
if sub != null:
sub.list_dir_begin()
var g := sub.get_next()
while g != "":
if g.ends_with(".gd"):
var t: String = FileAccess.get_file_as_string(d + "/" + f + "/" + g)
callables += t.count("Callable(self, \"")
g = sub.get_next()
elif f.ends_with(".gd"):
var t2: String = FileAccess.get_file_as_string(d + "/" + f)
callables += t2.count("Callable(self, \"")
f = dir.get_next()
Kit.check("V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕",
callables == 0)
Kit.check("V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕",
not FileAccess.file_exists("res://core/feats_engine.gd"))
var bo: String = FileAccess.get_file_as_string("res://core/battle_orchestrator.gd")
Kit.check("V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕",
not bo.contains("func _attune_row"))
var sm: String = FileAccess.get_file_as_string("res://core/save_manager.gd")
Kit.check("V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕",
not sm.contains("func attunement_counters"))
var mv: String = FileAccess.get_file_as_string("res://presentation/main.gd")
Kit.check("V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕",
not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger"))
# V1d 层序落位单口:递归扫全 presentation(含 views/、scenes/)——move_child 只允许出现在
# ui_registry.gd 的 apply_layer_contract 函数体内,或 LAYER_REORDER_EXEMPT 登记的组件内重排函数内
# (合同装配单口——出现第二处落位=硬编码层序,须改合同表而非加调用)
var scanned: int = 0
var outside_calls: int = 0
for fn_path: String in _list_scripts("res://presentation"):
var src: String = FileAccess.get_file_as_string(fn_path)
scanned += 1
outside_calls += _count_outside_allowed(src, fn_path)
Kit.check("V1d 层序落位单口(presentation %d 文件——落位单口与登记豁免之外 move_child 调用点=%d 应=0)〔注册面 V1〕" % [scanned, outside_calls],
scanned > 0 and outside_calls == 0)
# 允许段之外逐个文件数 move_child:遇任意函数声明行重判「当前是否在允许函数内」,
# 内部类方法与缩进形态都不影响定位;remove_child( 尾部与 move_child( 字面重合,先剔除再计数。
static func _count_outside_allowed(src: String, fn_path: String) -> int:
var allowed: PackedStringArray = PackedStringArray()
if fn_path.ends_with("ui_registry.gd"):
allowed.append("apply_layer_contract")
if LAYER_REORDER_EXEMPT.has(fn_path):
allowed.append(String(LAYER_REORDER_EXEMPT[fn_path]))
var in_allowed: bool = false
var calls: int = 0
for line: String in src.split("\n"):
var t: String = line.strip_edges()
if t.begins_with("func "):
in_allowed = allowed.has(t.substr(5).split("(")[0].strip_edges())
if not in_allowed:
calls += line.replace("remove_child(", "").count("move_child(")
return calls
# 目录 .gd 清单(DirAccess 递归——presentation 下 views/、scenes/ 等子目录同样在册;扫描面>0 前置由调用侧断言承载)
static func _list_scripts(dir_path: String) -> PackedStringArray:
var out: PackedStringArray = PackedStringArray()
var dir := DirAccess.open(dir_path)
if dir == null:
return out
dir.list_dir_begin()
var fname := dir.get_next()
while fname != "":
if dir.current_is_dir() and not fname.begins_with("."):
out.append_array(_list_scripts(dir_path + "/" + fname))
elif fname.ends_with(".gd"):
out.append(dir_path + "/" + fname)
fname = dir.get_next()
dir.list_dir_end()
return out