""" test_run.py — U4 scaffold / stage / ensure_play_spec / play 薄壳。 scaffold/stage/ensure_play_spec 快(确定性);test_play_real 真起 Chrome 跑九门(~1min,验 play 薄壳跑通、 verdict 由 serve-and-play.sh 产出且结构正确)。前台串行(非 LLM gen 循环)。 跑:cheap-worker/.venv/bin/python cheap-worker/tests/test_run.py """ import json import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/ import cheap_run def _clean_logic_from_template() -> str: """_template 合法游戏改一行(≠模板,过 check)。""" tmpl = (cheap_run._TEMPLATE_DIR / "src" / "game-logic.js").read_text(encoding="utf-8") return "// cheap-worker U4 测试:在 _template 合法游戏基础上改一行使 ≠模板\n" + tmpl def test_scaffold(): gid = "cheap-run-sc" r = cheap_run.scaffold(gid) assert r["ok"], r["output"] gd = cheap_run.game_dir(gid) assert (gd / "src" / "game-logic.js").exists() assert (gd / "src" / "host-config.js").exists() def test_stage(): gid = "cheap-run-st" assert cheap_run.scaffold(gid)["ok"] cheap_run.write_file(gid, f"game-runtime/games/amgen-{gid}/src/game-logic.js", _clean_logic_from_template()) assert cheap_run.check(gid)["ok"] assert cheap_run.build(gid)["ok"] rs = cheap_run.stage(gid) assert rs["ok"], rs["output"] dst = cheap_run.wg1_game_dir(gid) assert (dst / "bundle.iife.js").exists() and (dst / "index.html").exists() def test_ensure_play_spec_keycycle(): """state 无 targets → key-cycle driver。""" gid = "cheap-run-spec-kc" sp = cheap_run.wg1_game_dir(gid) / "play-spec.json" sp.unlink(missing_ok=True) r = cheap_run.ensure_play_spec(gid, {"phase": "menu", "score": 0}) assert r["wrote"] and r["driverType"] == "key-cycle" assert json.loads(sp.read_text(encoding="utf-8"))["driver"]["type"] == "key-cycle" def test_ensure_play_spec_taptargets(): """state 有 targets 数组 → tap-targets occupied driver。""" gid = "cheap-run-spec-tt" sp = cheap_run.wg1_game_dir(gid) / "play-spec.json" sp.unlink(missing_ok=True) r = cheap_run.ensure_play_spec(gid, {"phase": "play", "score": 5, "targets": [{"x": 1, "y": 2, "occupied": True}]}) assert r["wrote"] and r["driverType"] == "tap-targets" spec = json.loads(sp.read_text(encoding="utf-8")) assert spec["driver"]["type"] == "tap-targets" and "targets" in spec["exportState"] def test_ensure_play_spec_no_overwrite(): """已存在 play-spec(如别处已产)不覆盖。""" gid = "cheap-run-spec-no" sp = cheap_run.wg1_game_dir(gid) / "play-spec.json" sp.parent.mkdir(parents=True, exist_ok=True) sp.write_text('{"existing": true}', encoding="utf-8") r = cheap_run.ensure_play_spec(gid, {"targets": []}) assert not r["wrote"] and "不覆盖" in r["reason"] assert json.loads(sp.read_text(encoding="utf-8"))["existing"] is True def test_play_real(): """真起 Chrome 跑九门:验 play 薄壳跑通、verdict 由 serve-and-play.sh 产出且结构正确(不纠结 pass/fail)。""" gid = "cheap-run-play" assert cheap_run.scaffold(gid)["ok"] cheap_run.write_file(gid, f"game-runtime/games/amgen-{gid}/src/game-logic.js", _clean_logic_from_template()) assert cheap_run.check(gid)["ok"] assert cheap_run.build(gid)["ok"] assert cheap_run.stage(gid)["ok"] sm = cheap_run.smoke(gid) cheap_run.ensure_play_spec(gid, sm.get("state")) pr = cheap_run.play(gid) assert pr["verdict"] is not None, "play 应产 verdict.json,实际无:\n" + pr["raw"] assert isinstance(pr["verdict"].get("guards"), dict), "verdict 应含九门 guards:" + str(pr["verdict"])[:300] if __name__ == "__main__": _fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)] _failed = 0 for _fn in _fns: try: _fn() print(f" PASS {_fn.__name__}") except Exception as e: # noqa: BLE001 _failed += 1 print(f" FAIL {_fn.__name__}: {type(e).__name__}: {e}") print(f"\n{len(_fns) - _failed}/{len(_fns)} passed") sys.exit(1 if _failed else 0)