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>
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""
|
||
test_compare.py — U6 对比逻辑单测(mock verdict + 产物形态识别,不依赖真跑)。
|
||
|
||
跑:cheap-worker/.venv/bin/python cheap-worker/tests/test_compare.py
|
||
"""
|
||
|
||
import sys
|
||
import tempfile
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # → cheap-worker/
|
||
import compare_node as C
|
||
|
||
|
||
def test_gates_from_verdict():
|
||
v = {"pass": True, "guards": {"A_boot": {"pass": True}, "H_progress": {"pass": False}}}
|
||
assert C.gates_from_verdict(v) == {"A_boot": True, "H_progress": False}
|
||
assert C.gates_from_verdict(None) == {}
|
||
|
||
|
||
def test_compare_equivalent():
|
||
"""两路九门全过 + Python 真 src/ → spike 成功。"""
|
||
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": True}, py_real_src=True)
|
||
assert r["spikePass"] is True
|
||
assert r["pyPassRate"] == 1.0 and r["nodePassRate"] == 1.0
|
||
|
||
|
||
def test_compare_py_better():
|
||
"""Python 过门率高于 Node → spike 成功,delta>0。"""
|
||
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": False}, py_real_src=True)
|
||
assert r["spikePass"] is True and r["rateDelta"] > 0
|
||
|
||
|
||
def test_compare_py_worse():
|
||
"""Python 过门率低于 Node → 未达标,delta<0。"""
|
||
r = C.compare_logic({"A": True, "B": False}, {"A": True, "B": True}, py_real_src=True)
|
||
assert r["spikePass"] is False and r["rateDelta"] < 0
|
||
|
||
|
||
def test_compare_py_not_real_src():
|
||
"""Python 非真 src/ 多文件 → 即使过门率相等也判未达标(doc↔code 兑现门)。"""
|
||
r = C.compare_logic({"A": True, "B": True}, {"A": True, "B": True}, py_real_src=False)
|
||
assert r["spikePass"] is False
|
||
|
||
|
||
def test_src_shape_real_multifile():
|
||
with tempfile.TemporaryDirectory() as d:
|
||
src = Path(d) / "src"
|
||
src.mkdir()
|
||
(src / "game-logic.js").write_text("x", encoding="utf-8")
|
||
(src / "host-config.js").write_text("y", encoding="utf-8")
|
||
s = C.src_shape(Path(d))
|
||
assert s["realSrcMultifile"] is True and s["jsCount"] == 2
|
||
|
||
|
||
def test_src_shape_single_file():
|
||
with tempfile.TemporaryDirectory() as d:
|
||
src = Path(d) / "src"
|
||
src.mkdir()
|
||
(src / "game-logic.js").write_text("x", encoding="utf-8") # 单文件 → 非"多文件"
|
||
s = C.src_shape(Path(d))
|
||
assert s["realSrcMultifile"] is False and s["jsCount"] == 1
|
||
|
||
|
||
def test_src_shape_empty():
|
||
with tempfile.TemporaryDirectory() as d:
|
||
s = C.src_shape(Path(d)) # 无 src/ → 空壳
|
||
assert s["realSrcMultifile"] is False and s["jsCount"] == 0
|
||
|
||
|
||
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)
|