Files
dwarf-fortress-annals/tests/test_gate.py
T
Chen Yi e0073e90bd 第 1 卷(人物主线):第 1–3 集 + 人物表
主角线:Guspu Frillyknots / Ral Fastenhatchets / Alath Blottedmine
由线索挖掘器自动选定,按因果链切集,经 5 维度自评闸门(≥12 分)后入库。
2026-10-05 19:28:49 +08:00

126 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pyright: reportMissingImports=false, reportAttributeAccessIssue=false
# 说明:LSP 的 Python 环境看不到本项目包,会把包内 import 误报为缺失模块。
"""闸门流程的回归测试(不调用真实模型,用假评分器)。
对应 t4 契约:<12 先重写一次、仍不合格才换线索、失败上限、换线原因写入 notes/。
真实模型能否真的判出平淡,由 scripts/gate-selfcheck.py 负责证明。
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from dfannals import gate # noqa: E402
def verdict(total: int, reason: str = "测试用") -> gate.Verdict:
"""按总分构造一个评分(维度均分,保持与真实结构一致)。"""
keys = [k for k, _, _ in gate.DIMENSIONS]
per, rest = divmod(total, len(keys))
scores = {k: min(gate.MAX_SCORE, per + (1 if i < rest else 0)) for i, k in enumerate(keys)}
return gate.Verdict(total=sum(scores.values()), scores=scores, reason=reason)
def test_passes_first_try() -> None:
calls: list[int] = []
def write(attempt: int, _prev) -> str:
calls.append(attempt)
return "好稿"
text, run = gate.gate_episode("001", write, evaluate_fn=lambda _t: verdict(20))
assert text == "好稿"
assert run.accepted and run.rewrites == 0
assert calls == [0], "通过后不应重写"
def test_rewrite_once_then_pass() -> None:
"""<12 分先重写一次,重写后合格就通过。"""
seq = [verdict(5), verdict(18)]
seen: list[int] = []
def write(attempt: int, prev) -> str:
seen.append(attempt)
if attempt == 1:
assert prev is not None and prev.total == 5, "重写时要带上上一稿评分"
return f"第{attempt}稿"
text, run = gate.gate_episode("002", write, evaluate_fn=lambda _t: seq.pop(0))
assert text == "第1稿" and run.accepted
assert run.rewrites == 1 and seen == [0, 1]
assert [v.total for v in run.verdicts] == [5, 18]
def test_not_accepted_after_max_rewrites() -> None:
"""一直不合格:重写次数不得越过上限,且标记为未通过。"""
runs: list[int] = []
text, run = gate.gate_episode(
"003",
lambda attempt, _p: (runs.append(attempt), "稿")[1],
evaluate_fn=lambda _t: verdict(3),
)
assert not run.accepted
assert run.rewrites == gate.MAX_REWRITES
assert runs == [0, 1], f"实际尝试次数:{runs}"
assert len(run.verdicts) == gate.MAX_REWRITES + 1
def test_render_shows_weak_dimensions() -> None:
v = verdict(6, "全是重复对战")
text = v.render()
assert "判平淡" in text and "全是重复对战" in text and "6/" in text
assert len(v.weak()) == 2
def test_switch_note_records_reason(tmp_path: Path) -> None:
"""放弃线索必须记下原因,且写入仓库 notes/ 附录。"""
path = gate.append_switch_note(
tmp_path,
world_name="Mon Sagus",
members=["Guspu Frillyknots", "Ral Fastenhatchets"],
span="193–239(46 年)",
verdict=verdict(5, "只有阴谋事件重复,没有立场变化"),
reason="重写一次后仍判平淡",
)
assert path.exists() and path.parent.name == "notes"
body = path.read_text(encoding="utf-8")
assert "Guspu Frillyknots" in body and "重写一次后仍判平淡" in body
assert "只有阴谋事件重复" in body
def test_append_switch_note_is_append_only(tmp_path: Path) -> None:
gate.append_switch_note(tmp_path, world_name="W", members=["A"], span="1–2",
verdict=verdict(4), reason="第一次放弃")
gate.append_switch_note(tmp_path, world_name="W", members=["B"], span="3–4",
verdict=verdict(4), reason="第二次放弃")
body = (tmp_path / "notes" / gate.SWITCH_LOG).read_text(encoding="utf-8")
assert "第一次放弃" in body and "第二次放弃" in body
assert body.count("# 被放弃的线索") == 1, "表头只应写一次"
def _main() -> int:
import tempfile
plain = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
failed = 0
for fn in plain:
try:
if fn.__code__.co_argcount:
with tempfile.TemporaryDirectory() as d:
fn(Path(d))
else:
fn()
print(f" ✓ {fn.__name__}")
except AssertionError as exc:
failed += 1
print(f" ✗ {fn.__name__}: {exc}")
print(f"\n{len(plain) - failed}/{len(plain)} 通过")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(_main())