Chessistics/tools/automation/smoke.py

115 lines
4.1 KiB
Python
Raw Normal View History

"""End-to-end smoke test for the automation harness.
Runs a scripted playthrough: load mission 0, place a rook, take screenshots,
step forward, and validate state at each step. Fails hard on any anomaly.
"""
from __future__ import annotations
import hashlib
import json
import sys
from pathlib import Path
from harness import Harness, HarnessError
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main() -> int:
with Harness.launch(run_name="smoke") as h:
print(f"\n[smoke] run dir: {h.root}\n")
# --- 1. initial state ---
print("[smoke] load_mission")
state = h.load_mission("campaign_01", 0)
assert state["width"] > 0 and state["height"] > 0, state
assert state["phase"] == "Paused", f"expected Paused, got {state['phase']}"
assert state["remainingStock"], "stock is empty"
print(f" board {state['width']}x{state['height']}, phase={state['phase']}, stock={state['remainingStock']}")
shot1 = h.screenshot("01_loaded")
assert shot1.exists(), shot1
print(f" screenshot -> {shot1.name}")
# --- 2. place a piece ---
snap_before = h.state()
stock_before = dict(snap_before["remainingStock"])
first_kind = next(iter(stock_before))
print(f"[smoke] try placing {first_kind} at (0,0)->(0,0)")
# Find any legal placement. Simplest: for a Rook-ish piece, same cell start=end
# isn't legal — try adjacent cells. We scan for one kind we have in stock and a
# simple legal move.
placed = None
for kind in stock_before:
for s in [(0, 0), (1, 0), (0, 1)]:
for e in [(0, 1), (1, 1), (2, 0), (0, 2)]:
if s == e:
continue
result = h.place(kind, s, e)
if result.get("placed"):
placed = (kind, s, e, result)
break
if placed:
break
if placed:
break
if not placed:
print("[smoke] no legal placement found — inspect state dump:")
print(json.dumps(snap_before, indent=2))
return 2
kind, start, end, result = placed
print(f" placed {kind} {start}->{end}, pieceId={result.get('pieceId')}")
h.screenshot("02_placed")
snap_after = h.state()
assert len(snap_after["pieces"]) == len(snap_before["pieces"]) + 1, "piece count didn't grow"
assert snap_after["remainingStock"][kind] == stock_before[kind] - 1, "stock didn't decrement"
# --- 3. determinism check: two screenshots of a paused state must match ---
print("[smoke] determinism check")
a = h.screenshot("det_a")
b = h.screenshot("det_b")
if sha256(a) != sha256(b):
print(f" WARN: screenshots differ (hover cursor likely) — {sha256(a)[:8]} vs {sha256(b)[:8]}")
else:
print(" identical OK")
# --- 4. stepping ---
print("[smoke] stepping up to 10 turns")
for i in range(10):
step_info = h.step()
h.screenshot(f"step_{i:02}")
phase = step_info.get("phase")
print(f" turn={step_info.get('turn')} phase={phase}")
if phase == "MissionComplete":
print(" mission complete!")
break
# --- 5. negative test ---
print("[smoke] negative test: off-board placement")
try:
bad = h.place(kind, (-1, -1), (0, 0))
assert not bad.get("placed"), f"expected placed:false, got {bad}"
assert bad.get("reason"), f"expected reason, got {bad}"
print(f" rejected with: {bad['reason']}")
except HarnessError as e:
print(f" harness error (acceptable): {e}")
# --- 6. responsive after negative ---
print("[smoke] final state dump")
final = h.state()
print(f" phase={final['phase']} turn={final['turn']} pieces={len(final['pieces'])}")
print("\n[smoke] PASS\n")
return 0
if __name__ == "__main__":
sys.exit(main())