治「真相层缺失」病根(历史上模型文本/工具返回/门反馈零落盘、失败 run 被同 gid 重跑 覆盖,归因只能建在聚合数字上): - cheap_turns_sink.py:逐 turn 全文落 amgen-<gid>/turns.jsonl(模型文本/工具全参/ 工具返回/门续修反馈);observe-only middleware、best-effort,接线失败绝不阻断生成; CLI 与 Service 双路接线(cheap_run/cheap_service_app) - per-run 归档:同 gid 重跑前工程整体归档 _amgen-archive/<gid>-<ts>/,失败现场不再被覆盖 - evidence 清单化清理:按白名单保留,跨 run 残留(旧截图/旧 verdict)开跑即清,防证据串局 - failureLayer 三层归因:run-summary 落 generation/gate/gameplay 失败层字段, 聚合报表可分「生成没跑完/机械门挂/玩法层拒」 主会话亲验:两局真跑归档 179KB、turns 含门反馈全文;pytest 新增三个测试档全绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127 lines
5.6 KiB
Python
127 lines
5.6 KiB
Python
"""test_cheap_run_archive.py — W-AXIS 波1 per-run 归档 + evidence 清理清单化单测。
|
|
|
|
覆盖 cheap_run 的两件真相层落地(脱 agentscope,系统 Python 即可跑):
|
|
1. archive_prior_run:首跑无产物 → None;有上一 run → 把 amgen-<id>/ 与 _wg1-gen/<id>/ 整体【移】进
|
|
带时间戳归档位(原地清空、归档留全),返回指针;开关关 → None 且原地不动。
|
|
2. clean_stale_evidence:按固定清单清 _wg1-gen/<id>/evidence/ 残留(verdict-feedback 等全列),
|
|
兜底清 amgen-<id>/evidence/service-run-summary.json;缺文件不报错。
|
|
|
|
用 monkeypatch 把 cheap_run 的目录根指向临时沙箱,绝不碰真实 game-runtime/games。
|
|
跑法:cheap-worker/.venv/bin/python -m pytest cheap-worker/tests/test_cheap_run_archive.py -q
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_CW = os.path.dirname(_HERE)
|
|
_REPO_ROOT = os.path.dirname(_CW)
|
|
_GEN_WORKER = os.path.join(_REPO_ROOT, "tier2", "gen-worker")
|
|
for _p in (_CW, _GEN_WORKER):
|
|
if _p not in sys.path:
|
|
sys.path.insert(0, _p)
|
|
|
|
import cheap_run # noqa: E402
|
|
|
|
|
|
def _sandbox(monkeypatch, tmp_path):
|
|
"""把 cheap_run 的目录根重定向到临时沙箱(game_dir/wg1_game_dir/_ARCHIVE_DIR 全随之改)。"""
|
|
games = tmp_path / "games"
|
|
monkeypatch.setattr(cheap_run, "_GAMES_DIR", games)
|
|
monkeypatch.setattr(cheap_run, "_WG1_DIR", games / "_wg1-gen")
|
|
monkeypatch.setattr(cheap_run, "_ARCHIVE_DIR", games / "_amgen-archive")
|
|
return games
|
|
|
|
|
|
def _seed_prior_run(gid):
|
|
"""造一个「上一 run」的产物:amgen-<id>/{trace.jsonl,turns.jsonl} + _wg1-gen/<id>/evidence/verdict.json。"""
|
|
amgen = cheap_run.game_dir(gid)
|
|
(amgen / "src").mkdir(parents=True, exist_ok=True)
|
|
(amgen / "trace.jsonl").write_text("{}\n", encoding="utf-8")
|
|
(amgen / "turns.jsonl").write_text('{"kind":"model_turn"}\n', encoding="utf-8")
|
|
wg1_ev = cheap_run.wg1_game_dir(gid) / "evidence"
|
|
wg1_ev.mkdir(parents=True, exist_ok=True)
|
|
(wg1_ev / "verdict.json").write_text('{"pass":true}', encoding="utf-8")
|
|
return amgen, wg1_ev
|
|
|
|
|
|
# ── 归档 ─────────────────────────────────────────────────────────────────────
|
|
def test_archive_first_run_returns_none(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
assert cheap_run.archive_prior_run("t-first") is None # 无上一 run 可归档
|
|
|
|
|
|
def test_archive_moves_amgen_and_wg1(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
gid = "t-move"
|
|
amgen, wg1_ev = _seed_prior_run(gid)
|
|
ptr = cheap_run.archive_prior_run(gid)
|
|
assert ptr is not None
|
|
arch = Path(ptr)
|
|
# 原地清空(移走,不复制)。
|
|
assert not amgen.exists(), "amgen-<id>/ 应被移走"
|
|
assert not cheap_run.wg1_game_dir(gid).exists(), "_wg1-gen/<id>/ 应被移走"
|
|
# 归档留全(trace/turns/verdict 都在归档位)。
|
|
assert (arch / "amgen" / "trace.jsonl").exists()
|
|
assert (arch / "amgen" / "turns.jsonl").exists()
|
|
assert (arch / "wg1" / "evidence" / "verdict.json").exists()
|
|
|
|
|
|
def test_archive_twice_distinct_slots(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
gid = "t-twice"
|
|
_seed_prior_run(gid)
|
|
p1 = cheap_run.archive_prior_run(gid)
|
|
_seed_prior_run(gid) # 再造一次上一 run
|
|
p2 = cheap_run.archive_prior_run(gid)
|
|
assert p1 and p2 and p1 != p2, "两次归档必须落不同槽位(同秒加 -n 后缀)"
|
|
|
|
|
|
def test_archive_disabled_noop(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
monkeypatch.setenv("CHEAP_ARCHIVE_ENABLED", "0")
|
|
gid = "t-off"
|
|
amgen, _ = _seed_prior_run(gid)
|
|
assert cheap_run.archive_prior_run(gid) is None
|
|
assert amgen.exists(), "归档关时原地不动(退回旧覆盖行为)"
|
|
|
|
|
|
# ── evidence 清理清单化 ──────────────────────────────────────────────────────
|
|
def test_clean_stale_evidence_removes_checklist(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
gid = "t-clean"
|
|
wg1_ev = cheap_run.wg1_game_dir(gid) / "evidence"
|
|
wg1_ev.mkdir(parents=True, exist_ok=True)
|
|
# 造全清单残留 + 一个不在清单的文件(不该被清)。
|
|
for name in ("verdict.json", "verdict-feedback.json", "game-log.json",
|
|
"first-paint.png", "after-play.png"):
|
|
(wg1_ev / name).write_text("x", encoding="utf-8")
|
|
(wg1_ev / "keep-me.txt").write_text("keep", encoding="utf-8")
|
|
amgen_ev = cheap_run.game_dir(gid) / "evidence"
|
|
amgen_ev.mkdir(parents=True, exist_ok=True)
|
|
(amgen_ev / "service-run-summary.json").write_text("{}", encoding="utf-8")
|
|
|
|
out = cheap_run.clean_stale_evidence(gid)
|
|
# 关键残留元凶 verdict-feedback.json 必被清(旧红线③不清它=残留混杂根因)。
|
|
assert not (wg1_ev / "verdict-feedback.json").exists()
|
|
assert not (wg1_ev / "verdict.json").exists()
|
|
assert not (wg1_ev / "game-log.json").exists()
|
|
assert not (wg1_ev / "first-paint.png").exists()
|
|
assert not (amgen_ev / "service-run-summary.json").exists()
|
|
# 清单外文件保留。
|
|
assert (wg1_ev / "keep-me.txt").exists()
|
|
assert "wg1/evidence/verdict-feedback.json" in out["removed"]
|
|
|
|
|
|
def test_clean_stale_evidence_missing_ok(monkeypatch, tmp_path):
|
|
_sandbox(monkeypatch, tmp_path)
|
|
# evidence 目录都不存在 → 不报错、removed 空。
|
|
out = cheap_run.clean_stale_evidence("t-none")
|
|
assert out == {"removed": []}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import pytest
|
|
sys.exit(pytest.main([__file__, "-q"]))
|