diff options
Diffstat (limited to 'packages/meshbay-node/tests')
| -rw-r--r-- | packages/meshbay-node/tests/test_e2e_windows_app.py | 40 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_packaging_win.py | 22 |
2 files changed, 56 insertions, 6 deletions
diff --git a/packages/meshbay-node/tests/test_e2e_windows_app.py b/packages/meshbay-node/tests/test_e2e_windows_app.py index 86e6a62..da1d251 100644 --- a/packages/meshbay-node/tests/test_e2e_windows_app.py +++ b/packages/meshbay-node/tests/test_e2e_windows_app.py @@ -46,6 +46,12 @@ pytestmark = [ pytest.mark.timeout(900), ] +# The environment as it is now, at import: the suite's conftest points HOME, +# USERPROFILE, LOCALAPPDATA and APPDATA at a throwaway directory for every test, +# and an application started under that crashes at once (0x80000003). The +# module-scoped fixture launched before it applied; the relaunch in the test +# body did not, which looked like a crash of the application. +REAL_ENV = dict(os.environ) LOCALAPPDATA = Path(os.environ.get("LOCALAPPDATA", "")) APPDATA = Path(os.environ.get("APPDATA", "")) INSTALL = Path(os.environ.get("MESHBAY_E2E_INSTALL", LOCALAPPDATA / "Programs" / "MeshBay")) @@ -62,8 +68,11 @@ def _free_port() -> int: def _launch(argv: list[str]) -> None: - """Start a process detached: DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP.""" - subprocess.Popen(argv, creationflags=0x00000008 | 0x00000200, close_fds=True) + """Start the application detached, in the real profile.""" + log = Path(REAL_ENV.get("TEMP", ".")) / "mb-e2e-app.log" # its console, for a failure + subprocess.Popen(argv, creationflags=0x00000008 | 0x00000200, close_fds=True, + stdout=log.open("a"), stderr=subprocess.STDOUT, + env={**REAL_ENV, "ELECTRON_ENABLE_LOGGING": "1"}) def _node_processes() -> dict[int, int]: @@ -150,8 +159,16 @@ class Page: _wait(f"a control named {label!r}", lambda: _try(lambda: self.eval(f"(clickText({json.dumps(label)}), 1)")), timeout) + def quit(self) -> None: + """Quit the application the way its own Quit does (before-quit runs).""" + version = json.loads(_http_get(f"http://127.0.0.1:{CDP_PORT}/json/version")) + from websockets.sync.client import connect + with connect(version["webSocketDebuggerUrl"]) as browser: + browser.send(json.dumps({"id": 1, "method": "Browser.close"})) + _try(lambda: browser.recv(timeout=5)) + def close(self): - self._conn.__exit__(None, None, None) + _try(lambda: self._conn.__exit__(None, None, None)) def _http_get(url: str, token: str | None = None) -> str: @@ -301,3 +318,20 @@ def test_a_fresh_account_gets_its_node_set_up_and_can_stop_it(app, hub): s.get("status") == "running" and s.get("pid") != first.get("pid") and s, 120) assert len(_node_pids()) == 1, _node_pids() assert second["pk_node_ed25519"] == first["pk_node_ed25519"] + + if MODE == "open": + # Only while MeshBay is open: Quit stops the node, opening starts it. + page.quit() + _wait("no node after Quit", lambda: not _node_pids(), 60) + # A second instance started while the first is still exiting defers + # to it (single-instance lock) and quits. + _wait("the application gone", lambda: b"MeshBay.exe" not in subprocess.run( + ["tasklist", "/FI", "IMAGENAME eq MeshBay.exe", "/NH"], + capture_output=True).stdout, 60) + _launch([str(INSTALL / "MeshBay.exe"), f"--remote-debugging-port={CDP_PORT}"]) + again = _wait("the application window again", lambda: _try(Page), 60) + again.close() + _wait("a node started with the app", lambda: (s := _node_status()) and + s.get("status") == "running", 120) + # The CLI that started it is meshbay-node.exe too, for a moment. + _wait("a single meshbay-node.exe", lambda: len(_node_pids()) == 1, 20) diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index 1fe6be8..b5f6191 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -1625,11 +1625,27 @@ def test_a_declined_prompt_leaves_the_node_as_it_was(): def test_only_while_open_starts_with_the_app_and_stops_at_quit(): main_js = MAIN_JS.read_text(encoding="utf-8") - start = _fn_body(main_js, "async function winStartNodeWithApp()") + start = _fn_body(main_js, "async function winStartNodeWithAppOnce()") assert "winStartupMode()) !== 'open'" in start and "nodeProvisioned()" in start assert "winNodeStartVia(['autostart', 'start'])" in start ready = main_js.split("app.whenReady().then(", 1)[1] assert ready.index("createWindow();") < ready.index("nodeWithApp.start()") quit_ = main_js.split("app.on('before-quit', (event) => {", 1)[1].split("\n });", 1)[0] - assert "!nodeStartedByApp" in quit_ and "event.preventDefault()" in quit_ - assert "mode === 'open' ? killNodeProcesses()" in quit_ + assert "event.preventDefault()" in quit_ + assert "(await winStartupMode()) !== 'open'" in quit_ and "killNodeProcesses()" in quit_ + # Whoever started it: a node left by the sign-in launcher before the mode + # was switched ran on after Quit (found on a real install, 2026-10-08). + assert "!nodeStartedByApp" not in quit_ + assert "await probeNode() || (await winNodePids()).length" in quit_ + + +def test_one_start_at_a_time_at_launch(): + """The start with the app and the sign-in's own both ran `autostart start` + at once: three meshbay-node.exe were seen racing for the port.""" + main_js = MAIN_JS.read_text(encoding="utf-8") + once = _fn_body(main_js, "async function winStartNodeWithApp()") + assert "if (!nodeWithAppStart) nodeWithAppStart = winStartNodeWithAppOnce();" in once + ensure = _fn_body(main_js, "async function ensureNode(opts)") + assert ensure.index("await nodeWithAppStart") < ensure.index("provisionNode(") + start = main_js.split("handle('node:start'", 1)[1][:600] + assert "await nodeWithAppStart" in start |