115 lines
4.1 KiB
Python
115 lines
4.1 KiB
Python
|
|
"""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())
|