diff --git a/tests/browser/server.py b/tests/browser/server.py index c446ed0..77dc0f4 100644 --- a/tests/browser/server.py +++ b/tests/browser/server.py @@ -583,8 +583,13 @@ def main(argv: list[str] | None = None) -> None: loop = asyncio.new_event_loop() if args.exit_on_stdin_eof: exit_when_stdin_closes(loop) + original_cwd = os.getcwd() with tempfile.TemporaryDirectory(prefix="pm-browser-") as directory: - web.run_app(create_app(Path(directory).resolve()), sock=listener, loop=loop) + try: + web.run_app(create_app(Path(directory).resolve()), sock=listener, loop=loop) + finally: + # create_app works inside the root; Windows cannot remove a process's cwd. + os.chdir(original_cwd) if __name__ == "__main__": diff --git a/tests/test_browser_server_lifetime.py b/tests/test_browser_server_lifetime.py index 1380ed5..eb68bbb 100644 --- a/tests/test_browser_server_lifetime.py +++ b/tests/test_browser_server_lifetime.py @@ -39,7 +39,10 @@ class TestBrowserServerLifetime(unittest.TestCase): def stop(): if process.poll() is None: process.kill() - process.communicate(timeout=SERVER_TIMEOUT_S) + process.wait(timeout=SERVER_TIMEOUT_S) + # communicate() would flush a closed stdin, which Python < 3.13 refuses. + for stream in (process.stdin, process.stdout, process.stderr): + stream.close() self.addCleanup(stop) line = process.stdout.readline().decode() @@ -63,7 +66,10 @@ class TestBrowserServerLifetime(unittest.TestCase): process = self.start("--exit-on-stdin-eof") self.assertEqual(len(self.roots()), 1, "the server works in its own temp root") process.stdin.close() - self.assertEqual(process.wait(timeout=SERVER_TIMEOUT_S), 0) + code = process.wait(timeout=SERVER_TIMEOUT_S) + self.assertEqual( + code, 0, process.stderr.read().decode(errors="replace")[-4000:] + ) self.assertEqual(self.roots(), [], "the temp root and its database are removed") def test_server_keeps_running_with_stdin_closed_without_the_flag(self): diff --git a/tests/vendor/browser-server-fixture.test.mjs b/tests/vendor/browser-server-fixture.test.mjs index aa8c3e1..9b05c4b 100644 --- a/tests/vendor/browser-server-fixture.test.mjs +++ b/tests/vendor/browser-server-fixture.test.mjs @@ -3,6 +3,7 @@ import assert from 'node:assert/strict'; import { createServer } from 'node:net'; import { spawn } from 'node:child_process'; import { once } from 'node:events'; +import { readFileSync } from 'node:fs'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { setTimeout as delay } from 'node:timers/promises'; import { startBrowserServer, createServerSupervisor, attachServerStderr } from '../../tools/vendor/start-browser-server.mjs'; @@ -27,7 +28,11 @@ async function writeStderr(server, text) { await waitFor(() => server.stderrTail().endsWith(text.trimEnd()), `stderr ${JSON.stringify(text)}`); } -const alive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } }; +/** A process that exited counts as gone even while a non-reaping PID 1 (CI's container) leaves it a zombie. */ +function alive(pid) { + try { process.kill(pid, 0); } catch { return false; } + try { return readFileSync(`/proc/${pid}/stat`, 'utf8').replace(/^.*\) /s, '')[0] !== 'Z'; } catch { return true; } +} /** Starts a server from a separate Node worker process, SIGKILLs that worker and returns the server's pid. */ async function serverOfKilledWorker(options) {