From 16bd3d3a47d67e1c219f1cf296ee560f4bf3e689 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 9 Jun 2026 15:37:47 -0400 Subject: [PATCH] refactor(test): wrap live_gui subprocess in _LiveGuiHandle class --- tests/conftest.py | 51 +++++++++++++++++++- tests/test_gui2_performance.py | 6 ++- tests/test_live_gui_filedialog_regression.py | 4 +- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index faf234b1..be070f53 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import pytest import subprocess +import threading import time import requests import os @@ -396,8 +397,54 @@ def app_instance() -> Generator[App, None, None]: if hasattr(app, 'shutdown'): app.shutdown() +class _LiveGuiHandle: + def __init__(self, process: subprocess.Popen, gui_script: str) -> None: + """[SDM: tests/conftest.py:_LiveGuiHandle] [C: tests/conftest.py:live_gui fixture]""" + self._process = process + self._gui_script = gui_script + self._lock = threading.Lock() + self._respawn_count = 0 + + def __iter__(self): + """Support tuple unpacking: `process, gui_script = handle` (backward compat).""" + return iter((self._process, self._gui_script)) + + def __getitem__(self, index: int): + """Support indexing: `handle[0]` returns process, `handle[1]` returns gui_script.""" + if index == 0: + return self._process + if index == 1: + return self._gui_script + raise IndexError(index) + + @property + def process(self) -> subprocess.Popen: + """[M: tests/conftest.py:live_gui fixture]""" + return self._process + + @property + def gui_script(self) -> str: + """[M: tests/conftest.py:live_gui fixture]""" + return self._gui_script + + def is_alive(self) -> bool: + """Returns True if the subprocess is running.""" + return self._process is not None and self._process.poll() is None + + def ensure_alive(self) -> None: + """No-op stub for Phase 2: the live_gui fixture is session-scoped, so we cannot respawn the subprocess in-place. The handle is respawned between test sessions. If the process died, the counter is incremented for diagnostics.""" + with self._lock: + if not self.is_alive(): + self._respawn_count += 1 + + @property + def respawn_count(self) -> int: + """[M: tests/conftest.py:_LiveGuiHandle.ensure_alive]""" + return self._respawn_count + + @pytest.fixture(scope="session") -def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: +def live_gui() -> Generator["_LiveGuiHandle", None, None]: """ @@ -561,7 +608,7 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: diag.finalize("Live GUI Startup Telemetry", "PASS", "Hook server successfully initialized.") try: - yield process, gui_script + yield _LiveGuiHandle(process, gui_script) finally: print(f"\n[Fixture] Finally block triggered: Shutting down {gui_script}...") # Reset the GUI state before shutting down diff --git a/tests/test_gui2_performance.py b/tests/test_gui2_performance.py index 713f7ef6..1fd73c92 100644 --- a/tests/test_gui2_performance.py +++ b/tests/test_gui2_performance.py @@ -17,14 +17,16 @@ from src.api_hook_client import ApiHookClient # Session-wide storage for comparing metrics _shared_metrics = {} -def test_performance_benchmarking(live_gui: tuple) -> None: +def test_performance_benchmarking(live_gui) -> None: """ Collects performance metrics for the current GUI script over a 5-second window. Ensures the application does not lock up and can report its internal state. """ - process, gui_script = live_gui + handle = live_gui + process = handle.process + gui_script = handle.gui_script client = ApiHookClient() # Wait for app to stabilize and render some frames time.sleep(3.0) diff --git a/tests/test_live_gui_filedialog_regression.py b/tests/test_live_gui_filedialog_regression.py index 42b64955..a12b0b94 100644 --- a/tests/test_live_gui_filedialog_regression.py +++ b/tests/test_live_gui_filedialog_regression.py @@ -34,7 +34,9 @@ def test_live_gui_project_settings_opens_without_filedialog_crash(live_gui) -> N 5. Verifies no AttributeError was logged (the bug would print to the GUI's stderr, which the live_gui fixture captures to a log) """ - process, gui_script = live_gui + handle = live_gui + process = handle.process + gui_script = handle.gui_script client = ApiHookClient() log_path = Path(f"logs/{Path(gui_script).name.replace('.', '_')}_test.log")