games-development-ai/cheap-worker/tests/test_deadloop_fix_loop.py
lili 29da237307 test(cheap-gen): 死圈修复外科修复回路确定性集成测试(fix①-A × fix②-check 成环)
n=5 收敛环的确定性内核(不含大模型,只证工具能组合成环):scaffold 合规起点 → 注入一处
裸 Math.random(模拟模型自造回退)→ check 经真 node tools.mjs 报「红线 + 确切行号 +
edit_file 补救指向」→ edit_file 精修那一行 → check 红线消失、整体回 ok=True。

证的正是死圈出口:过去=产物带违规 + 只有整文件覆盖 write_file + 报错无行号 → agent 整文件
重写、漏 path、连撞熔断;现在=报错点名到行 + 给 edit_file → 一行精修即出圈。「大模型会不会
照做」交真环境 n=5 收敛环。1 测过(scaffold→inject→check→edit→check),exit=0。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 02:28:51 -07:00

71 lines
3.4 KiB
Python

"""test_deadloop_fix_loop.py — 死圈修复外科修复回路的【确定性】集成测试(2026-07-08)。
这是 n=5 收敛环的确定性内核(不含大模型,只证工具能组合成环):
scaffold(合规起点) → 注入一处裸 Math.random 违规(模拟模型自造回退) → check 经真 node tools.mjs
报「红线 + 确切行号 + edit_file 补救指向」 → edit_file 精修那一行 → check 红线消失。
它把 fix①-A(增量 edit 工具)与 fix②-check(报错带行号 + edit_file 指向)串起来验:
过去死圈 = 产物带违规、只有整文件覆盖的 write_file、报错还没行号 → agent 只能整文件重写、漏 path、
连撞熔断。现在 = 报错点名到行 + 给 edit_file、agent 一行精修即出圈。本测证这条回路在工具层真能闭合;
「大模型会不会照做」交真环境 n=5 收敛环(见 [[cheap-gen-deadloop-fix]])。
用真 games/ 下临时 game_dir(amgen-editloop*)+ 真 node tools.mjs check,teardown 清理,不碰真产物。
跑:cheap-worker/.venv/bin/python -m pytest cheap-worker/tests/test_deadloop_fix_loop.py -q
"""
import shutil
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/
import cheap_run # noqa: E402
import pytest # noqa: E402
_GID = "editloop001"
_LOGIC_REL = f"game-runtime/games/amgen-{_GID}/src/game-logic.js"
_ANCHOR = "export function createGame({ plugins, bundle, viewport }) {"
@pytest.fixture(autouse=True)
def _cleanup():
try:
yield
finally:
shutil.rmtree(cheap_run.game_dir(_GID), ignore_errors=True)
def test_check_edit_check_loop_surgical_fix():
# 1) scaffold 合规起点(点圆得分模板)
sc = cheap_run.scaffold(_GID)
assert sc["ok"], sc
logic_abs = cheap_run.game_dir(_GID) / "src" / "game-logic.js"
# 2) 注入一处裸 Math.random 违规:在 createGame 体首新增一行(新增而非替换——修回后文件仍异于模板、
# 不撞"与 _template 完全相同"门,回路终点是真过门态而非退回模板)。
src = logic_abs.read_text(encoding="utf-8")
assert src.count(_ANCHOR) == 1, "模板 createGame 锚点应唯一"
injected = src.replace(_ANCHOR, _ANCHOR + "\n const _probe = Math.random(); // 集成测试注入违规", 1)
w = cheap_run.write_file(_GID, _LOGIC_REL, injected)
assert w["ok"], w
# 3) check 应 FAIL,且报红线 + 确切行号 + edit_file 补救指向(这正是 agent 出圈要读到的)
c1 = cheap_run.check(_GID)
assert not c1["ok"], f"注入违规后 check 应 FAIL:{c1['output']}"
out = c1["output"]
assert "红线" in out and "Math" in out, f"应报 Math.random 红线:{out}"
assert "" in out and "" in out, f"红线应带确切行号:{out}"
assert "edit_file" in out, f"红线应给 edit_file 补救指向:{out}"
# 4) edit_file 精修那一行:Math.random() → 常量(消红线、作用域安全)
e = cheap_run.edit_file(_GID, _LOGIC_REL, "const _probe = Math.random();", "const _probe = 0.5;")
assert e["ok"], e
# 5) check 红线消失,回路成环(且整体回到过门态——注入行已修、文件仍异于模板)
c2 = cheap_run.check(_GID)
assert "红线" not in c2["output"], f"精修后红线应消失:{c2['output']}"
assert c2["ok"], f"精修后应整体过门(回到 ok=True):{c2['output']}"
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-q"]))