Added chatterbox connections
This commit is contained in:
1 parent
41ef12f954
commit
e58ceeb192
58 files changed
+8931
-8
No files matched your search
@@ -0,0 +1,154 @@
|
||||
"""Owns an isolated room broker/controller for a full simulated run and trace replay."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
BASE = "http://127.0.0.1:18776"
|
||||
PORTS = (14232, 18776, 18777)
|
||||
|
||||
def api(path, body=None):
|
||||
req = urllib.request.Request(BASE + path,
|
||||
data=None if body is None else json.dumps(body).encode(),
|
||||
headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
return json.load(response)
|
||||
|
||||
def action(name, entity="", value=None):
|
||||
state = api("/room/state")["state"]
|
||||
payload = {"command_id":str(uuid.uuid4()),"run_id":state["run_id"],
|
||||
"expected_revision":state["revision"],"expires_at_ms":int(time.time()*1000)+10000,
|
||||
"input":{"action":name,"entity":entity,"value":value}}
|
||||
result = api("/room/command", payload)
|
||||
assert result["accepted"], (name, result["message"])
|
||||
return result["state"]
|
||||
|
||||
def golden():
|
||||
action("prepare", value={"profile":"standard","party":6,"seed":42})
|
||||
action("start_run")
|
||||
action("briefing_done")
|
||||
for puzzle, token in [("P01",None),("P02A","T01"),("P02B","T02"),("P03","T03"),("P04","T04"),("P05","T05"),("P06","T06")]:
|
||||
action("solve", puzzle)
|
||||
if token: action("deliver", token)
|
||||
for i in range(1,7):
|
||||
cabinet=f"cabinet_{i:02}"
|
||||
action("token", cabinet, {"token":f"T{i:02}","age_ms":0})
|
||||
action("start", cabinet)
|
||||
state=action("advance", value=30000)
|
||||
for station in state["stations"]:
|
||||
action("win", station["id"], {"attempt":station["attempt"],"progress":station["target"]})
|
||||
action("boss_start")
|
||||
state=action("boss_win")
|
||||
effect=next(e for e in state["effects"] if e["kind"]=="passage.release")
|
||||
assert not action("ack", effect["id"])["passage_open"]
|
||||
action("passage", value=True)
|
||||
state=action("advance", value=300)
|
||||
for station in state["stations"]:
|
||||
action("bay", station["id"])
|
||||
action("cell","BAY"+station["cell"][4:],{"cell":station["cell"],"present":False})
|
||||
action("cell",station["socket"],{"cell":station["cell"],"present":True})
|
||||
state=action("advance", value=2500)
|
||||
while state["phase"] in ("CALIBRATION_TUTORIAL","CALIBRATION"):
|
||||
wave=state["wave"]
|
||||
for station,target in wave["targets"].items():
|
||||
action("ready",station,{"challenge":wave["id"],"selection":target,"stable_ms":2000})
|
||||
state=action("execute", value=wave["id"])
|
||||
assert state["phase"]=="LAUNCH_ARM"
|
||||
epoch=state["launch_epoch"]
|
||||
for station in state["stations"]:
|
||||
action("arm",station["id"],epoch)
|
||||
for group in range(state["operators"]):
|
||||
action("group_ready",value={"epoch":epoch,"group":group})
|
||||
action("commit",value=epoch)
|
||||
state=action("advance",value=5000)
|
||||
assert state["lifecycle"]=="WON" and state["launches"]==1
|
||||
assert all(s["passes"]==2 for s in state["stations"])
|
||||
return state
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--preview",action="store_true",help="After tests, serve GM UI for up to 10 minutes; create qa.stop to stop early")
|
||||
args=parser.parse_args()
|
||||
for port in PORTS:
|
||||
with socket.socket() as sock: sock.bind(("127.0.0.1",port))
|
||||
nats=shutil.which("nats-server")
|
||||
binary=ROOT/"target/debug"/("glitch-odin-lab.exe" if os.name=="nt" else "glitch-odin-lab")
|
||||
if not nats or not binary.is_file(): raise RuntimeError("Install NATS and build the Rust lab first")
|
||||
children=[]
|
||||
output=ROOT/"evidence"
|
||||
output.mkdir(exist_ok=True)
|
||||
with tempfile.TemporaryFile(mode="w+b") as log:
|
||||
def spawn(argv):
|
||||
child=subprocess.Popen(argv,cwd=ROOT,stdout=log,stderr=log,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name=="nt" else 0)
|
||||
children.append(child)
|
||||
return child
|
||||
try:
|
||||
spawn([nats,"-c",str(ROOT/"room-nats.conf")])
|
||||
deadline=time.monotonic()+10
|
||||
while True:
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1",14232),timeout=.2): break
|
||||
except OSError:
|
||||
if time.monotonic()>deadline: raise
|
||||
time.sleep(.1)
|
||||
spawn([str(binary),"room"])
|
||||
deadline=time.monotonic()+20
|
||||
while True:
|
||||
try:
|
||||
api("/room/state")
|
||||
break
|
||||
except OSError:
|
||||
if time.monotonic()>deadline: raise
|
||||
time.sleep(.1)
|
||||
state=golden()
|
||||
trace=api("/room/trace")
|
||||
trace_path=output/"room-golden-trace.json"
|
||||
trace_path.write_text(json.dumps(trace,indent=2),encoding="utf-8")
|
||||
subprocess.run([str(binary),"replay-room",str(trace_path)],check=True)
|
||||
(output/"room-result.json").write_text(json.dumps({
|
||||
"status":"passed","transport":"HTTP -> Rust -> NATS -> Rust room reducer",
|
||||
"lifecycle":state["lifecycle"],"launches":state["launches"],
|
||||
"passes":[s["passes"] for s in state["stations"]],
|
||||
"final_hash":trace["final_hash"],"trace_events":len(trace["trace"])
|
||||
},indent=2),encoding="utf-8")
|
||||
print("PASS: complete seven-cabinet room, puzzles, cells, practices, calibration, launch, and offline trace replay",flush=True)
|
||||
if args.preview:
|
||||
action("reset",value={"confirmed":True})
|
||||
action("start_run")
|
||||
action("briefing_done")
|
||||
action("solve","P01")
|
||||
action("solve","P02A")
|
||||
action("deliver","T01")
|
||||
action("token","cabinet_01",{"token":"T01","age_ms":0})
|
||||
action("start","cabinet_01")
|
||||
action("advance",value=10000)
|
||||
spawn([sys.executable,str(ROOT/"room_web.py")])
|
||||
stop=ROOT/"qa.stop"
|
||||
if stop.exists(): stop.unlink()
|
||||
print("PREVIEW: http://127.0.0.1:18777/?room=1",flush=True)
|
||||
deadline=time.monotonic()+600
|
||||
while time.monotonic()<deadline and not stop.exists():
|
||||
time.sleep(.2)
|
||||
if stop.exists(): stop.unlink()
|
||||
finally:
|
||||
for p in reversed(children):
|
||||
if p.poll() is None: p.terminate()
|
||||
for p in children:
|
||||
try: p.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
p.kill();p.wait()
|
||||
log.seek(0)
|
||||
text=log.read().decode("utf-8",errors="replace")
|
||||
(output/"room-runtime.log").write_text(text,encoding="utf-8")
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user