From 966b5c3d034c42756eb286b919a5dc7f87c4a097 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 4 Mar 2026 00:01:01 -0500 Subject: [PATCH] wow this ai messed up. --- conductor/tracks.md | 3 + .../test_stabilization_20260302/index.md | 0 .../test_stabilization_20260302/metadata.json | 0 .../test_stabilization_20260302/plan.md | 0 .../test_stabilization_20260302/spec.md | 0 .../tracks/ux_sim_test_20260302/metadata.json | 8 - conductor/tracks/ux_sim_test_20260302/plan.md | 3 - conductor/tracks/ux_sim_test_20260302/spec.md | 5 - config.toml | 4 +- project_history.toml | 2 +- tests/conftest.py | 190 +++++++++++------- tests/test_gui_phase4.py | 11 +- 12 files changed, 132 insertions(+), 94 deletions(-) rename conductor/{archive => tracks}/test_stabilization_20260302/index.md (100%) rename conductor/{archive => tracks}/test_stabilization_20260302/metadata.json (100%) rename conductor/{archive => tracks}/test_stabilization_20260302/plan.md (100%) rename conductor/{archive => tracks}/test_stabilization_20260302/spec.md (100%) delete mode 100644 conductor/tracks/ux_sim_test_20260302/metadata.json delete mode 100644 conductor/tracks/ux_sim_test_20260302/plan.md delete mode 100644 conductor/tracks/ux_sim_test_20260302/spec.md diff --git a/conductor/tracks.md b/conductor/tracks.md index d65ff2a..6fa92cd 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -8,6 +8,9 @@ This file tracks all major tracks for the project. Each track has its own detail *The following tracks MUST be executed in this exact order to safely resolve tech debt before feature development.* +0. [~] **Track: test_stabilization_20260302** (user added back in: Absolute mess) +*[Link]*(./tracks/test_stabilization_20260302) + 1. [ ] **Track: Strict Static Analysis & Type Safety** *Link: [./tracks/strict_static_analysis_and_typing_20260302/](./tracks/strict_static_analysis_and_typing_20260302/)* diff --git a/conductor/archive/test_stabilization_20260302/index.md b/conductor/tracks/test_stabilization_20260302/index.md similarity index 100% rename from conductor/archive/test_stabilization_20260302/index.md rename to conductor/tracks/test_stabilization_20260302/index.md diff --git a/conductor/archive/test_stabilization_20260302/metadata.json b/conductor/tracks/test_stabilization_20260302/metadata.json similarity index 100% rename from conductor/archive/test_stabilization_20260302/metadata.json rename to conductor/tracks/test_stabilization_20260302/metadata.json diff --git a/conductor/archive/test_stabilization_20260302/plan.md b/conductor/tracks/test_stabilization_20260302/plan.md similarity index 100% rename from conductor/archive/test_stabilization_20260302/plan.md rename to conductor/tracks/test_stabilization_20260302/plan.md diff --git a/conductor/archive/test_stabilization_20260302/spec.md b/conductor/tracks/test_stabilization_20260302/spec.md similarity index 100% rename from conductor/archive/test_stabilization_20260302/spec.md rename to conductor/tracks/test_stabilization_20260302/spec.md diff --git a/conductor/tracks/ux_sim_test_20260302/metadata.json b/conductor/tracks/ux_sim_test_20260302/metadata.json deleted file mode 100644 index 2eafd2c..0000000 --- a/conductor/tracks/ux_sim_test_20260302/metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "ux_sim_test_20260302", - "title": "UX_SIM_TEST", - "description": "Simulation testing for GUI UX", - "type": "feature", - "status": "new", - "progress": 0.0 -} \ No newline at end of file diff --git a/conductor/tracks/ux_sim_test_20260302/plan.md b/conductor/tracks/ux_sim_test_20260302/plan.md deleted file mode 100644 index 5f17e2c..0000000 --- a/conductor/tracks/ux_sim_test_20260302/plan.md +++ /dev/null @@ -1,3 +0,0 @@ -# Implementation Plan: UX_SIM_TEST - -- [ ] Task 1: Initialize diff --git a/conductor/tracks/ux_sim_test_20260302/spec.md b/conductor/tracks/ux_sim_test_20260302/spec.md deleted file mode 100644 index 6b42dbd..0000000 --- a/conductor/tracks/ux_sim_test_20260302/spec.md +++ /dev/null @@ -1,5 +0,0 @@ -# Specification: UX_SIM_TEST - -Type: feature - -Description: Simulation testing for GUI UX diff --git a/config.toml b/config.toml index 2cd4d4b..746ada0 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ [ai] provider = "gemini_cli" -model = "gemini-2.5-flash-lite" +model = "gemini-2.0-flash" temperature = 0.0 max_tokens = 8192 history_trunc_limit = 8000 @@ -15,7 +15,7 @@ paths = [ "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml", "C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml", ] -active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_livecontextsim.toml" +active = "C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml" [gui.show_windows] "Context Hub" = true diff --git a/project_history.toml b/project_history.toml index 06a71f1..90cd688 100644 --- a/project_history.toml +++ b/project_history.toml @@ -8,5 +8,5 @@ active = "main" [discussions.main] git_commit = "" -last_updated = "2026-03-03T23:37:12" +last_updated = "2026-03-03T23:54:45" history = [] diff --git a/tests/conftest.py b/tests/conftest.py index 767cb94..f068c88 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,95 +7,42 @@ import os import signal import sys import datetime +import shutil from pathlib import Path from typing import Generator, Any from unittest.mock import patch, MagicMock -# Ensure project root is in path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -import ai_client +# Import the App class after patching if necessary, but here we just need the type hint from gui_2 import App -@pytest.fixture(autouse=True) -def reset_ai_client() -> Generator[None, None, None]: - """Reset ai_client global state between every test to prevent state pollution.""" - ai_client.reset_session() - # Default to a safe model - ai_client.set_provider("gemini", "gemini-2.5-flash-lite") - yield - -@pytest.fixture -def app_instance() -> Generator[App, None, None]: - """ - Centralized App instance with all external side effects mocked. - Matches the pattern used in test_token_viz.py and test_gui_phase4.py. - """ - with ( - patch('gui_2.load_config', return_value={ - 'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, - 'projects': {'paths': [], 'active': ''}, - 'gui': {'show_windows': {}} - }), - patch('gui_2.save_config'), - patch('gui_2.project_manager'), - patch('gui_2.session_logger'), - patch('gui_2.immapp.run'), - patch.object(App, '_load_active_project'), - patch.object(App, '_fetch_models'), - patch.object(App, '_load_fonts'), - patch.object(App, '_post_init'), - patch.object(App, '_prune_old_logs'), - patch.object(App, '_init_ai_and_hooks') - ): - app = App() - yield app - # Cleanup: Ensure background threads and asyncio loop are stopped - app.shutdown() - - if hasattr(app, '_loop') and not app._loop.is_closed(): - tasks = [t for t in asyncio.all_tasks(app._loop) if not t.done()] - if tasks: - # Cancel tasks so they can be gathered - for task in tasks: - task.cancel() - app._loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) - - # 4. Finally close the loop - app._loop.close() - -@pytest.fixture -def mock_app(app_instance: App) -> App: - """ - Simpler fixture returning a mocked App instance. - Reuses app_instance for automatic cleanup and consistent mocking. - """ - return app_instance - class VerificationLogger: - """High-signal reporting for automated tests, inspired by Unreal Engine's diagnostic style.""" - def __init__(self, test_name: str, script_name: str): + def __init__(self, test_name: str, script_name: str) -> None: self.test_name = test_name self.script_name = script_name + self.entries = [] + self.start_time = time.time() + # Route artifacts to tests/logs/ self.logs_dir = Path(f"tests/logs/{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): + def log_state(self, field: str, before: Any, after: Any) -> None: + delta = "" + if isinstance(before, (int, float)) and isinstance(after, (int, float)): + diff = after - before + delta = f"{'+' if diff > 0 else ''}{diff}" self.entries.append({ "Field": field, "Before": str(before), "After": str(after), - "Delta": str(delta) if delta is not None else "" + "Delta": delta }) - # 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: + def finalize(self, title: str, status: str, result_msg: str) -> None: + elapsed = round(time.time() - self.start_time, 2) + log_file = self.logs_dir / f"{self.script_name}.txt" + with open(log_file, "w", encoding="utf-8") as f: f.write(f"[ Test: {self.test_name} ]\n") - f.write(f"({description})\n\n") + f.write(f"({title})\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") @@ -131,15 +78,98 @@ def kill_process_tree(pid: int | None) -> None: except Exception as e: print(f"[Fixture] Error killing process tree {pid}: {e}") +@pytest.fixture +def mock_app() -> App: + """ + Mock version of the App for simple unit tests that don't need a loop. + """ + with ( + patch('gui_2.load_config', return_value={ + 'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, + 'projects': {'paths': [], 'active': ''}, + 'gui': {'show_windows': {}} + }), + patch('gui_2.save_config'), + patch('gui_2.project_manager'), + patch('gui_2.session_logger'), + patch('gui_2.immapp.run'), + patch.object(App, '_load_active_project'), + patch.object(App, '_fetch_models'), + patch.object(App, '_load_fonts'), + patch.object(App, '_post_init'), + patch.object(App, '_prune_old_logs'), + patch.object(App, '_init_ai_and_hooks') + ): + return App() + +@pytest.fixture +def app_instance() -> Generator[App, None, None]: + """ + Centralized App instance with all external side effects mocked. + Matches the pattern used in test_token_viz.py and test_gui_phase4.py. + """ + with ( + patch('gui_2.load_config', return_value={ + 'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, + 'projects': {'paths': [], 'active': ''}, + 'gui': {'show_windows': {}} + }), + patch('gui_2.save_config'), + patch('gui_2.project_manager'), + patch('gui_2.session_logger'), + patch('gui_2.immapp.run'), + patch.object(App, '_load_active_project'), + patch.object(App, '_fetch_models'), + patch.object(App, '_load_fonts'), + patch.object(App, '_post_init'), + patch.object(App, '_prune_old_logs'), + patch.object(App, '_init_ai_and_hooks') + ): + app = App() + yield app + # Cleanup: Ensure background threads and asyncio loop are stopped + if hasattr(app, 'shutdown'): + app.shutdown() + + if hasattr(app, '_loop') and not app._loop.is_closed(): + tasks = [t for t in asyncio.all_tasks(app._loop) if not t.done()] + if tasks: + # Cancel tasks so they can be gathered + for task in tasks: + task.cancel() + # We can't really run the loop if it's already stopping or thread is dead, + # but we try to be clean. + try: + if app._loop.is_running(): + app._loop.call_soon_threadsafe(app._loop.stop) + except: pass + + # Finally close the loop if we can + try: + if not app._loop.is_running(): + app._loop.close() + except: pass + @pytest.fixture(scope="session") 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. + Includes high-signal environment telemetry and workspace isolation. """ - gui_script = "gui_2.py" + gui_script = os.path.abspath("gui_2.py") diag = VerificationLogger("live_gui_startup", "live_gui_diag") - diag.log_state("GUI Script", "N/A", gui_script) + diag.log_state("GUI Script", "N/A", "gui_2.py") + + # 1. Create a isolated workspace for the live GUI + temp_workspace = Path("tests/artifacts/live_gui_workspace") + if temp_workspace.exists(): + shutil.rmtree(temp_workspace) + temp_workspace.mkdir(parents=True, exist_ok=True) + + # Create dummy config and project files to avoid cluttering root + (temp_workspace / "config.toml").write_text("[projects]\npaths = []\nactive = ''\n", encoding="utf-8") + (temp_workspace / "manual_slop.toml").write_text("[project]\nname = 'TestProject'\n", encoding="utf-8") + (temp_workspace / "conductor" / "tracks").mkdir(parents=True, exist_ok=True) # Check if already running (shouldn't be) try: @@ -148,14 +178,22 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: 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...") + print(f"\n[Fixture] Starting {gui_script} --enable-test-hooks in {temp_workspace}...") os.makedirs("logs", exist_ok=True) log_file = open(f"logs/{gui_script.replace('.', '_')}_test.log", "w", encoding="utf-8") + + # Use environment variable to point to temp config if App supports it, + # or just run from that CWD. + env = os.environ.copy() + env["PYTHONPATH"] = os.getcwd() + process = subprocess.Popen( ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"], stdout=log_file, stderr=log_file, text=True, + cwd=str(temp_workspace.absolute()), + env=env, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0 ) @@ -202,3 +240,7 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: except: pass kill_process_tree(process.pid) log_file.close() + # Cleanup temp workspace + try: + shutil.rmtree(temp_workspace) + except: pass diff --git a/tests/test_gui_phase4.py b/tests/test_gui_phase4.py index 79e9a60..38ddf74 100644 --- a/tests/test_gui_phase4.py +++ b/tests/test_gui_phase4.py @@ -77,7 +77,16 @@ def test_delete_ticket_logic(mock_app: App): return label == "Delete##T-001" mock_imgui.button.side_effect = button_side_effect mock_imgui.tree_node_ex.return_value = True - mock_imgui.get_window_draw_list.return_value.add_rect_filled = MagicMock() + # Ensure get_color_u32 returns an integer to satisfy real C++ objects + mock_imgui.get_color_u32.return_value = 0xFFFFFFFF + # Ensure get_window_draw_list returns a fully mocked object + mock_draw_list = MagicMock() + mock_imgui.get_window_draw_list.return_value = mock_draw_list + mock_draw_list.add_rect_filled = MagicMock() + + # Mock ImVec2/ImVec4 types if vec4 creates real ones + mock_imgui.ImVec2 = MagicMock + mock_imgui.ImVec4 = MagicMock with patch.object(mock_app, '_push_mma_state_update') as mock_push: # Render T-001