Start powered off; the user presses On

The server now owns the QEMU process by default but does not launch it. You open
the page to a dark screen and press On, which is the behaviour asked for: like
walking up to a machine rather than finding it already booted.

This inverts the flag from the plan. Owning the process has to be the default,
since it is the only way On/Off can work at all; --attach is the opt-in for joining
a run.sh instance, where Off is refused.

Two tests guard the intent rather than the wiring: one asserts main() has --attach
and not --own-emulator, another asserts main() never calls power_on(), so a future
edit cannot quietly restore auto-boot.

Verified on the live server with no QEMU running beforehand:
  startup   0 QEMU processes, powered=false, frame.png 503, page 200
  On        1 QEMU process, powered=true, frame.png 200 (2920 bytes)
  Off       0 QEMU processes, frame.png 503, page still 200, keys 409
  On again  1 QEMU process, powered=true, frame.png 200

The web server stays up across Off, which is what you asked for: the screen goes
dark and waits for the next person to press On.
This commit is contained in:
mckero committed 2026-08-28 06:09:56 +01:00
1 parent 6a9cf9f28a
commit 5b53cbbc82
2 files changed
+72 -4

No files matched your search

+34
View File
@@ -437,5 +437,39 @@ class TestPowerBar(unittest.TestCase):
self.assertIn("screen-off", self.body)
class TestStartsPoweredOff(unittest.TestCase):
"""The emulator must not be running until the user asks for it."""
def test_app_with_no_client_serves_a_dark_screen(self):
app = webui.create_app(None, frame_addr=0x1000, status_addr=0x2000)
app.config.update(TESTING=True)
http = app.test_client()
self.assertEqual(http.get("/").status_code, 200)
self.assertFalse(http.get("/api/status").get_json()["powered"])
# No frame yet, and that is a state rather than an error.
self.assertEqual(http.get("/frame.png").status_code, 503)
def test_page_offers_an_on_button_while_off(self):
app = webui.create_app(None, frame_addr=0x1000, status_addr=0x2000)
app.config.update(TESTING=True)
body = app.test_client().get("/").get_data(as_text=True)
self.assertIn('data-power="on"', body)
def test_main_has_an_attach_flag_not_an_own_flag(self):
"""Owning the process is the default; attaching is the opt-in."""
import inspect
src = inspect.getsource(webui.main)
self.assertIn("--attach", src)
self.assertNotIn("--own-emulator", src)
def test_main_does_not_power_on_at_startup(self):
"""Arriving at a dark screen is the point; do not boot it for them."""
import inspect
src = inspect.getsource(webui.main)
# power_on may only appear under the attach branch, never unconditionally.
self.assertNotIn("supervisor.power_on()", src)
if __name__ == "__main__":
unittest.main()