refactor(test): wrap live_gui subprocess in _LiveGuiHandle class
This commit is contained in:
+49
-2
@@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
@@ -396,8 +397,54 @@ def app_instance() -> Generator[App, None, None]:
|
|||||||
if hasattr(app, 'shutdown'):
|
if hasattr(app, 'shutdown'):
|
||||||
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")
|
@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.")
|
diag.finalize("Live GUI Startup Telemetry", "PASS", "Hook server successfully initialized.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield process, gui_script
|
yield _LiveGuiHandle(process, gui_script)
|
||||||
finally:
|
finally:
|
||||||
print(f"\n[Fixture] Finally block triggered: Shutting down {gui_script}...")
|
print(f"\n[Fixture] Finally block triggered: Shutting down {gui_script}...")
|
||||||
# Reset the GUI state before shutting down
|
# Reset the GUI state before shutting down
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ from src.api_hook_client import ApiHookClient
|
|||||||
# Session-wide storage for comparing metrics
|
# Session-wide storage for comparing metrics
|
||||||
_shared_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.
|
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.
|
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()
|
client = ApiHookClient()
|
||||||
# Wait for app to stabilize and render some frames
|
# Wait for app to stabilize and render some frames
|
||||||
time.sleep(3.0)
|
time.sleep(3.0)
|
||||||
|
|||||||
@@ -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
|
5. Verifies no AttributeError was logged (the bug would print to
|
||||||
the GUI's stderr, which the live_gui fixture captures to a log)
|
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()
|
client = ApiHookClient()
|
||||||
|
|
||||||
log_path = Path(f"logs/{Path(gui_script).name.replace('.', '_')}_test.log")
|
log_path = Path(f"logs/{Path(gui_script).name.replace('.', '_')}_test.log")
|
||||||
|
|||||||
Reference in New Issue
Block a user