105 lines
4.5 KiB
Python
105 lines
4.5 KiB
Python
"""Real Godot / Rust / NATS smoke test; no physical hardware. Stop other labs first."""
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import urllib.error
|
|
import lab
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
PROJECT = ROOT.parents[1]
|
|
|
|
def main():
|
|
for port in (*lab.PORTS, 18768):
|
|
with socket.socket() as sock:
|
|
sock.bind(("127.0.0.1", port))
|
|
nats = shutil.which("nats-server")
|
|
godot = os.environ.get("GLITCH_LAB_GODOT") or str(PROJECT / "Godot_v4.7.2-stable_win64_console.exe")
|
|
binary = ROOT / "target/debug" / ("glitch-odin-lab.exe" if os.name == "nt" else "glitch-odin-lab")
|
|
if not nats or not Path(godot).is_file() or not binary.is_file():
|
|
raise RuntimeError("Need nats-server, built Rust lab, and Godot (or GLITCH_LAB_GODOT)")
|
|
children = []
|
|
with tempfile.TemporaryFile(mode="w+b") as log:
|
|
def spawn(args):
|
|
p = subprocess.Popen(args, cwd=ROOT, stdout=log, stderr=log,
|
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0)
|
|
children.append(p)
|
|
return p
|
|
def launch_game(inject):
|
|
return spawn([godot, "--headless", "--path", str(PROJECT),
|
|
"res://tests/odin_lab/cabinet.tscn", "--", "--glitch-lab",
|
|
*(["--lab-inject-rfid"] if inject else [])])
|
|
def state():
|
|
deadline = time.monotonic() + 20
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
return lab.api("/state")["state"]
|
|
except (OSError, ValueError):
|
|
time.sleep(.1)
|
|
raise RuntimeError("No cabinet state after startup")
|
|
try:
|
|
spawn([nats, "-c", str(ROOT / "nats.conf")])
|
|
deadline = time.monotonic() + 10
|
|
while True:
|
|
try:
|
|
with socket.create_connection(("127.0.0.1", 14222), timeout=.2):
|
|
break
|
|
except OSError:
|
|
if time.monotonic() > deadline: raise
|
|
time.sleep(.1)
|
|
game = launch_game(True)
|
|
spawn([str(binary), "godot"])
|
|
spawn([str(binary), "controller"])
|
|
initial = state()
|
|
assert initial["simulated"] is False and initial["phase"] == "powered_off"
|
|
session = initial["session"]
|
|
def send(action):
|
|
return lab.api("/command", lab.command(action, session))
|
|
assert not send("start")["accepted"]
|
|
assert send("power_on")["state"]["phase"] == "booting"
|
|
assert not send("scan")["accepted"]
|
|
deadline = time.monotonic() + 8
|
|
while state()["phase"] != "ready":
|
|
assert time.monotonic() < deadline, "Boot did not finish"
|
|
time.sleep(.1)
|
|
assert not send("start")["accepted"]
|
|
scan = lab.command("scan", session)
|
|
assert lab.api("/command", scan)["state"]["credits"] == 1
|
|
replay = lab.api("/command", scan)
|
|
assert replay["duplicate"] and replay["state"]["scans"] == 1
|
|
assert send("start")["state"]["phase"] == "playing"
|
|
assert not send("win")["accepted"], "Remote synthetic win must be disabled"
|
|
assert send("reset")["state"]["phase"] == "powered_off"
|
|
game.terminate()
|
|
game.wait(timeout=5)
|
|
try:
|
|
lab.api("/state")
|
|
raise AssertionError("Offline cabinet falsely returned state")
|
|
except urllib.error.HTTPError as error:
|
|
assert error.code == 503
|
|
launch_game(False)
|
|
restarted = state()
|
|
assert restarted["session"] != session and not restarted["rfid_injection"]
|
|
assert not send("power_on")["accepted"], "Old session accepted"
|
|
session = restarted["session"]
|
|
assert not send("scan")["accepted"]
|
|
print("PASS: HTTP -> Rust -> NATS -> sidecar -> real Godot, boot/credit/start/reset, offline and restart")
|
|
finally:
|
|
for child in reversed(children):
|
|
if child.poll() is None:
|
|
child.terminate()
|
|
for child in children:
|
|
try:
|
|
child.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
child.kill()
|
|
child.wait()
|
|
log.seek(0)
|
|
print(log.read().decode("utf-8", errors="replace")[-4000:])
|
|
|
|
if __name__ == "__main__":
|
|
main()
|