Files
ComfyAssets-ComfyUI_PromptM…/tests/test_browser_server_lifetime.py
T
Vito Sansevero 82ec8782b4 fix(browser): server shutdown and its tests on Windows, Python < 3.13 and CI
Three CI failures on ed809f7:

- Windows: server.py exited 1 on stdin EOF because create_app works with
  the temp root as its cwd, and Windows cannot remove a process's cwd.
  main() now restores the original cwd before the root is removed.
- Python 3.10 to 3.12: the lifetime test's cleanup called communicate()
  after closing stdin, which flushes the closed pipe (ValueError); 3.13
  tolerates it. It now waits and closes the pipes itself, and the exit
  assertion shows the server's stderr. Under 3.12: errors=2 before, OK
  after.
- Tooling (CI container): the SIGKILLed-worker tests treated an exited
  server as alive, because the container's PID 1 does not reap orphans
  and kill(pid, 0) succeeds on a zombie. Reproduced locally with docker
  without --init (2 failed); a zombie now counts as exited (2 passed).
2026-10-21 22:57:03 -07:00

84 lines
3.1 KiB
Python

"""The page-workflow test server ends with its launcher (#274 review M2).
A Playwright worker that is SIGKILLed never runs its fixture teardown, so the
server it started must notice on its own: with ``--exit-on-stdin-eof`` it shuts
down when the launcher's end of its stdin pipe closes, and still removes its
temp root. This runs on every OS in CI, where Windows is the strict case.
"""
import os
import subprocess
import sys
import tempfile
import time
import unittest
import urllib.request
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SERVER_TIMEOUT_S = 60
class TestBrowserServerLifetime(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
def start(self, *extra):
env = {**os.environ, "TMPDIR": self.tmp.name}
env.update(TEMP=self.tmp.name, TMP=self.tmp.name)
process = subprocess.Popen(
[sys.executable, "-m", "tests.browser.server", "--port", "0", *extra],
cwd=REPO_ROOT,
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
def stop():
if process.poll() is None:
process.kill()
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()
self.assertTrue(line.startswith("PM_TEST_SERVER_PORT="), line)
url = f"http://127.0.0.1:{int(line.split('=', 1)[1])}/prompt_manager/test"
deadline = time.monotonic() + SERVER_TIMEOUT_S
while True:
try:
with urllib.request.urlopen(url, timeout=1) as response: # nosec B310
self.assertEqual(response.status, 200)
return process
except OSError:
self.assertIsNone(process.poll(), "the server exited early")
self.assertLess(time.monotonic(), deadline, "never ready")
time.sleep(0.1)
def roots(self):
return sorted(path.name for path in Path(self.tmp.name).glob("pm-browser-*"))
def test_server_exits_and_removes_its_temp_root_when_stdin_closes(self):
process = self.start("--exit-on-stdin-eof")
self.assertEqual(len(self.roots()), 1, "the server works in its own temp root")
process.stdin.close()
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):
process = self.start()
process.stdin.close()
with self.assertRaises(subprocess.TimeoutExpired):
process.wait(timeout=1)
if __name__ == "__main__":
unittest.main()