From 21496ee58fe494f26e6f83781dcde850e99347e0 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 28 Feb 2026 20:36:31 -0500 Subject: [PATCH] test(stabilization): Implement high-signal live_gui telemetry and update plan --- .../python_style_refactor_20260227/plan.md | 3 +- tests/conftest.py | 70 ++++++++++++++++--- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/conductor/tracks/python_style_refactor_20260227/plan.md b/conductor/tracks/python_style_refactor_20260227/plan.md index f95b4e1..b60929c 100644 --- a/conductor/tracks/python_style_refactor_20260227/plan.md +++ b/conductor/tracks/python_style_refactor_20260227/plan.md @@ -47,9 +47,10 @@ - **Requirement:** Log table-based comparison of code states in `logs/test/unique_signature/test_ai_style_formatter.txt`. - [x] Task: Conductor - Align `tier4_interceptor.py` tests with current PowerShell output formatting. - **Requirement:** Log expected vs actual PowerShell output mappings in `logs/test/unique_signature/test_tier4_interceptor.txt`. -- [ ] Task: Conductor - Investigate and stabilize `live_gui` test environment collection/startup. +- [x] Task: Conductor - Investigate and stabilize `live_gui` test environment collection/startup. 173ea96 - **Requirement:** Log comprehensive environment telemetry (Before/After/Delta) in `logs/test/unique_signature/live_gui_diag.txt`. - [ ] Task: Conductor - User Manual Verification 'Phase 6: Test Suite Stabilization' + diff --git a/tests/conftest.py b/tests/conftest.py index 4bb3e7c..f04e896 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,8 @@ import requests import os import signal import sys +import datetime +from pathlib import Path from typing import Generator, Any # Ensure project root is in path @@ -20,15 +22,45 @@ def reset_ai_client() -> Generator[None, None, None]: ai_client.set_provider("gemini", "gemini-2.5-flash-lite") yield -class VisualLogger: - def log_state(self, label: str, before: Any, after: Any) -> None: - print(f"[STATE] {label}: {before} -> {after}") - def finalize(self, title: str, status: str, result: str) -> None: - print(f"[FINAL] {title}: {status} - {result}") +class VerificationLogger: + """High-signal reporting for automated tests, inspired by Unreal Engine's diagnostic style.""" + def __init__(self, test_name: str, script_name: str): + self.test_name = test_name + self.script_name = script_name + self.logs_dir = Path(f"logs/test/{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}") + self.logs_dir.mkdir(parents=True, exist_ok=True) + self.log_file = self.logs_dir / f"{script_name}.txt" + self.entries = [] + + def log_state(self, field: str, before: Any, after: Any, delta: Any = None): + self.entries.append({ + "Field": field, + "Before": str(before), + "After": str(after), + "Delta": str(delta) if delta is not None else "" + }) + # Also print to stdout for real-time visibility in CI + print(f"[STATE] {field}: {before} -> {after}") + + def finalize(self, description: str, status: str, result_msg: str): + with open(self.log_file, "a", encoding="utf-8") as f: + f.write(f"[ Test: {self.test_name} ]\n") + f.write(f"({description})\n\n") + f.write(f"{self.test_name}: before vs after\n") + f.write(f"{'Field':<25} {'Before':<20} {'After':<20} {'Delta':<15}\n") + f.write("-" * 80 + "\n") + for e in self.entries: + f.write(f"{e['Field']:<25} {e['Before']:<20} {e['After']:<20} {e['Delta']:<15}\n") + f.write("-" * 80 + "\n") + f.write(f"{status} {self.test_name} ({result_msg})\n\n") + print(f"[FINAL] {self.test_name}: {status} - {result_msg}") @pytest.fixture -def vlogger() -> VisualLogger: - return VisualLogger() +def vlogger(request) -> VerificationLogger: + """Fixture to provide a VerificationLogger instance to a test.""" + test_name = request.node.name + script_name = Path(request.node.fspath).stem + return VerificationLogger(test_name, script_name) def kill_process_tree(pid: int | None) -> None: """Robustly kills a process and all its children.""" @@ -53,8 +85,19 @@ def kill_process_tree(pid: int | None) -> None: def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: """ Session-scoped fixture that starts gui_2.py with --enable-test-hooks. + Includes high-signal environment telemetry. """ gui_script = "gui_2.py" + diag = VerificationLogger("live_gui_startup", "live_gui_diag") + diag.log_state("GUI Script", "N/A", gui_script) + + # Check if already running (shouldn't be) + try: + resp = requests.get("http://127.0.0.1:8999/status", timeout=0.1) + already_up = resp.status_code == 200 + except: already_up = False + diag.log_state("Hook Server Port 8999", "Down", "UP" if already_up else "Down") + print(f"\n[Fixture] Starting {gui_script} --enable-test-hooks...") os.makedirs("logs", exist_ok=True) log_file = open(f"logs/{gui_script.replace('.', '_')}_test.log", "w", encoding="utf-8") @@ -65,7 +108,10 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: text=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0 ) - max_retries = 15 # Slightly more time for gui_2 + + diag.log_state("GUI Process PID", "N/A", process.pid) + + max_retries = 15 ready = False print(f"[Fixture] Waiting up to {max_retries}s for Hook Server on port 8999...") start_time = time.time() @@ -81,10 +127,18 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: print(f"[Fixture] {gui_script} process died unexpectedly during startup.") break time.sleep(0.5) + + diag.log_state("Startup Success", "N/A", str(ready)) + diag.log_state("Startup Time", "N/A", f"{round(time.time() - start_time, 2)}s") + if not ready: + diag.finalize("Live GUI Startup Telemetry", "FAIL", "Hook server failed to respond.") print(f"[Fixture] TIMEOUT/FAILURE: Hook server for {gui_script} failed to respond.") kill_process_tree(process.pid) pytest.fail(f"Failed to start {gui_script} with test hooks.") + + diag.finalize("Live GUI Startup Telemetry", "PASS", "Hook server successfully initialized.") + try: yield process, gui_script finally: