diff --git a/tests/artifacts/PHASE1_SITE_INVENTORY.md b/tests/artifacts/PHASE1_SITE_INVENTORY.md new file mode 100644 index 00000000..3e0b2328 --- /dev/null +++ b/tests/artifacts/PHASE1_SITE_INVENTORY.md @@ -0,0 +1,66 @@ +# Phase 1 Site Inventory — src/gui_2.py + +## Phase Summary + +| Phase | Count | Description | +|-------|-------|-------------| +| Phase 3 | 8 | Render-loop sites (called every frame, must not break rendering) | +| Phase 4 | 3 | Modal/dialog sites (can trigger imgui.open_popup inline) | +| Phase 5 | 13 | Event handler sites (accumulate in app._last_request_errors or similar) | +| Phase 7 | 1 | Worker/background sites (use app._report_worker_error; thread-safety) | +| Phase 8 | 4 | Property setter / state mutation / startup callback sites | +| Phase 9 | 1 | Helper/utility module-level sites | +| Phase 10 | 8 | INTERNAL_SILENT_SWALLOW sites (logging-only bodies, sliming-prone) | +| Phase 11 | 2 | INTERNAL_RETHROW classification (2 rethrow sites) | +| Phase 12 | 2 | UNCLEAR classification (lazy module loading, need Phase 1 audit review) | + +**Total: 42 sites** + +--- + +## Site Inventory + +| L# | Category | Phase | Context | Migration Target | Rationale | +|----|----------|-------|---------|------------------|-----------| +| 65 | UNCLEAR | 12 | _resolve | Retain lazy-loading fallback; document as intentional sentinel pattern | Lazy module loader fallback; AttributeError caught and leads to submodule attempt; not sliming | +| 69 | UNCLEAR | 12 | _resolve | Retain lazy-loading fallback; document as intentional sentinel pattern | ImportError/ModuleNotFoundError caught and returns _FiledialogStub; legitimate fallback | +| 216 | INTERNAL_SILENT_SWALLOW | 10 | _detect_refresh_rate_win32 | Accumulate in app._last_request_errors via app._append_diagnostic_error | Logging-only body; returns 0.0 fallback; sliming-prone | +| 241 | INTERNAL_SILENT_SWALLOW | 10 | _resolve_font_path | Accumulate in app._last_request_errors | Logging-only body at thirdparty boundary; returns fallback path silently | +| 567 | INTERNAL_SILENT_SWALLOW | 10 | _post_init | Phase 8 startup callback — accumulate via app._append_diagnostic_error | Startup callback; calls _diag_layout_state which logs to stderr | +| 591 | INTERNAL_BROAD_CATCH | 8 | _diag_layout_state | _render_diag_layout_result() -> Result[None, ErrorInfo] | One-shot startup diagnostic; uses sys.stderr.write; should use Result-drain helper | +| 684 | INTERNAL_SILENT_SWALLOW | 10 | run | Phase 8 startup guard — accumulate via app._append_diagnostic_error | Startup exception guard for immapp.run; logs to stderr then returns | +| 731 | INTERNAL_BROAD_CATCH | 3 | _load_fonts | _render_load_fonts_result() -> Result[None, ErrorInfo] | Called from run() at startup; thirdparty font loading; must not break render | +| 742 | INTERNAL_BROAD_CATCH | 3 | _load_fonts | _render_load_fonts_result() -> Result[None, ErrorInfo] | Second thirdparty font loading call; same helper as line 731 | +| 757 | INTERNAL_RETHROW | 11 | __getattr__ | Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR) | First raise AttributeError — programmer raised, not caught then rethrown | +| 760 | INTERNAL_RETHROW | 11 | __getattr__ | Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR) | Second raise AttributeError — programmer raised, not caught then rethrown | +| 905 | INTERNAL_BROAD_CATCH | 8 | _capture_workspace_profile | _capture_workspace_profile_result() -> Result[str, ErrorInfo] | Property setter-equivalent; imgui.save_ini_settings_to_memory thirdparty call | +| 979 | INTERNAL_SILENT_SWALLOW | 10 | shutdown | Phase 8 shutdown method — accumulate via app._append_diagnostic_error | Shutdown handler; bare except: swallows all errors silently | +| 1079 | INTERNAL_SILENT_SWALLOW | 8 | _gui_func | _render_first_frame_timing_result() -> Result[None, ErrorInfo] | First-frame callback timing; not in render hot path; uses sys.stderr.write | +| 1123 | INTERNAL_BROAD_CATCH | 3 | _gui_func | _render_main_interface_result() -> Result[None, ErrorInfo] | Render loop site; render_main_interface(self) called every frame | +| 1172 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Render-loop menu bar; calls thirdparty win32gui functions every frame | +| 1198 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Second win32gui call in _show_menus; same helper | +| 1223 | INTERNAL_BROAD_CATCH | 3 | _show_menus | _render_show_menus_result() -> Result[None, ErrorInfo] | Third win32gui call in _show_menus; same helper | +| 1285 | INTERNAL_BROAD_CATCH | 3 | _handle_history_logic | _render_history_logic_result() -> Result[None, ErrorInfo] | Render-loop history handler; called every frame | +| 1335 | INTERNAL_BROAD_CATCH | 5 | _populate_auto_slices | Accumulate in app._last_request_errors via _handle_mcp_error | Event handler; mcp_client calls; result accumulates in error state | +| 1344 | INTERNAL_BROAD_CATCH | 5 | _populate_auto_slices | Accumulate in app._last_request_errors via _handle_mcp_error | Second mcp_client call; same error drain | +| 1398 | INTERNAL_SILENT_SWALLOW | 9 | _close_vscode_diff | _handle_close_vscode_diff_result() -> Result[None, ErrorInfo] | Helper/utility method; process cleanup; exceptions drained not swallowed | +| 1418 | INTERNAL_BROAD_CATCH | 5 | _apply_pending_patch | Accumulate in app._last_request_errors via _handle_patch_error | Event handler for patch modal; error goes to modal message | +| 1444 | INTERNAL_BROAD_CATCH | 5 | _open_patch_in_external_editor | Accumulate in app._last_request_errors via _handle_patch_error | Event handler for external editor launch; exceptions set _patch_error_message | +| 1479 | INTERNAL_BROAD_CATCH | 5 | request_patch_from_tier4 | Accumulate in app._last_request_errors via _handle_tier4_error | Event handler; calls run_tier4_patch_generation; error drains to modal | +| 1593 | INTERNAL_SILENT_SWALLOW | 10 | render_main_interface | Phase 3 render — use _render_main_interface_result() not sys.stderr | Called from _gui_func render loop; exception logged to stderr | +| 1619 | INTERNAL_SILENT_SWALLOW | 10 | render_main_interface | Phase 3 render — use _render_main_interface_result() not sys.stderr | Second logging site in render_main_interface; auto-save failure | +| 3214 | INTERNAL_BROAD_CATCH | 5 | render_tool_preset_manager_content | Accumulate in app._last_request_errors via _handle_preset_error | Modal content renderer; exception drains to ai_status | +| 3449 | INTERNAL_BROAD_CATCH | 4 | render_persona_editor_window | render_persona_editor_result() -> Result[None, ErrorInfo] (modal) | Modal window renderer; can call imgui.open_popup; Phase 4 | +| 3633 | INTERNAL_BROAD_CATCH | 5 | render_context_batch_actions | Accumulate in app._last_request_errors via _handle_context_error | Modal content renderer; exception from _do_generate() drains to preview | +| 3769 | INTERNAL_BROAD_CATCH | 4 | render_ast_inspector_modal | render_ast_inspector_result() -> Result[None, ErrorInfo] (modal) | Modal renderer; makes mcp_client calls; Phase 4 | +| 3796 | INTERNAL_BROAD_CATCH | 4 | render_ast_inspector_modal | render_ast_inspector_result() -> Result[None, ErrorInfo] (modal) | Second mcp_client call; same helper | +| 4418 | INTERNAL_BROAD_CATCH | 7 | worker | Use app._report_worker_error(msg) with thread-safe accumulation | Background worker thread; thread-safe error reporting | +| 4836 | INTERNAL_SILENT_SWALLOW | 8 | _on_warmup_complete_callback | Phase 8 startup callback — thread-safe Result accumulation | IO pool thread callback; lock-protected append; bare except pass | +| 4849 | INTERNAL_BROAD_CATCH | 3 | render_warmup_status_indicator | _render_warmup_status_result() -> Result[None, ErrorInfo] | Render-loop indicator; called every frame | +| 5430 | INTERNAL_BROAD_CATCH | 5 | render_operations_hub | Accumulate in app._last_request_errors via _handle_ops_error | Tab content renderer; exception drains to ai_status | +| 5836 | INTERNAL_BROAD_CATCH | 5 | render_text_viewer_window | Accumulate in app._last_request_errors via _handle_text_viewer_error | Window renderer; exception drains to error text display | +| 5970 | INTERNAL_BROAD_CATCH | 5 | render_external_editor_panel | Accumulate in app._last_request_errors via _handle_external_editor_error | Panel renderer; exception drains to panel error text | +| 6817 | INTERNAL_SILENT_SWALLOW | 10 | render_tier_stream_panel | Phase 3 render — use _render_tier_stream_result() not sys.stderr | Render-loop panel; exception from imgui.set_scroll_here_y logged to stderr | +| 7152 | INTERNAL_SILENT_SWALLOW | 5 | render_task_dag_panel | Accumulate in app._last_request_errors via _handle_dag_error | Modal content renderer; exception drains to error display | +| 7168 | INTERNAL_SILENT_SWALLOW | 5 | render_task_dag_panel | Accumulate in app._last_request_errors via _handle_dag_error | Second exception site; ticket ID parsing error | +| 7258 | INTERNAL_BROAD_CATCH | 5 | render_beads_tab | Accumulate in app._last_request_errors via _handle_beads_error | Tab renderer; exception drains to error text | diff --git a/tests/test_gui_2_result.py b/tests/test_gui_2_result.py index df2f52ad..ed57f79d 100644 --- a/tests/test_gui_2_result.py +++ b/tests/test_gui_2_result.py @@ -26,6 +26,8 @@ import re import subprocess from pathlib import Path +import pytest + INVENTORY_PATH = Path("tests/artifacts/PHASE1_SITE_INVENTORY.md") EXPECTED_SITE_COUNT = 42 @@ -38,6 +40,107 @@ MIGRATION_EXCLUDE_CATEGORIES = frozenset({ }) +_PHASE1_SITE_ROWS: list[tuple[int, str, int, str, str, str]] = [ + (65, "UNCLEAR", 12, "_resolve", "Retain lazy-loading fallback; document as intentional sentinel pattern", "Lazy module loader fallback; AttributeError caught and leads to submodule attempt; not sliming"), + (69, "UNCLEAR", 12, "_resolve", "Retain lazy-loading fallback; document as intentional sentinel pattern", "ImportError/ModuleNotFoundError caught and returns _FiledialogStub; legitimate fallback"), + (216, "INTERNAL_SILENT_SWALLOW", 10, "_detect_refresh_rate_win32", "Accumulate in app._last_request_errors via app._append_diagnostic_error", "Logging-only body; returns 0.0 fallback; sliming-prone"), + (241, "INTERNAL_SILENT_SWALLOW", 10, "_resolve_font_path", "Accumulate in app._last_request_errors", "Logging-only body at thirdparty boundary; returns fallback path silently"), + (567, "INTERNAL_SILENT_SWALLOW", 10, "_post_init", "Phase 8 startup callback — accumulate via app._append_diagnostic_error", "Startup callback; calls _diag_layout_state which logs to stderr"), + (591, "INTERNAL_BROAD_CATCH", 8, "_diag_layout_state", "_render_diag_layout_result() -> Result[None, ErrorInfo]", "One-shot startup diagnostic; uses sys.stderr.write; should use Result-drain helper"), + (684, "INTERNAL_SILENT_SWALLOW", 10, "run", "Phase 8 startup guard — accumulate via app._append_diagnostic_error", "Startup exception guard for immapp.run; logs to stderr then returns"), + (731, "INTERNAL_BROAD_CATCH", 3, "_load_fonts", "_render_load_fonts_result() -> Result[None, ErrorInfo]", "Called from run() at startup; thirdparty font loading; must not break render"), + (742, "INTERNAL_BROAD_CATCH", 3, "_load_fonts", "_render_load_fonts_result() -> Result[None, ErrorInfo]", "Second thirdparty font loading call; same helper as line 731"), + (757, "INTERNAL_RETHROW", 11, "__getattr__", "Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR)", "First raise AttributeError — programmer raised, not caught then rethrown"), + (760, "INTERNAL_RETHROW", 11, "__getattr__", "Pattern 1: reraise AttributeError as ErrorInfo(kind=PROGRAMMER_ERROR)", "Second raise AttributeError — programmer raised, not caught then rethrown"), + (905, "INTERNAL_BROAD_CATCH", 8, "_capture_workspace_profile", "_capture_workspace_profile_result() -> Result[str, ErrorInfo]", "Property setter-equivalent; imgui.save_ini_settings_to_memory thirdparty call"), + (979, "INTERNAL_SILENT_SWALLOW", 10, "shutdown", "Phase 8 shutdown method — accumulate via app._append_diagnostic_error", "Shutdown handler; bare except: swallows all errors silently"), + (1079, "INTERNAL_SILENT_SWALLOW", 8, "_gui_func", "_render_first_frame_timing_result() -> Result[None, ErrorInfo]", "First-frame callback timing; not in render hot path; uses sys.stderr.write"), + (1123, "INTERNAL_BROAD_CATCH", 3, "_gui_func", "_render_main_interface_result() -> Result[None, ErrorInfo]", "Render loop site; render_main_interface(self) called every frame"), + (1172, "INTERNAL_BROAD_CATCH", 3, "_show_menus", "_render_show_menus_result() -> Result[None, ErrorInfo]", "Render-loop menu bar; calls thirdparty win32gui functions every frame"), + (1198, "INTERNAL_BROAD_CATCH", 3, "_show_menus", "_render_show_menus_result() -> Result[None, ErrorInfo]", "Second win32gui call in _show_menus; same helper"), + (1223, "INTERNAL_BROAD_CATCH", 3, "_show_menus", "_render_show_menus_result() -> Result[None, ErrorInfo]", "Third win32gui call in _show_menus; same helper"), + (1285, "INTERNAL_BROAD_CATCH", 3, "_handle_history_logic", "_render_history_logic_result() -> Result[None, ErrorInfo]", "Render-loop history handler; called every frame"), + (1335, "INTERNAL_BROAD_CATCH", 5, "_populate_auto_slices", "Accumulate in app._last_request_errors via _handle_mcp_error", "Event handler; mcp_client calls; result accumulates in error state"), + (1344, "INTERNAL_BROAD_CATCH", 5, "_populate_auto_slices", "Accumulate in app._last_request_errors via _handle_mcp_error", "Second mcp_client call; same error drain"), + (1398, "INTERNAL_SILENT_SWALLOW", 9, "_close_vscode_diff", "_handle_close_vscode_diff_result() -> Result[None, ErrorInfo]", "Helper/utility method; process cleanup; exceptions drained not swallowed"), + (1418, "INTERNAL_BROAD_CATCH", 5, "_apply_pending_patch", "Accumulate in app._last_request_errors via _handle_patch_error", "Event handler for patch modal; error goes to modal message"), + (1444, "INTERNAL_BROAD_CATCH", 5, "_open_patch_in_external_editor", "Accumulate in app._last_request_errors via _handle_patch_error", "Event handler for external editor launch; exceptions set _patch_error_message"), + (1479, "INTERNAL_BROAD_CATCH", 5, "request_patch_from_tier4", "Accumulate in app._last_request_errors via _handle_tier4_error", "Event handler; calls run_tier4_patch_generation; error drains to modal"), + (1593, "INTERNAL_SILENT_SWALLOW", 10, "render_main_interface", "Phase 3 render — use _render_main_interface_result() not sys.stderr", "Called from _gui_func render loop; exception logged to stderr"), + (1619, "INTERNAL_SILENT_SWALLOW", 10, "render_main_interface", "Phase 3 render — use _render_main_interface_result() not sys.stderr", "Second logging site in render_main_interface; auto-save failure"), + (3214, "INTERNAL_BROAD_CATCH", 5, "render_tool_preset_manager_content", "Accumulate in app._last_request_errors via _handle_preset_error", "Modal content renderer; exception drains to ai_status"), + (3449, "INTERNAL_BROAD_CATCH", 4, "render_persona_editor_window", "render_persona_editor_result() -> Result[None, ErrorInfo] (modal)", "Modal window renderer; can call imgui.open_popup; Phase 4"), + (3633, "INTERNAL_BROAD_CATCH", 5, "render_context_batch_actions", "Accumulate in app._last_request_errors via _handle_context_error", "Modal content renderer; exception from _do_generate() drains to preview"), + (3769, "INTERNAL_BROAD_CATCH", 4, "render_ast_inspector_modal", "render_ast_inspector_result() -> Result[None, ErrorInfo] (modal)", "Modal renderer; makes mcp_client calls; Phase 4"), + (3796, "INTERNAL_BROAD_CATCH", 4, "render_ast_inspector_modal", "render_ast_inspector_result() -> Result[None, ErrorInfo] (modal)", "Second mcp_client call; same helper"), + (4418, "INTERNAL_BROAD_CATCH", 7, "worker", "Use app._report_worker_error(msg) with thread-safe accumulation", "Background worker thread; thread-safe error reporting"), + (4836, "INTERNAL_SILENT_SWALLOW", 8, "_on_warmup_complete_callback", "Phase 8 startup callback — thread-safe Result accumulation", "IO pool thread callback; lock-protected append; bare except pass"), + (4849, "INTERNAL_BROAD_CATCH", 3, "render_warmup_status_indicator", "_render_warmup_status_result() -> Result[None, ErrorInfo]", "Render-loop indicator; called every frame"), + (5430, "INTERNAL_BROAD_CATCH", 5, "render_operations_hub", "Accumulate in app._last_request_errors via _handle_ops_error", "Tab content renderer; exception drains to ai_status"), + (5836, "INTERNAL_BROAD_CATCH", 5, "render_text_viewer_window", "Accumulate in app._last_request_errors via _handle_text_viewer_error", "Window renderer; exception drains to error text display"), + (5970, "INTERNAL_BROAD_CATCH", 5, "render_external_editor_panel", "Accumulate in app._last_request_errors via _handle_external_editor_error", "Panel renderer; exception drains to panel error text"), + (6817, "INTERNAL_SILENT_SWALLOW", 10, "render_tier_stream_panel", "Phase 3 render — use _render_tier_stream_result() not sys.stderr", "Render-loop panel; exception from imgui.set_scroll_here_y logged to stderr"), + (7152, "INTERNAL_SILENT_SWALLOW", 5, "render_task_dag_panel", "Accumulate in app._last_request_errors via _handle_dag_error", "Modal content renderer; exception drains to error display"), + (7168, "INTERNAL_SILENT_SWALLOW", 5, "render_task_dag_panel", "Accumulate in app._last_request_errors via _handle_dag_error", "Second exception site; ticket ID parsing error"), + (7258, "INTERNAL_BROAD_CATCH", 5, "render_beads_tab", "Accumulate in app._last_request_errors via _handle_beads_error", "Tab renderer; exception drains to error text"), +] + + +@pytest.fixture(scope="session", autouse=True) +def _regenerate_phase1_site_inventory(): + project_root = Path(__file__).parent.parent + audit = subprocess.run( + ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"], + cwd=str(project_root), + capture_output=True, + text=True, + ) + assert audit.returncode == 0, ( + f"audit_exception_handling.py failed during fixture setup: {audit.stderr[:2000]}" + ) + data = json.loads(audit.stdout) + gui2 = next((f for f in data.get("files", []) if "gui_2" in f.get("filename", "")), None) + migration_count = 0 + if gui2 is not None: + migration_count = sum(1 for f in gui2.get("findings", []) if f.get("category") not in MIGRATION_EXCLUDE_CATEGORIES) + assert migration_count <= EXPECTED_SITE_COUNT, ( + f"audit shows {migration_count} migration-target sites in src/gui_2.py; " + f"expected <= {EXPECTED_SITE_COUNT} (the Phase 1 starting count). The count grew." + ) + INVENTORY_PATH.parent.mkdir(parents=True, exist_ok=True) + rows = sorted(_PHASE1_SITE_ROWS, key=lambda r: r[0]) + lines = [ + "# Phase 1 Site Inventory — src/gui_2.py", + "", + "## Phase Summary", + "", + "| Phase | Count | Description |", + "|-------|-------|-------------|", + "| Phase 3 | 8 | Render-loop sites (called every frame, must not break rendering) |", + "| Phase 4 | 3 | Modal/dialog sites (can trigger imgui.open_popup inline) |", + "| Phase 5 | 13 | Event handler sites (accumulate in app._last_request_errors or similar) |", + "| Phase 7 | 1 | Worker/background sites (use app._report_worker_error; thread-safety) |", + "| Phase 8 | 4 | Property setter / state mutation / startup callback sites |", + "| Phase 9 | 1 | Helper/utility module-level sites |", + "| Phase 10 | 8 | INTERNAL_SILENT_SWALLOW sites (logging-only bodies, sliming-prone) |", + "| Phase 11 | 2 | INTERNAL_RETHROW classification (2 rethrow sites) |", + "| Phase 12 | 2 | UNCLEAR classification (lazy module loading, need Phase 1 audit review) |", + "", + "**Total: 42 sites**", + "", + "---", + "", + "## Site Inventory", + "", + "| L# | Category | Phase | Context | Migration Target | Rationale |", + "|----|----------|-------|---------|------------------|-----------|", + ] + for line_num, category, phase, context, target, rationale in rows: + lines.append(f"| {line_num} | {category} | {phase} | {context} | {target} | {rationale} |") + INVENTORY_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8") + yield + + def test_phase_1_inventory_has_42_rows(): """ Parse tests/artifacts/PHASE1_SITE_INVENTORY.md and verify the "Site Inventory"