WU-A 迁移面 spike:便宜档生成核心(多轮 ReAct + 五工具沙箱 + done 门)用 Python 在新建 cheap-worker/ 重写成 AgentScope agent,import 复用 tier2 框架层(model client/四道熔断/历史压缩/observability), check/build/九门 shell-out 现有 node/cjs(不重写 esbuild/CDP/形状门)。 - U1 地基:跨包 sys.path import tier2 worker + 代理旁路 + key 从内网文档注入 env + M3 装配 - U2 五工具:read/list/write Python 实现 + check/build shell-out(形状门三态与 Node 一致) - U3 LittleJS system prompt:prompt.mjs Python 化(两条 footgun 正反例 + 输入契约) - U4 scaffold/stage/play 薄壳 + ensure_play_spec(据 state 形态产 play-spec 治链路假绿) - U5 ReAct 主编排:内置 Agent+ReActConfig+外层有界 resume + done 门 + 收口 stage/smoke/九门 - U6 对照 Node:产物形态 + 九门逐门 + 过门率对比 验收:同 brief Python 新路 vs Node 旧路,产物都真 src/ 6 文件、九门都 9/9 全 PASS、过门率 1.0=1.0 → spikePass=True。端到端单局 attempt 1 一次收敛、148s。落点 cheap-worker/ 对 tier2/amodel-gen 零改动(结构性零回归)。测试 38/38 全绿。 origin: docs/plans/2026-06-26-001-feat-cheap-worker-python-spike-plan.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
4.2 KiB
Python
104 lines
4.2 KiB
Python
"""
|
||
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)
|