The operator now holds the lobby: with 'Manual launch' checked (default), the relay stops at 'all pods have the egg; WAITING FOR OPERATOR LAUNCH' instead of auto-firing on a 20s timer -- the app shows 'ALL PODS READY -- press LAUNCH MISSION' with the roster lights, and the big green button fires the mission when the operator is ready (everyone seated, voice chat confirmed). A 'Restart session' button cycles the same mission/roster for the next round (players just re-run their join script). - btconsole.py --relay --manual-launch: launch armed by the line 'launch' on stdin (a daemon reader thread; the app's Launch button writes it via QProcess). The settle window is still honoured relative to egg delivery (max(now, eggs_done + 20s)) -- firing RunMission before the pods reach WaitingForLaunch Fail()s them, the same hazard the auto timer guards. Auto mode (no flag) unchanged -- CLI/test recipes unaffected. - btoperator.py: Manual-launch checkbox (Network box, default on), LAUNCH MISSION button (enabled by the relay's READY line, disabled after fire), Restart session button, status headline shows the ready state. - operator_e2e.py: now exercises the gate -- waits for READY, asserts the button enabled, presses it, verifies the operator-gated LAUNCH. Verified: scripted e2e PASS -- registered -> READY (held) -> operator launch -> settle honoured -> both pods LAUNCHED (operator-gated=True). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
#!/usr/bin/env python
|
|
"""operator_e2e.py -- scripted end-to-end test of the operator console.
|
|
|
|
Drives the real Operator window through its own methods on a timer:
|
|
start a RELAY session -> launch 2 local btl4 instances -> wait for the
|
|
mission LAUNCH to be observed by the session monitor -> report + shut down.
|
|
Exercises the exact QProcess paths the buttons use (no synthetic clicks).
|
|
Exit 0 = launched; 1 = timed out.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.join(HERE, "..", "tools"))
|
|
|
|
from PySide6.QtCore import QTimer
|
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
import btoperator
|
|
|
|
# never block on dialogs in scripted mode
|
|
QMessageBox.warning = staticmethod(lambda *a, **k: print("[e2e] WARN dialog:", a[2] if len(a) > 2 else a))
|
|
QMessageBox.information = staticmethod(lambda *a, **k: None)
|
|
|
|
TIMEOUT_S = 120
|
|
result = {"ok": False}
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
btoperator.dark_palette(app)
|
|
win = btoperator.Operator()
|
|
win.show()
|
|
|
|
def step_start():
|
|
print("[e2e] starting relay session")
|
|
win._start_session()
|
|
assert win.console_proc is not None, "session did not start"
|
|
QTimer.singleShot(3000, step_launch)
|
|
|
|
def step_launch():
|
|
print("[e2e] launching local instances")
|
|
win._launch_local()
|
|
assert len(win.game_procs) == 2, \
|
|
"expected 2 local instances, got %d" % len(win.game_procs)
|
|
poll.start(2000)
|
|
|
|
ticks = {"n": 0}
|
|
|
|
pressed = {"launch": False}
|
|
|
|
def step_poll():
|
|
ticks["n"] += 1
|
|
m = win.monitor
|
|
states = dict(m.state) if m else {}
|
|
print("[e2e] t=%ds states=%s ready=%s launched=%s" %
|
|
(ticks["n"] * 2, states, m.ready if m else "?",
|
|
m.launched if m else "?"))
|
|
# MANUAL GATE: when the relay reports ready, press the Launch button.
|
|
if m and m.ready and not m.launched and not pressed["launch"]:
|
|
assert win.launch_btn.isEnabled(), "Launch button should be enabled"
|
|
print("[e2e] relay READY -> pressing LAUNCH MISSION")
|
|
win._launch_mission()
|
|
pressed["launch"] = True
|
|
if m and m.launched:
|
|
result["ok"] = True
|
|
status = win.pod_status.text().encode("ascii", "replace").decode()
|
|
print("[e2e] LAUNCH observed (operator-gated=%s) -- pod status: %s"
|
|
% (pressed["launch"], status))
|
|
finish()
|
|
elif ticks["n"] * 2 >= TIMEOUT_S:
|
|
print("[e2e] TIMEOUT")
|
|
finish()
|
|
|
|
def finish():
|
|
poll.stop()
|
|
win._stop_session()
|
|
QTimer.singleShot(500, app.quit)
|
|
|
|
poll = QTimer()
|
|
poll.timeout.connect(step_poll)
|
|
QTimer.singleShot(1000, step_start)
|
|
app.exec()
|
|
print("[e2e] %s" % ("PASS" if result["ok"] else "FAIL"))
|
|
return 0 if result["ok"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|