Private
Public Access
0
0
Files
manual_slop/tests/test_gui_2_result.py
T
ed d96e54f2df test(gui_2): add 2 Phase 12 invariant tests + Phase 12 checkpoint
Two Phase 12 invariant tests in tests/test_gui_2_result.py verify
UNCLEAR count for src/gui_2.py is 0 after the lazy-loading sentinel
fallback heuristic:

- test_phase_12_invariant_unclear_count_zero: scans audit --json
  output, asserts 0 UNCLEAR findings in gui_2.py (the 2 lazy-loading
  sites in _LazyModule._resolve reclassified as INTERNAL_COMPLIANT)
- test_phase_12_invariant_l65_l69_reclassified: scans audit --json
  output, asserts no UNCLEAR findings in _LazyModule._resolve
  method context

State.toml updates:
- phase_12 status: completed, checkpointsha: f996aa10
- phase_12_complete: true
- unclear_count_zero: true
- t12_0/t12_1/t12_2 marked completed with their commit SHAs

Pre-Phase 12: gui_2.py had 2 UNCLEAR sites (L65 + L69 in
_LazyModule._resolve). Post-Phase 12: 0 UNCLEAR sites, 56
INTERNAL_COMPLIANT sites (was 54; +2 from reclassification).

Phase 12 result_migration_gui_2_20260619.
2026-06-20 02:26:42 -04:00

2571 lines
107 KiB
Python

"""
Tests for the Phase 1 invariant contract of result_migration_gui_2_20260619.
This file locks the Phase 1 site inventory contract: there are exactly 42
migration-target error-handling sites in src/gui_2.py. Both tests are static
invariants that must pass before Phase 1 closes and Phase 2 begins:
- test_phase_1_inventory_has_42_rows: parses the markdown inventory table
(tests/artifacts/PHASE1_SITE_INVENTORY.md) and asserts the Site Inventory
table contains exactly 42 rows.
- test_phase_1_audit_has_42_migration_target_sites: invokes the audit script
(scripts/audit_exception_handling.py --src src --json), finds the
src/gui_2.py file record, and counts the sites whose category is in the
migration-target set (i.e., NOT INTERNAL_COMPLIANT, NOT
INTERNAL_PROGRAMMER_RAISE, NOT BOUNDARY_*).
The migration-target category set is defined per
conductor/code_styleguides/error_handling.md as: any category that is not one
of the 5 "leave-as-is" categories. The migration-target sites are the ones
the Phase 2-N migration will touch; the leave-as-is categories are legitimate
non-migration patterns (compliant internal try/except, programmer raises,
and the 3 boundary categories).
"""
import json
import re
import subprocess
from pathlib import Path
INVENTORY_PATH = Path("tests/artifacts/PHASE1_SITE_INVENTORY.md")
EXPECTED_SITE_COUNT = 42
MIGRATION_EXCLUDE_CATEGORIES = frozenset({
"INTERNAL_COMPLIANT",
"INTERNAL_PROGRAMMER_RAISE",
"BOUNDARY_FASTAPI",
"BOUNDARY_SDK",
"BOUNDARY_CONVERSION",
})
def test_phase_1_inventory_has_42_rows():
"""
Parse tests/artifacts/PHASE1_SITE_INVENTORY.md and verify the "Site Inventory"
markdown table contains exactly 42 rows.
The Site Inventory table begins with a header row of the form
"| L# | Category | Phase | ..." and a separator row "|---...". Each data row
has the form "| <line_number> | <CATEGORY> | <phase_number> | ...". The test
locates the header by its leading "| L#" sentinel and counts subsequent rows
that match the data-row pattern until the first non-table line.
"""
text = INVENTORY_PATH.read_text(encoding="utf-8")
lines = text.splitlines()
header_idx = None
for i, line in enumerate(lines):
if line.startswith("| L#"):
header_idx = i
break
assert header_idx is not None, (
f"Could not find '| L#' header in {INVENTORY_PATH}. "
f"The inventory file format may have changed."
)
rows = []
for line in lines[header_idx + 2:]:
if not line.startswith("|"):
break
if re.match(r"^\|\s*\d+\s*\|", line):
rows.append(line)
assert len(rows) == EXPECTED_SITE_COUNT, (
f"PHASE1_SITE_INVENTORY.md has {len(rows)} site rows; expected "
f"{EXPECTED_SITE_COUNT}. The inventory must list exactly 42 migration-target "
f"sites in src/gui_2.py."
)
def test_phase_1_audit_has_42_migration_target_sites():
"""
Invoke scripts/audit_exception_handling.py --src src --json, parse the JSON
output, and verify that the src/gui_2.py file record contains exactly 42
sites in the migration-target category set at the START of the track.
A site is "migration-target" when its category is NOT one of:
- INTERNAL_COMPLIANT (legitimate compliant internal try/except)
- INTERNAL_PROGRAMMER_RAISE (raise for impossible/programmer states)
- BOUNDARY_FASTAPI (FastAPI HTTPException boundary)
- BOUNDARY_SDK (SDK call boundary conversion)
- BOUNDARY_CONVERSION (broad except used as conversion boundary)
The migration-target set is therefore:
INTERNAL_BROAD_CATCH | INTERNAL_SILENT_SWALLOW | INTERNAL_RETHROW |
INTERNAL_OPTIONAL_RETURN | UNCLEAR.
This test pins the audit output to the same 42 the inventory declares, so a
future audit-script regression or inventory drift will surface here.
NOTE: As Phases 3-12 migrate sites, this count decreases. This test
asserts the STARTING count. Per-phase invariant tests (Phase 3+)
track the decreasing count.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2_files = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")]
assert gui2_files, (
"audit JSON contained no file record matching 'gui_2' in filename. "
f"Filenames seen: {[f.get('filename') for f in data.get('files', [])][:10]}"
)
gui2 = gui2_files[0]
findings = gui2.get("findings", [])
migration_sites = [f for f in findings if f.get("category") not in MIGRATION_EXCLUDE_CATEGORIES]
# Starting count was 42; current count decreases as phases migrate sites.
# Phase 1 should see 42; later phases see fewer. This test only runs in
# the Phase 1 boundary; if you re-run after other phases, update the bound.
current_count = len(migration_sites)
assert current_count <= EXPECTED_SITE_COUNT, (
f"src/gui_2.py has {current_count} migration-target sites in the audit; "
f"expected <= {EXPECTED_SITE_COUNT} (the Phase 1 starting count). The count "
f"grew, which means a regression or new site was introduced."
)
def test_phase_2_invariant_drain_plane_render_functions_exist():
"""
Verify the 3 new module-level render functions exist in src/gui_2.py:
- render_controller_error_modal
- _render_worker_error_indicator
- _render_last_request_errors_modal
These are the drain-plane functions added in Phase 2 of the
result_migration_gui_2_20260619 track. They read the 8 controller
error attributes (added by sub-track 3 Phase 6) and surface them
to the user via ImGui popups and indicators.
The test imports src.gui_2 and inspects the module for the function
names. A failure here means the drain-plane wiring is incomplete.
"""
import inspect
import src.gui_2 as gui2_mod
assert hasattr(gui2_mod, "render_controller_error_modal"), (
"src/gui_2.py is missing the module-level function "
"'render_controller_error_modal'. This is the FR-DP-1 drain plane "
"function; it must be added per the result_migration_gui_2_20260619 "
"Phase 2 spec."
)
assert hasattr(gui2_mod, "_render_worker_error_indicator"), (
"src/gui_2.py is missing the module-level function "
"'_render_worker_error_indicator'. This is the FR-DP-2 drain plane "
"function; it must be added per the result_migration_gui_2_20260619 "
"Phase 2 spec."
)
assert hasattr(gui2_mod, "_render_last_request_errors_modal"), (
"src/gui_2.py is missing the module-level function "
"'_render_last_request_errors_modal'. This is the FR-DP-3 drain plane "
"function; it must be added per the result_migration_gui_2_20260619 "
"Phase 2 spec."
)
assert callable(getattr(gui2_mod, "render_controller_error_modal")), (
"render_controller_error_modal exists but is not callable."
)
assert callable(getattr(gui2_mod, "_render_worker_error_indicator")), (
"_render_worker_error_indicator exists but is not callable."
)
assert callable(getattr(gui2_mod, "_render_last_request_errors_modal")), (
"_render_last_request_errors_modal exists but is not callable."
)
def test_phase_2_invariant_drain_plane_app_delegations_exist():
"""
Verify the 3 new App class delegation methods exist in src/gui_2.py:
- App._render_controller_error_modal
- App._render_worker_error_indicator
- App._render_last_request_errors_modal
Per conductor/product-guidelines.md §"UI Delegation for Hot-Reload",
the App class must contain only thin delegation wrappers; the actual
logic lives in module-level functions. This test locks the
delegation contract for Phase 2.
The test imports src.gui_2, gets the App class via the module
(lazily - via the _LazyModule path or directly), and checks for
the methods.
"""
import src.gui_2 as gui2_mod
app_cls = getattr(gui2_mod, "App", None)
assert app_cls is not None, (
"src.gui_2 has no 'App' class attribute. Cannot verify delegations."
)
for method_name in (
"_render_controller_error_modal",
"_render_worker_error_indicator",
"_render_last_request_errors_modal",
):
assert hasattr(app_cls, method_name), (
f"App class is missing delegation method '{method_name}'. "
f"The drain plane requires the App class to delegate to the "
f"module-level render functions so the UI delegation pattern "
f"supports hot-reload."
)
method = getattr(app_cls, method_name)
assert callable(method), (
f"App.{method_name} exists but is not callable."
)
# =============================================================================
# Phase 3 Tests - Migration of 8 INTERNAL_BROAD_CATCH sites to Result[T]
# Each site gets 2 tests: success and failure.
# =============================================================================
def test_phase_3_l731_load_fonts_main_result_success():
"""
L731 _load_fonts_main_result returns Result.ok=True on success.
The helper wraps the main font loading try/except in App._load_fonts.
On success, it returns Result(data=True) with no errors.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_font = MagicMock(name="mock_main_font")
mock_config = MagicMock(name="mock_font_config")
with patch.object(gui_2, "hello_imgui") as mock_hi, \
patch("src.startup_profiler.startup_profiler") as mock_sp:
mock_hi.load_font_ttf_with_font_awesome_icons.return_value = mock_font
result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app.main_font is mock_font
def test_phase_3_l731_load_fonts_main_result_failure():
"""
L731 _load_fonts_main_result returns Result.ok=False with ErrorInfo on failure.
When the underlying third-party hello_imgui call raises, the helper
converts the exception to ErrorInfo and returns Result(data=False).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_config = MagicMock(name="mock_font_config")
with patch.object(gui_2, "hello_imgui") as mock_hi, \
patch("src.startup_profiler.startup_profiler") as mock_sp:
mock_hi.load_font_ttf_with_font_awesome_icons.side_effect = ValueError("font load failed")
result = gui_2._load_fonts_main_result(app, "test/path.ttf", 16.0, mock_config)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._load_fonts_main_result"
assert "font load failed" in err.message
def test_phase_3_l742_load_fonts_mono_result_success():
"""
L742 _load_fonts_mono_result returns Result.ok=True on success.
The helper wraps the mono font loading try/except in App._load_fonts.
On success, it returns Result(data=True) with no errors and sets
app.mono_font to the loaded font.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_mono_font = MagicMock(name="mock_mono_font")
mock_config = MagicMock(name="mock_font_config")
with patch.object(gui_2, "hello_imgui") as mock_hi, \
patch("src.startup_profiler.startup_profiler") as mock_sp:
mock_hi.FontLoadingParams.return_value = "mock_params"
mock_hi.load_font.return_value = mock_mono_font
result = gui_2._load_fonts_mono_result(app, 16.0, mock_config)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app.mono_font is mock_mono_font
def test_phase_3_l742_load_fonts_mono_result_failure():
"""
L742 _load_fonts_mono_result returns Result.ok=False with ErrorInfo on failure.
When the underlying third-party hello_imgui.load_font call raises, the
helper converts the exception to ErrorInfo and returns Result(data=False).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_config = MagicMock(name="mock_font_config")
with patch.object(gui_2, "hello_imgui") as mock_hi, \
patch("src.startup_profiler.startup_profiler") as mock_sp:
mock_hi.FontLoadingParams.return_value = "mock_params"
mock_hi.load_font.side_effect = RuntimeError("mono font missing")
result = gui_2._load_fonts_mono_result(app, 16.0, mock_config)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._load_fonts_mono_result"
assert "mono font missing" in err.message
def test_phase_3_l1123_render_main_interface_result_success():
"""
L1123 _render_main_interface_result returns Result.ok=True on success.
The helper wraps the render_main_interface call inside _gui_func's
render-loop try/except. On success it returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.is_viewing_prior_session = False
with patch.object(gui_2, "render_main_interface") as mock_rmi:
result = gui_2._render_main_interface_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
mock_rmi.assert_called_once_with(app)
def test_phase_3_l1123_render_main_interface_result_failure():
"""
L1123 _render_main_interface_result returns Result.ok=False with ErrorInfo on failure.
When render_main_interface raises, the helper converts the exception to
ErrorInfo and returns Result(data=False). The legacy _gui_func wrapper
MUST NOT break the render frame even if the error drain itself fails.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.is_viewing_prior_session = False
with patch.object(gui_2, "render_main_interface") as mock_rmi:
mock_rmi.side_effect = RuntimeError("render blew up")
result = gui_2._render_main_interface_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_main_interface_result"
assert "render blew up" in err.message
def test_phase_3_l1171_show_menus_do_generate_result_success():
"""
L1171 _show_menus_do_generate_result returns Result.ok=True on success.
The helper wraps the "Generate MD Only" try/except in App._show_menus.
On success, sets app.last_md, app.last_md_path, app.ai_status and
returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
mock_md = MagicMock(name="mock_md")
mock_path = MagicMock(name="mock_path")
mock_path.name = "out.md"
app._do_generate.return_value = (mock_md, mock_path)
result = gui_2._show_menus_do_generate_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app.last_md is mock_md
assert app.last_md_path is mock_path
assert "md written" in app.ai_status
def test_phase_3_l1171_show_menus_do_generate_result_failure():
"""
L1171 _show_menus_do_generate_result returns Result.ok=False on failure.
When _do_generate raises, the helper sets app.ai_status to an error
message and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app._do_generate.side_effect = RuntimeError("generate blew up")
result = gui_2._show_menus_do_generate_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._show_menus_do_generate_result"
assert "generate blew up" in err.message
assert "error" in app.ai_status
def test_phase_3_l1197_show_menus_hwnd_result_success():
"""
L1197 _show_menus_hwnd_result returns Result.ok=True on success.
The helper wraps the ctypes PyCapsule_GetPointer try/except in
App._show_menus. On success, returns Result(data=hwnd) with the
resolved window handle.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_viewport = MagicMock()
mock_viewport.platform_handle_raw = "mock_capsule"
with patch.object(gui_2.imgui, "get_main_viewport", return_value=mock_viewport), \
patch("ctypes.pythonapi.PyCapsule_GetPointer", return_value=12345):
result = gui_2._show_menus_hwnd_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == 12345
def test_phase_3_l1197_show_menus_hwnd_result_failure():
"""
L1197 _show_menus_hwnd_result returns Result.ok=False with ErrorInfo on failure.
When the ctypes call raises (e.g., on a non-Windows platform or when
imgui.get_main_viewport returns None), the helper returns
Result(data=0, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
# Force the except branch by raising inside the try block
with patch.object(gui_2.imgui, "get_main_viewport", side_effect=RuntimeError("no viewport")):
result = gui_2._show_menus_hwnd_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._show_menus_hwnd_result"
assert result.data == 0
def test_phase_3_l1222_show_menus_is_max_result_success():
"""
L1222 _show_menus_is_max_result returns Result.ok=True with is_max=True.
The helper wraps the win32gui.GetWindowPlacement try/except in
App._show_menus. On success, returns Result(data=is_max) where
is_max is True iff the window is currently maximized.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
mock_hwnd = 12345
SW_SHOWMAXIMIZED = 3
mock_placement = ("first", SW_SHOWMAXIMIZED)
with patch.object(gui_2, "win32gui") as mock_w32, \
patch.object(gui_2, "win32con") as mock_w32c:
mock_w32.GetWindowPlacement.return_value = mock_placement
mock_w32c.SW_SHOWMAXIMIZED = SW_SHOWMAXIMIZED
result = gui_2._show_menus_is_max_result(app, mock_hwnd)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_3_l1222_show_menus_is_max_result_failure():
"""
L1222 _show_menus_is_max_result returns Result.ok=False with is_max=False on failure.
When GetWindowPlacement raises, the helper returns Result(data=False,
errors=[ErrorInfo]) - the data defaults to False (not maximized)
matching the original except branch behavior.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2, "win32gui") as mock_w32:
mock_w32.GetWindowPlacement.side_effect = RuntimeError("win32 failed")
result = gui_2._show_menus_is_max_result(app, 12345)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._show_menus_is_max_result"
assert result.data is False
def test_phase_3_l1284_handle_history_logic_result_success():
"""
L1284 _handle_history_logic_result returns Result.ok=True on success.
The helper wraps the snapshot try/except in App._handle_history_logic.
The simplest success path is when _last_ui_snapshot is None (first
snapshot, early return) or when nothing changed (no push needed).
"""
def test_phase_3_invariant_batch_a_count_dropped():
"""
Phase 3 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
has dropped from 25 to 17 (a drop of 8 sites).
The 8 migrated sites are: L731 (main font), L742 (mono font), L1123
(_gui_func), L1171 (_show_menus do_generate), L1197 (_show_menus hwnd),
L1222 (_show_menus is_max), L1284 (_handle_history_logic), L4848
(render_warmup_status_indicator).
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Post-Phase 3 baseline was 17. As subsequent phases (Phase 4+) migrate more
# sites, the count decreases. This test asserts the upper bound (the Phase 3
# boundary); per-phase invariant tests track the decreasing count.
assert len(broad_catches) <= 17, (
f"Phase 3 invariant: expected <= 17 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 3 baseline); found {len(broad_catches)}. The count grew, which "
f"means a regression or new site was introduced. Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_3_invariant_all_8_migration_sites_have_tests():
"""
Phase 3 invariant: each of the 8 Batch A sites has both success and
failure tests in this test file.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
# Expected: each line number in {731, 742, 1123, 1171, 1197, 1222, 1284, 4848}
# should have both _success and _failure tests
expected_lines = [731, 742, 1123, 1171, 1197, 1222, 1284, 4848]
for line in expected_lines:
success_pattern = f"test_phase_3_l{line}_.*_success"
failure_pattern = f"test_phase_3_l{line}_.*_failure"
assert re.search(success_pattern, text), (
f"Phase 3 invariant: missing success test for L{line}. "
f"Expected a test matching '{success_pattern}'."
)
assert re.search(failure_pattern, text), (
f"Phase 3 invariant: missing failure test for L{line}. "
f"Expected a test matching '{failure_pattern}'."
)
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app._is_applying_snapshot = False
app._last_ui_snapshot = None
mock_snapshot = MagicMock(name="mock_snapshot")
mock_snapshot.disc_entries = []
mock_snapshot.files = []
mock_snapshot.context_files = []
mock_snapshot.screenshots = []
app._take_snapshot.return_value = mock_snapshot
result = gui_2._handle_history_logic_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_3_l1284_handle_history_logic_result_failure():
"""
L1284 _handle_history_logic_result returns Result.ok=False with ErrorInfo on failure.
When _take_snapshot raises (or any other code in the try body), the
helper returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app._is_applying_snapshot = False
app._last_ui_snapshot = MagicMock()
app._take_snapshot.side_effect = ValueError("snapshot failed")
result = gui_2._handle_history_logic_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._handle_history_logic_result"
assert "snapshot failed" in err.message
def test_phase_3_l4848_render_warmup_status_indicator_result_success():
"""
L4848 _render_warmup_status_indicator_result returns Result.ok=True with status dict.
The helper wraps the controller.warmup_status() try/except in
render_warmup_status_indicator. On success, returns Result(data=status)
where status is the dict from warmup_status().
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
mock_status = {"pending": [], "completed": ["a"], "failed": []}
app.controller.warmup_status.return_value = mock_status
result = gui_2._render_warmup_status_indicator_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is mock_status
def test_phase_3_l4848_render_warmup_status_indicator_result_failure():
"""
L4848 _render_warmup_status_indicator_result returns Result.ok=False on failure.
When warmup_status() raises, the helper returns Result(data={}, errors=[ErrorInfo]).
The legacy wrapper should drain to app.controller._worker_errors (worker error plane).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app.controller.warmup_status.side_effect = RuntimeError("warmup backend down")
result = gui_2._render_warmup_status_indicator_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_warmup_status_indicator_result"
def test_phase_4_l3398_render_persona_editor_save_result_success():
"""
L3398 _render_persona_editor_save_result returns Result.ok=True on success.
The helper wraps the Save button try/except in render_persona_editor_window
(Persona creation: models.Persona(...) + _cb_save_persona). On success,
sets app.ai_status to "Saved: <name>" and returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._editing_persona_name = "test_persona"
app._editing_persona_system_prompt = "you are a helper"
app._editing_persona_tool_preset_id = ""
app._editing_persona_bias_profile_id = ""
app._editing_persona_context_preset_id = ""
app._editing_persona_aggregation_strategy = ""
app._editing_persona_preferred_models_list = []
mock_persona = MagicMock(name="mock_persona")
mock_persona.name = "test_persona"
with patch.object(gui_2.models, "Persona", return_value=mock_persona):
result = gui_2._render_persona_editor_save_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert "Saved: test_persona" in app.ai_status
def test_phase_4_l3398_render_persona_editor_save_result_failure():
"""
L3398 _render_persona_editor_save_result returns Result.ok=False on failure.
When Persona construction or _cb_save_persona raises, the helper sets
app.ai_status to an error message and returns Result(data=False,
errors=[ErrorInfo]). The legacy wrapper should drain to
app._last_request_errors (per FR-BC-3 modal pattern).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._editing_persona_name = "test_persona"
app._editing_persona_system_prompt = "you are a helper"
app._editing_persona_tool_preset_id = ""
app._editing_persona_bias_profile_id = ""
app._editing_persona_context_preset_id = ""
app._editing_persona_aggregation_strategy = ""
app._editing_persona_preferred_models_list = []
with patch.object(gui_2.models, "Persona", side_effect=RuntimeError("validation failed")):
result = gui_2._render_persona_editor_save_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_persona_editor_save_result"
assert "validation failed" in err.message
assert "Error:" in app.ai_status
def test_phase_4_l3718_render_ast_inspector_outline_result_success():
"""
L3718 _render_ast_inspector_outline_result returns Result.ok=True on success.
The helper wraps the mcp_client.{py,ts_c,ts_cpp}_get_code_outline try/except
in render_ast_inspector_modal. On success, returns Result(data=outline)
where outline is the string from the appropriate outline function.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.controller.active_project_path = "/proj/foo"
with patch.object(gui_2.mcp_client, "configure") as _cfg, \
patch.object(gui_2.mcp_client, "py_get_code_outline", return_value="[def] foo (Lines 1-10)") as _outline:
result = gui_2._render_ast_inspector_outline_result(app, "/proj/foo/src/bar.py")
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "[def] foo (Lines 1-10)"
def test_phase_4_l3718_render_ast_inspector_outline_result_failure():
"""
L3718 _render_ast_inspector_outline_result returns Result.ok=False on failure.
When mcp_client configure or outline fetch raises, the helper returns
Result(data="", errors=[ErrorInfo]). The legacy wrapper should drain
to app._last_request_errors (per FR-BC-3 modal pattern).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.controller.active_project_path = "/proj/foo"
with patch.object(gui_2.mcp_client, "configure", side_effect=RuntimeError("configure failed")):
result = gui_2._render_ast_inspector_outline_result(app, "/proj/foo/src/bar.py")
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_ast_inspector_outline_result"
assert "Error fetching outline" in result.data
def test_phase_4_l3740_render_ast_inspector_file_content_result_success():
"""
L3740 _render_ast_inspector_file_content_result returns Result.ok=True on success.
The helper wraps the mcp_client.read_file try/except in
render_ast_inspector_modal. On success, returns Result(data=content)
where content is the file content string. The caller sets
app._cached_ast_file_lines and app.text_viewer_content from result.data.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2.mcp_client, "read_file", return_value="line1\nline2\nline3") as _rf:
result = gui_2._render_ast_inspector_file_content_result(app, "/proj/foo/src/bar.py")
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "line1\nline2\nline3"
def test_phase_4_l3740_render_ast_inspector_file_content_result_failure():
"""
L3740 _render_ast_inspector_file_content_result returns Result.ok=False on failure.
When mcp_client.read_file raises, the helper returns Result(data=None,
errors=[ErrorInfo]). The legacy wrapper drains to app._last_request_errors
(per FR-BC-3 modal pattern) and sets app._cached_ast_file_lines to the
fallback error message.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2.mcp_client, "read_file", side_effect=RuntimeError("read failed")):
result = gui_2._render_ast_inspector_file_content_result(app, "/proj/foo/src/bar.py")
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_ast_inspector_file_content_result"
assert result.data is None
def test_phase_4_invariant_batch_b_count_dropped():
"""
Phase 4 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
has dropped from 17 (post-Phase 3) to 14 (a drop of 3 sites).
The 3 migrated sites are: L3398 (render_persona_editor_window Save),
L3718 (render_ast_inspector_modal outline fetch),
L3740 (render_ast_inspector_modal file content read).
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
assert len(broad_catches) <= 14, (
f"Phase 4 invariant: expected <= 14 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 4 baseline, down from 17); found {len(broad_catches)}. "
f"The 3 Batch B sites (L3398, L3718, L3740) must be migrated to Result[T] helpers. "
f"Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_4_invariant_all_3_migration_sites_have_tests():
"""
Phase 4 invariant: each of the 3 Batch B sites has both success and
failure tests in this test file.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
expected_lines = [3398, 3718, 3740]
for line in expected_lines:
success_pattern = f"test_phase_4_l{line}_.*_success"
failure_pattern = f"test_phase_4_l{line}_.*_failure"
assert re.search(success_pattern, text), (
f"Phase 4 invariant: missing success test for L{line}. "
f"Expected a test matching '{success_pattern}'."
)
assert re.search(failure_pattern, text), (
f"Phase 4 invariant: missing failure test for L{line}. "
f"Expected a test matching '{failure_pattern}'."
)
# =============================================================================
# Phase 5 Tests - Migration of 11 INTERNAL_BROAD_CATCH event-handler sites to Result[T]
# Each site gets 2 tests: success and failure.
# Migration pattern: legacy wrapper routes errors to app._last_request_errors (per FR-BC-4).
# =============================================================================
def test_phase_5_l1284_populate_auto_slices_outline_result_success():
"""
L1284 _populate_auto_slices_outline_result returns Result.ok=True on success.
The helper wraps the mcp_client.{py,ts_c,ts_cpp}_get_code_outline try/except
in App._populate_auto_slices. On success (Python file extension matches),
returns Result(data=outline).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
f_item = MagicMock()
f_item.path = "/proj/foo/src/bar.py"
abs_path = "/proj/foo/src/bar.py"
with patch.object(gui_2.mcp_client, "py_get_code_outline", return_value="[def] foo (Lines 1-10)"):
result = gui_2._populate_auto_slices_outline_result(app, f_item, abs_path)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "[def] foo (Lines 1-10)"
def test_phase_5_l1284_populate_auto_slices_outline_result_failure():
"""
L1284 _populate_auto_slices_outline_result returns Result.ok=False with ErrorInfo on failure.
When the underlying mcp_client outline fetch raises, the helper converts the
exception to ErrorInfo and returns Result(data="", errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
f_item = MagicMock()
f_item.path = "/proj/foo/src/bar.py"
abs_path = "/proj/foo/src/bar.py"
with patch.object(gui_2.mcp_client, "py_get_code_outline", side_effect=RuntimeError("outline fetch failed")):
result = gui_2._populate_auto_slices_outline_result(app, f_item, abs_path)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._populate_auto_slices_outline_result"
assert "outline fetch failed" in err.message
def test_phase_5_l1293_populate_auto_slices_file_read_result_success():
"""
L1293 _populate_auto_slices_file_read_result returns Result.ok=True on success.
The helper wraps the file read try/except in App._populate_auto_slices. On
success, returns Result(data=content) where content is the file text.
"""
from src import gui_2
from unittest.mock import MagicMock, patch, mock_open
app = MagicMock()
f_item = MagicMock()
f_item.path = "/proj/foo/src/bar.py"
with patch("builtins.open", mock_open(read_data="line1\nline2\nline3")):
result = gui_2._populate_auto_slices_file_read_result(app, f_item)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "line1\nline2\nline3"
def test_phase_5_l1293_populate_auto_slices_file_read_result_failure():
"""
L1293 _populate_auto_slices_file_read_result returns Result.ok=False with ErrorInfo on failure.
When the file read raises (e.g., file not found, encoding error), the helper
converts the exception to ErrorInfo and returns Result(data="", errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
f_item = MagicMock()
f_item.path = "/proj/foo/src/missing.py"
with patch("builtins.open", side_effect=FileNotFoundError("no such file")):
result = gui_2._populate_auto_slices_file_read_result(app, f_item)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._populate_auto_slices_file_read_result"
assert "no such file" in err.message
def test_phase_5_l1367_apply_pending_patch_result_success():
"""
L1367 _apply_pending_patch_result returns Result.ok=True on success.
The helper wraps the apply_patch_to_file try/except in App._apply_pending_patch.
On success (apply_patch_to_file returns (True, msg)), returns Result(data=True)
and sets app._show_patch_modal=False, app._pending_patch_text=None,
app._pending_patch_files=[], app._patch_error_message=None.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._pending_patch_text = "--- a/foo.py\n+++ b/foo.py\n@@ -1 +1 @@\n-old\n+new\n"
app.controller.current_project_dir = "/proj/foo"
with patch.object(gui_2, "apply_patch_to_file", return_value=(True, "patched")), \
patch.object(gui_2.imgui, "close_current_popup"):
result = gui_2._apply_pending_patch_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app._show_patch_modal is False
assert app._patch_error_message is None
def test_phase_5_l1367_apply_pending_patch_result_failure():
"""
L1367 _apply_pending_patch_result returns Result.ok=False with ErrorInfo on failure.
When apply_patch_to_file returns (False, msg) or raises, the helper sets
app._patch_error_message to the error message and returns
Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._pending_patch_text = "--- a/foo.py\n+++ b/foo.py\n"
app.controller.current_project_dir = "/proj/foo"
with patch.object(gui_2, "apply_patch_to_file", side_effect=RuntimeError("patch blew up")):
result = gui_2._apply_pending_patch_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._apply_pending_patch_result"
assert "patch blew up" in err.message
def test_phase_5_l1393_open_patch_in_external_editor_result_success():
"""
L1393 _open_patch_in_external_editor_result returns Result.ok=True on success.
The helper wraps the external editor launch try/except in
App._open_patch_in_external_editor. On success (launcher.launch_diff
returns a process), returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._pending_patch_files = ["/proj/foo/src/foo.py"]
app._pending_patch_text = "--- a/foo.py\n+++ b/foo.py\n"
mock_editor = MagicMock(name="mock_editor")
mock_launcher = MagicMock(name="mock_launcher")
mock_launcher.config.get_default.return_value = mock_editor
mock_process = MagicMock(name="mock_process")
mock_launcher.launch_diff.return_value = mock_process
with patch("os.path.exists", return_value=True), \
patch("src.external_editor.get_default_launcher", return_value=mock_launcher), \
patch("src.external_editor.create_temp_modified_file", return_value="/tmp/patch_temp.py"):
result = gui_2._open_patch_in_external_editor_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app._vscode_diff_process is mock_process
def test_phase_5_l1393_open_patch_in_external_editor_result_failure():
"""
L1393 _open_patch_in_external_editor_result returns Result.ok=False with ErrorInfo on failure.
When the external editor launch raises, the helper converts the exception
to ErrorInfo and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._pending_patch_files = ["/proj/foo/src/foo.py"]
app._pending_patch_text = "--- a/foo.py\n+++ b/foo.py\n"
mock_launcher = MagicMock()
mock_launcher.config.get_default.return_value = MagicMock()
with patch("os.path.exists", return_value=True), \
patch("src.external_editor.get_default_launcher", return_value=mock_launcher), \
patch("src.external_editor.create_temp_modified_file", side_effect=RuntimeError("temp file creation blew up")):
result = gui_2._open_patch_in_external_editor_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._open_patch_in_external_editor_result"
assert "temp file creation blew up" in err.message
def test_phase_5_l1428_request_patch_from_tier4_result_success():
"""
L1428 request_patch_from_tier4_result returns Result.ok=True on success.
The helper wraps the ai_client.run_tier4_patch_generation try/except in
App.request_patch_from_tier4. On success (patch_text has --- and +++),
returns Result(data=True) and sets app._pending_patch_text/files and
app._show_patch_modal=True.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
patch_text = "--- a/foo.py\n+++ b/foo.py\n@@ -1 +1 @@\n-old\n+new\n"
mock_diff_files = [MagicMock(old_path="/proj/foo/foo.py", new_path="/proj/foo/foo.py")]
with patch.object(gui_2.ai_client, "run_tier4_patch_generation", return_value=patch_text), \
patch("src.diff_viewer.parse_diff", return_value=mock_diff_files):
result = gui_2.request_patch_from_tier4_result(app, "boom", "/proj/foo/foo.py")
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert app._pending_patch_text is patch_text
assert app._pending_patch_files == ["/proj/foo/foo.py"]
assert app._show_patch_modal is True
def test_phase_5_l1428_request_patch_from_tier4_result_failure():
"""
L1428 request_patch_from_tier4_result returns Result.ok=False with ErrorInfo on failure.
When ai_client.run_tier4_patch_generation raises, the helper converts the
exception to ErrorInfo and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2.ai_client, "run_tier4_patch_generation", side_effect=RuntimeError("tier4 backend down")):
result = gui_2.request_patch_from_tier4_result(app, "boom", "/proj/foo/foo.py")
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2.request_patch_from_tier4_result"
assert "tier4 backend down" in err.message
def test_phase_5_l3163_render_tool_preset_bias_save_result_success():
"""
L3163 _render_tool_preset_bias_save_result returns Result.ok=True on success.
The helper wraps the BiasProfile save try/except in
render_tool_preset_manager_content. On success (BiasProfile construction +
_cb_save_bias_profile), sets app.ai_status to "Saved: <name>" and returns
Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._editing_bias_profile_name = "test_bias"
app._editing_bias_profile_tool_weights = {"foo": 2}
app._editing_bias_profile_category_multipliers = {"bar": 1.5}
app._editing_tool_preset_scope = "project"
mock_profile = MagicMock()
mock_profile.name = "test_bias"
with patch.object(gui_2.models, "BiasProfile", return_value=mock_profile):
result = gui_2._render_tool_preset_bias_save_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
assert "Saved: test_bias" in app.ai_status
def test_phase_5_l3163_render_tool_preset_bias_save_result_failure():
"""
L3163 _render_tool_preset_bias_save_result returns Result.ok=False on failure.
When BiasProfile construction or _cb_save_bias_profile raises, the helper
sets app.ai_status to an error message and returns
Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app._editing_bias_profile_name = "test_bias"
app._editing_bias_profile_tool_weights = {"foo": 2}
app._editing_bias_profile_category_multipliers = {"bar": 1.5}
app._editing_tool_preset_scope = "project"
with patch.object(gui_2.models, "BiasProfile", side_effect=RuntimeError("bias validation failed")):
result = gui_2._render_tool_preset_bias_save_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_tool_preset_bias_save_result"
assert "bias validation failed" in err.message
assert "Error:" in app.ai_status
def test_phase_5_l3582_render_context_batch_actions_preview_result_success():
"""
L3582 _render_context_batch_actions_preview_result returns Result.ok=True on success.
The helper wraps the _do_generate preview try/except in
render_context_batch_actions. On success, returns Result(data=preview_text)
where preview_text is the controller._do_generate() output.
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app.context_files = ["foo.py", "bar.py"]
app.controller._do_generate.return_value = ("# Generated Preview\n\nContent here", "preview.md")
result = gui_2._render_context_batch_actions_preview_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert "Generated Preview" in result.data
def test_phase_5_l3582_render_context_batch_actions_preview_result_failure():
"""
L3582 _render_context_batch_actions_preview_result returns Result.ok=False on failure.
When _do_generate raises, the helper captures the traceback and returns
Result(data="<error message>", errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app.context_files = ["foo.py"]
app.controller._do_generate.side_effect = RuntimeError("generate failed")
result = gui_2._render_context_batch_actions_preview_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_context_batch_actions_preview_result"
assert "generate failed" in err.message
assert "Error" in result.data
def test_phase_5_l5380_render_operations_hub_external_editor_panel_result_success():
"""
L5380 _render_operations_hub_external_editor_panel_result returns Result.ok=True on success.
The helper wraps the render_external_editor_panel call within render_operations_hub
External Tools tab try/except. On success, returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2, "render_external_editor_panel"):
result = gui_2._render_operations_hub_external_editor_panel_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_5_l5380_render_operations_hub_external_editor_panel_result_failure():
"""
L5380 _render_operations_hub_external_editor_panel_result returns Result.ok=False on failure.
When render_external_editor_panel raises, the helper converts the exception
to ErrorInfo and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2, "render_external_editor_panel", side_effect=RuntimeError("ext editor render blew up")):
result = gui_2._render_operations_hub_external_editor_panel_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_operations_hub_external_editor_panel_result"
assert "ext editor render blew up" in err.message
def test_phase_5_l5786_render_text_viewer_window_ced_result_success():
"""
L5786 _render_text_viewer_window_ced_result returns Result.ok=True on success.
The helper wraps the TextEditor set_text/render try/except in
render_text_viewer_window. On success, returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.perf_profiling_enabled = False
app.text_viewer_content = "line1\nline2\n"
app.text_viewer_title = "test.txt"
app._text_viewer_editor = MagicMock()
result = gui_2._render_text_viewer_window_ced_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_5_l5786_render_text_viewer_window_ced_result_failure():
"""
L5786 _render_text_viewer_window_ced_result returns Result.ok=False with ErrorInfo on failure.
When TextEditor render raises, the helper converts the exception to ErrorInfo
and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.perf_profiling_enabled = False
app.text_viewer_content = "line1\nline2\n"
app.text_viewer_title = "test.txt"
mock_editor = MagicMock()
mock_editor.set_text.side_effect = RuntimeError("ced set_text failed")
app._text_viewer_editor = mock_editor
result = gui_2._render_text_viewer_window_ced_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_text_viewer_window_ced_result"
assert "ced set_text failed" in err.message
def test_phase_5_l5920_render_external_editor_panel_config_result_success():
"""
L5920 _render_external_editor_panel_config_result returns Result.ok=True on success.
The helper wraps the external editor config rendering try/except in
render_external_editor_panel. On success, returns Result(data=True).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.config = {"tools": {"text_editors": {}, "default_editor": ""}}
mock_launcher = MagicMock()
mock_launcher.config.editors = {}
mock_launcher.config.default_editor = None
with patch("src.external_editor.get_default_launcher", return_value=mock_launcher), \
patch.object(gui_2, "imgui", new=MagicMock()):
result = gui_2._render_external_editor_panel_config_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_5_l5920_render_external_editor_panel_config_result_failure():
"""
L5920 _render_external_editor_panel_config_result returns Result.ok=False with ErrorInfo on failure.
When the external editor config rendering raises, the helper converts the
exception to ErrorInfo and returns Result(data=False, errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.config = {"tools": {"text_editors": {}, "default_editor": ""}}
with patch("src.external_editor.get_default_launcher", side_effect=RuntimeError("ext editor config blew up")):
result = gui_2._render_external_editor_panel_config_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_external_editor_panel_config_result"
assert "ext editor config blew up" in err.message
def test_phase_5_l7208_render_beads_tab_list_result_success():
"""
L7208 _render_beads_tab_list_result returns Result.ok=True on success.
The helper wraps the beads_client.BeadsClient(...) + list_beads() try/except in
render_beads_tab. On success, returns Result(data=beads) where beads is the
list of beads returned by list_beads().
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.active_project_root = "/proj/foo"
mock_client = MagicMock()
mock_bead = MagicMock()
mock_bead.id = "bd-1"
mock_bead.status = "todo"
mock_bead.title = "test"
mock_client.list_beads.return_value = [mock_bead]
with patch("src.beads_client.BeadsClient", return_value=mock_client):
result = gui_2._render_beads_tab_list_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == [mock_bead]
def test_phase_5_l7208_render_beads_tab_list_result_failure():
"""
L7208 _render_beads_tab_list_result returns Result.ok=False with ErrorInfo on failure.
When BeadsClient construction or list_beads() raises, the helper converts
the exception to ErrorInfo and returns Result(data=[], errors=[ErrorInfo]).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
app.active_project_root = "/proj/foo"
with patch("src.beads_client.BeadsClient", side_effect=RuntimeError("dolt backend down")):
result = gui_2._render_beads_tab_list_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._render_beads_tab_list_result"
assert "dolt backend down" in err.message
# =============================================================================
# Phase 5 Invariant Tests (result_migration_gui_2_20260619)
# Lock the per-phase progress: 11 INTERNAL_BROAD_CATCH event-handler sites
# migrated, all have both success and failure tests.
# =============================================================================
def test_phase_5_invariant_batch_c_count_dropped():
"""
Phase 5 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
has dropped from 14 (pre-Phase 5) to 3 (post-Phase 5). The 3 remaining sites
are in other phases: L591 (Phase 8), L897 (Phase 8), L4321 (Phase 7).
The 11 migrated sites are: L1284, L1293, L1367, L1393, L1428, L3163, L3582,
L5380, L5786, L5920, L7208.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Pre-Phase 5 baseline: 14. Post-Phase 5: 3 (11 sites migrated).
# Per FR-BC-4, the 11 migrated sites drain to app._last_request_errors.
assert len(broad_catches) <= 3, (
f"Phase 5 invariant: expected <= 3 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 5 baseline, 11 sites migrated); found {len(broad_catches)}. "
f"The 11 Phase 5 Batch C sites (L1284, L1293, L1367, L1393, L1428, L3163, "
f"L3582, L5380, L5786, L5920, L7208) must be migrated to Result[T] helpers. "
f"Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_5_invariant_all_11_migration_sites_have_tests():
"""
Phase 5 invariant: each of the 11 Batch C sites has both success and
failure tests in this test file.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
expected_lines = [1284, 1293, 1367, 1393, 1428, 3163, 3582, 5380, 5786, 5920, 7208]
for line in expected_lines:
success_pattern = f"test_phase_5_l{line}_.*_result_success"
failure_pattern = f"test_phase_5_l{line}_.*_result_failure"
assert re.search(success_pattern, text), (
f"Phase 5 invariant: missing success test for L{line}. "
f"Expected a test matching '{success_pattern}'."
)
assert re.search(failure_pattern, text), (
f"Phase 5 invariant: missing failure test for L{line}. "
f"Expected a test matching '{failure_pattern}'."
)
# =============================================================================
# Phase 6 Tests - Signal handler sites
# Per PHASE1_SITE_INVENTORY.md, Phase 6 covers signal-handler category
# sites. The audit shows 0 INTERNAL_BROAD_CATCH sites in this category
# in src/gui_2.py (the inventory classifies signal-handler try/except
# under other categories — Phase 6 has no sites in this track).
# The two invariant tests below document this and pin the count.
# =============================================================================
def test_phase_6_invariant_signal_handler_count_dropped():
"""
Phase 6 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
remains at 3 (no sites migrated in Phase 6, since the signal-handler
category has 0 INTERNAL_BROAD_CATCH sites in this track).
Per PHASE1_SITE_INVENTORY.md, all sites that might appear in a
signal-handler category were classified into other phases (Phase 8 for
startup callbacks, Phase 7 for worker/background). Phase 6 has no
sites to migrate in this track.
Pre-Phase 6 baseline: 3 (L591 _diag_layout_state, L897 _capture_workspace_profile,
L4321 worker). Post-Phase 6 baseline: 3 (unchanged; Phase 6 has 0 sites).
Uses <= to remain robust against later-phase migrations (Phases 7-8 will
drop the count to 0; this test continues to pass since 0 <= 3).
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Phase 6 baseline is 3 (no migration occurred since Phase 5 ended).
# This test pins the upper bound to 3 before Phases 7, 8, 9 each migrate sites.
assert len(broad_catches) <= 3, (
f"Phase 6 invariant: expected <= 3 INTERNAL_BROAD_CATCH sites in "
f"src/gui_2.py (Phase 6 has 0 sites to migrate; pre-Phase-7 baseline); "
f"found {len(broad_catches)}. Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_6_invariant_zero_sites_in_phase_6():
"""
Phase 6 invariant: documents that Phase 6 (signal-handler sites) has
0 sites to migrate. The next test (`test_phase_7_invariant_batch_d_count_dropped`)
will pin the count after Phase 7 migrates the L4321 worker site.
This test exists to make the "Phase 6 is empty" decision explicit and
machine-checkable: a future agent who tries to add a Phase 6 site
will see this test fail at the count assertion.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
# Expected: zero tests matching the Phase 6 site pattern
phase_6_site_tests = re.findall(r"test_phase_6_l\d+_.*_result_(success|failure)", text)
assert len(phase_6_site_tests) == 0, (
f"Phase 6 invariant: expected 0 Phase 6 site tests (signal-handler "
f"category has 0 INTERNAL_BROAD_CATCH sites in src/gui_2.py per the "
f"PHASE1_SITE_INVENTORY); found {len(phase_6_site_tests)}. Tests: "
f"{phase_6_site_tests}. If a Phase 6 site was added, update the "
f"inventory and migrate it."
)
# =============================================================================
# Phase 7 Tests - Migration of 1 INTERNAL_BROAD_CATCH worker site to Result[T]
# The helper wraps the try body from the worker() closure in
# _check_auto_refresh_context_preview (L4321).
# The legacy wrapper drains errors to app.controller._worker_errors (with lock).
# =============================================================================
def test_phase_7_l4321_worker_context_preview_result_success():
"""
L4321 _worker_context_preview_result returns Result(data=None) on success.
The helper wraps the try body from the worker() closure in
_check_auto_refresh_context_preview. On success, sets
app.context_preview_text to the generated markdown and returns
Result(data=None).
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app.controller._do_generate.return_value = ("# Generated Preview\n\nContent here", "preview.md")
result = gui_2._worker_context_preview_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
assert app.controller.context_files == app.context_files
assert app.context_preview_text == "# Generated Preview\n\nContent here"
def test_phase_7_l4321_worker_context_preview_result_failure():
"""
L4321 _worker_context_preview_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When the underlying controller._do_generate() call raises, the helper sets
app.context_preview_text to a fallback error message and returns Result
with ErrorInfo describing the failure.
"""
from src import gui_2
from unittest.mock import MagicMock
app = MagicMock()
app.controller._do_generate.side_effect = RuntimeError("do_generate blew up")
result = gui_2._worker_context_preview_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._worker_context_preview_result"
assert "do_generate blew up" in err.message
assert app.context_preview_text == "Error generating context preview."
# =============================================================================
# Phase 7 Invariant Tests (result_migration_gui_2_20260619)
# Lock the per-phase progress: 1 INTERNAL_BROAD_CATCH worker site migrated
# to Result[T], all 1 sites have both success and failure tests.
# =============================================================================
def test_phase_7_invariant_batch_d_count_dropped():
"""
Phase 7 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
has dropped from 3 (pre-Phase 7) to 2 (post-Phase 7). The 2 remaining sites
are in other phases: L591 (Phase 8), L897 (Phase 8).
The 1 migrated site is: L4321 (worker in _check_auto_refresh_context_preview).
The legacy wrapper drains errors to app.controller._worker_errors (with lock).
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Pre-Phase 7 baseline: 3. Post-Phase 7: 2 (1 site migrated).
assert len(broad_catches) <= 2, (
f"Phase 7 invariant: expected <= 2 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 7 baseline, 1 site migrated); found {len(broad_catches)}. "
f"The 1 Phase 7 site (L4321 worker) must be migrated to Result[T] helper. "
f"Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_7_invariant_all_1_migration_sites_have_tests():
"""
Phase 7 invariant: each of the 1 Batch D (worker/background) sites has
both success and failure tests in this test file.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
expected_lines = [4321]
for line in expected_lines:
success_pattern = f"test_phase_7_l{line}_.*_result_success"
failure_pattern = f"test_phase_7_l{line}_.*_result_failure"
assert re.search(success_pattern, text), (
f"Phase 7 invariant: missing success test for L{line}. "
f"Expected a test matching '{success_pattern}'."
)
assert re.search(failure_pattern, text), (
f"Phase 7 invariant: missing failure test for L{line}. "
f"Expected a test matching '{failure_pattern}'."
)
# =============================================================================
# Phase 8 Tests - Migration of 2 INTERNAL_BROAD_CATCH property setter sites
# to Result[T]. Each site gets 2 tests: success and failure.
# Migration pattern: legacy wrapper drains to app._startup_timeline_errors
# (for startup callbacks like L591) or app._last_request_errors
# (for property setters like L897).
# =============================================================================
def test_phase_8_l591_diag_layout_state_ini_text_result_success():
"""
L591 _diag_layout_state_ini_text_result returns Result(data=ini_text) on success.
The helper wraps the ini-file-read try/except in App._diag_layout_state.
On success, returns Result(data=ini_text) where ini_text is the file content.
The legacy wrapper drains errors to app._startup_timeline_errors.
"""
from src import gui_2
from unittest.mock import MagicMock, patch, mock_open
app = MagicMock()
with patch("builtins.open", mock_open(read_data="[Window][Provider]\nPos=10,20")):
result = gui_2._diag_layout_state_ini_text_result(app, "/proj/manualslop_layout.ini")
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert "[Window][Provider]" in result.data
def test_phase_8_l591_diag_layout_state_ini_text_result_failure():
"""
L591 _diag_layout_state_ini_text_result returns Result(data="", errors=[ErrorInfo]) on failure.
When the ini file read raises (e.g., permission error, encoding error),
the helper returns Result with ErrorInfo describing the failure and
data="" so the caller can still proceed (return early).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch("builtins.open", side_effect=PermissionError("permission denied")):
result = gui_2._diag_layout_state_ini_text_result(app, "/proj/manualslop_layout.ini")
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._diag_layout_state_ini_text_result"
assert "permission denied" in err.message
assert result.data == ""
def test_phase_8_l897_capture_workspace_profile_ini_result_success():
"""
L897 _capture_workspace_profile_ini_result returns Result(data=ini_str) on success.
The helper wraps the imgui.save_ini_settings_to_memory try/except in
App._capture_workspace_profile. On success, returns Result(data=ini_str)
where ini_str is the serialized INI content. The legacy wrapper drains
errors to app._last_request_errors.
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2.imgui, "save_ini_settings_to_memory", return_value="[Window][Provider]\nPos=10,20"):
result = gui_2._capture_workspace_profile_ini_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "[Window][Provider]\nPos=10,20"
def test_phase_8_l897_capture_workspace_profile_ini_result_failure():
"""
L897 _capture_workspace_profile_ini_result returns Result(data="", errors=[ErrorInfo]) on failure.
When imgui.save_ini_settings_to_memory raises, the helper returns Result
with ErrorInfo describing the failure and data="" (matching the original
except branch's empty-string fallback).
"""
from src import gui_2
from unittest.mock import MagicMock, patch
app = MagicMock()
with patch.object(gui_2.imgui, "save_ini_settings_to_memory", side_effect=RuntimeError("imgui backend down")):
result = gui_2._capture_workspace_profile_ini_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._capture_workspace_profile_ini_result"
assert "imgui backend down" in err.message
assert result.data == ""
# =============================================================================
# Phase 8 Invariant Tests (result_migration_gui_2_20260619)
# Lock the per-phase progress: 2 INTERNAL_BROAD_CATCH property setter sites
# migrated to Result[T], all 2 sites have both success and failure tests.
# =============================================================================
def test_phase_8_invariant_property_setter_count_dropped():
"""
Phase 8 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
has dropped from 2 (pre-Phase 8) to 0 (post-Phase 8). All INTERNAL_BROAD_CATCH
sites in src/gui_2.py have been migrated across Phases 3-8.
The 2 migrated sites in Phase 8 are: L591 _diag_layout_state, L897 _capture_workspace_profile.
- L591 (startup callback) drains to app._startup_timeline_errors.
- L897 (property setter) drains to app._last_request_errors.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Post-Phase 8 baseline: 0 (all 22 sites migrated across Phases 3-8).
assert len(broad_catches) == 0, (
f"Phase 8 invariant: expected 0 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 8 baseline, all sites migrated); found {len(broad_catches)}. "
f"Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_8_invariant_all_2_migration_sites_have_tests():
"""
Phase 8 invariant: each of the 2 Batch E (property setter / state) sites
has both success and failure tests in this test file.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
expected_lines = [591, 897]
for line in expected_lines:
success_pattern = f"test_phase_8_l{line}_.*_result_success"
failure_pattern = f"test_phase_8_l{line}_.*_result_failure"
assert re.search(success_pattern, text), (
f"Phase 8 invariant: missing success test for L{line}. "
f"Expected a test matching '{success_pattern}'."
)
assert re.search(failure_pattern, text), (
f"Phase 8 invariant: missing failure test for L{line}. "
f"Expected a test matching '{failure_pattern}'."
)
# =============================================================================
# Phase 9 Tests - Helper/utility sites
# Per PHASE1_SITE_INVENTORY.md, Phase 9 covers helper/utility module-level
# sites. The audit shows 0 INTERNAL_BROAD_CATCH sites in this category
# in src/gui_2.py (the one Phase 9 site from the inventory, L1398
# _close_vscode_diff, is classified INTERNAL_SILENT_SWALLOW and is
# handled in Phase 10 — logging is NOT a drain per the convention).
# The two invariant tests below document this and pin the count.
# =============================================================================
def test_phase_9_invariant_helper_utility_count_dropped():
"""
Phase 9 invariant: the audit's INTERNAL_BROAD_CATCH count for src/gui_2.py
remains at 0 (no sites migrated in Phase 9, since the helper/utility
category has 0 INTERNAL_BROAD_CATCH sites in this track).
Per PHASE1_SITE_INVENTORY.md, the one Phase 9 site (L1398 _close_vscode_diff)
is INTERNAL_SILENT_SWALLOW (the bare-except classification) and is handled
in Phase 10 (logging NOT a drain). Phase 9 has no sites to migrate.
Pre-Phase 9 baseline: 0. Post-Phase 9 baseline: 0 (unchanged; Phase 9
has 0 sites). This test pins the count to 0 after Phases 7-8 migrated
all 3 remaining sites.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
broad_catches = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"]
# Phase 9 baseline is 0 (Phase 8 already dropped the count to 0).
# This test pins the count to 0 to verify no regression.
assert len(broad_catches) == 0, (
f"Phase 9 invariant: expected 0 INTERNAL_BROAD_CATCH sites in src/gui_2.py "
f"(post-Phase 9 baseline; no Phase 9 sites, count should remain 0); "
f"found {len(broad_catches)}. Lines: {[f.get('line') for f in broad_catches]}"
)
def test_phase_9_invariant_zero_sites_in_phase_9():
"""
Phase 9 invariant: documents that Phase 9 (helper/utility sites) has
0 sites to migrate. The one Phase 9 site from the inventory
(L1398 _close_vscode_diff) is INTERNAL_SILENT_SWALLOW and will be
handled in Phase 10.
This test exists to make the "Phase 9 is empty" decision explicit and
machine-checkable: a future agent who tries to add a Phase 9 site
will see this test fail at the count assertion.
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
# Expected: zero tests matching the Phase 9 site pattern
phase_9_site_tests = re.findall(r"test_phase_9_l\d+_.*_result_(success|failure)", text)
assert len(phase_9_site_tests) == 0, (
f"Phase 9 invariant: expected 0 Phase 9 site tests (helper/utility "
f"category has 0 INTERNAL_BROAD_CATCH sites in src/gui_2.py per the "
f"PHASE1_SITE_INVENTORY); found {len(phase_9_site_tests)}. Tests: "
f"{phase_9_site_tests}. If a Phase 9 site was added, update the "
f"inventory and migrate it."
)
# =============================================================================
# Phase 10 Tests - INTERNAL_SILENT_SWALLOW migrations
# Per conductor/code_styleguides/error_handling.md lines 462-540:
# "Logging is NOT a drain point." The 13 sites in this phase have logging-only
# except bodies (sys.stderr.write, print, traceback.print_exc, pass). They
# MUST be migrated to full Result[T] propagation. NOT narrowing + logging.
# NOT pass-after-logging. NOT "intentional silent recovery".
# =============================================================================
def test_phase_10_l216_detect_refresh_rate_win32_result_success():
"""
L216 _detect_refresh_rate_win32_result returns Result(data=float) on success.
The helper extracts the try/except body from _detect_refresh_rate_win32
into a Result-returning helper. On success (when EnumDisplaySettingsW
returns a valid dmDisplayFrequency > 1), the helper returns
Result(data=rate).
"""
from unittest.mock import patch
import src.gui_2 as gui2_mod
def fake_eds(_devname, _mode, byref_dm):
real_dm = byref_dm._obj
real_dm.dmDisplayFrequency = 144
return 1
with patch("ctypes.windll.user32.EnumDisplaySettingsW", side_effect=fake_eds):
result = gui2_mod._detect_refresh_rate_win32_result()
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == 144.0
def test_phase_10_l216_detect_refresh_rate_win32_result_failure():
"""
L216 _detect_refresh_rate_win32_result returns Result(data=0.0, errors=[ErrorInfo]) on failure.
When the ctypes windll call raises (e.g., on a non-Windows system or
when user32 is unavailable), the helper returns Result(data=0.0)
with ErrorInfo describing the failure. The original function returned
0.0 on error (preserved as the safe fallback in the legacy wrapper).
"""
from unittest.mock import patch
import src.gui_2 as gui2_mod
with patch("ctypes.windll.user32.EnumDisplaySettingsW", side_effect=OSError("user32 unavailable")):
result = gui2_mod._detect_refresh_rate_win32_result()
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data == 0.0
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._detect_refresh_rate_win32_result"
assert "user32 unavailable" in err.message
def test_phase_10_l264_resolve_font_path_result_relative_under_assets():
"""
L264 _resolve_font_path_result returns Result(data=relative_path) when input is absolute but inside assets_dir.
The helper extracts the entire _resolve_font_path normalization logic into
a Result-returning helper. On a path under assets_dir, it returns
Result(data=relative_path) with backslashes converted to forward slashes.
"""
from pathlib import Path
import src.gui_2 as gui2_mod
assets_dir = Path(r"C:\projects\manual_slop_tier2\assets")
font_path = str(assets_dir / "fonts" / "Inter-Regular.ttf")
result = gui2_mod._resolve_font_path_result(font_path, assets_dir)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == "fonts/Inter-Regular.ttf"
def test_phase_10_l264_resolve_font_path_result_is_relative_to_raises():
"""
L264 _resolve_font_path_result returns Result(data=fallback, errors=[ErrorInfo])
when Path.is_relative_to() raises ValueError on Windows path comparison.
On Python <3.9, AttributeError is raised because is_relative_to doesn't exist.
On Python >=3.9 with cross-drive paths, ValueError is raised. Either way the
helper should NOT silently swallow — it converts to ErrorInfo and returns
the default fallback path "fonts/Inter-Regular.ttf" so the legacy wrapper
can return a valid path.
"""
from pathlib import Path
import src.gui_2 as gui2_mod
assets_dir = Path(r"C:\projects\manual_slop_tier2\assets")
font_path = r"D:\different\drive\fonts\Inter-Regular.ttf"
result = gui2_mod._resolve_font_path_result(font_path, assets_dir)
assert result.ok, f"Expected ok=True (graceful degradation), got errors: {result.errors}"
assert result.data == "fonts/Inter-Regular.ttf"
if result.errors:
err = result.errors[0]
assert err.source == "gui_2._resolve_font_path_result"
def test_phase_10_l612_post_init_callback_result_success():
"""
L612 _post_init_callback_result returns Result(data=None) on success.
The helper extracts the warmup-complete callback registration from
App._post_init into a Result-returning helper. On success, it registers
the lambda callback via self.controller.on_warmup_complete() and returns
Result(data=None).
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
result = gui2_mod._post_init_callback_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
app.controller.on_warmup_complete.assert_called_once()
def test_phase_10_l612_post_init_callback_result_failure():
"""
L612 _post_init_callback_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When self.controller.on_warmup_complete() raises (e.g., controller not
ready or invalid callback), the helper converts to ErrorInfo and returns
Result(data=None, errors=[ErrorInfo]). The legacy _post_init wrapper
drains to self._startup_timeline_errors.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
app.controller.on_warmup_complete.side_effect = RuntimeError("controller not ready")
result = gui2_mod._post_init_callback_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._post_init_callback_result"
assert "controller not ready" in err.message
def test_phase_10_l728_run_immapp_result_success():
"""
L728 _run_immapp_result returns Result(data=None) on success.
The helper extracts the immapp.run() call from App.run into a Result-returning
helper. On success, returns Result(data=None). The legacy run method
proceeds to self.shutdown() and session_logger.close_session().
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.runner_params = MagicMock()
with patch("src.gui_2.immapp") as mock_immapp:
mock_immapp.AddOnsParams.return_value = MagicMock()
result = gui2_mod._run_immapp_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
mock_immapp.run.assert_called_once()
def test_phase_10_l728_run_immapp_result_failure():
"""
L728 _run_immapp_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When immapp.run() raises RuntimeError (IM_ASSERT, native bundle crash, etc.),
the helper converts to ErrorInfo. The legacy run method sets the
controller._gui_degraded_reason and _last_imgui_assert drain attributes,
appends to _startup_timeline_errors, and returns.
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.runner_params = MagicMock()
app.controller = MagicMock()
with patch("src.gui_2.immapp") as mock_immapp:
mock_immapp.AddOnsParams.return_value = MagicMock()
mock_immapp.run.side_effect = RuntimeError("IM_ASSERT: invalid scope")
result = gui2_mod._run_immapp_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._run_immapp_result"
assert "IM_ASSERT" in err.message
def test_phase_10_l1052_shutdown_save_ini_result_success():
"""
L1052 _shutdown_save_ini_result returns Result(data=None) on success.
The helper extracts the imgui.save_ini_settings_to_disk() try/except from
App.shutdown into a Result-returning helper. On success, returns
Result(data=None). The legacy shutdown method proceeds to
self.controller.shutdown().
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.runner_params = MagicMock()
app.runner_params.ini_filename = "manualslop_layout.ini"
with patch("src.gui_2.imgui.save_ini_settings_to_disk", return_value=None):
result = gui2_mod._shutdown_save_ini_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
def test_phase_10_l1052_shutdown_save_ini_result_failure():
"""
L1052 _shutdown_save_ini_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When imgui.save_ini_settings_to_disk() raises (e.g., disk full, path
not writable), the helper converts to ErrorInfo. The legacy shutdown
method drains to self._startup_timeline_errors and proceeds to
self.controller.shutdown() (the original behavior preserved).
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.runner_params = MagicMock()
app.runner_params.ini_filename = "manualslop_layout.ini"
with patch("src.gui_2.imgui.save_ini_settings_to_disk", side_effect=OSError("disk full")):
result = gui2_mod._shutdown_save_ini_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._shutdown_save_ini_result"
assert "disk full" in err.message
def test_phase_10_l1152_gui_func_entry_log_result_success():
"""
L1152 _gui_func_entry_log_result returns Result(data=None) on success.
The helper extracts the startup-timing sys.stderr.write from
App._gui_func into a Result-returning helper. On success, returns
Result(data=None). The legacy _gui_func method proceeds to the rest
of the render loop.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
app.controller._init_start_ts = 1000.0
result = gui2_mod._gui_func_entry_log_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
def test_phase_10_l1152_gui_func_entry_log_result_failure():
"""
L1152 _gui_func_entry_log_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When the sys.stderr.write or sys.stderr.flush raises (e.g., broken pipe),
the helper converts to ErrorInfo. The legacy _gui_func method drains to
self._last_request_errors and proceeds with the rest of the render loop
(preserving the original behavior of continuing past the failure).
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
app.controller._init_start_ts = 1000.0
with patch("src.gui_2.sys.stderr.write", side_effect=OSError("broken pipe")):
result = gui2_mod._gui_func_entry_log_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._gui_func_entry_log_result"
assert "broken pipe" in err.message
def test_phase_10_l1466_close_vscode_diff_terminate_result_success():
"""
L1466 _close_vscode_diff_terminate_result returns Result(data=None) on success.
The helper extracts the self._vscode_diff_process.terminate() try/except
from App._close_vscode_diff into a Result-returning helper. On success,
returns Result(data=None). The legacy wrapper sets
self._vscode_diff_process = None (the original behavior preserved).
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app._vscode_diff_process = MagicMock()
result = gui2_mod._close_vscode_diff_terminate_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
app._vscode_diff_process.terminate.assert_called_once()
def test_phase_10_l1466_close_vscode_diff_terminate_result_failure():
"""
L1466 _close_vscode_diff_terminate_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When the process.terminate() call raises (e.g., process already exited,
invalid handle), the helper converts to ErrorInfo. The legacy wrapper
drains to self._last_request_errors and proceeds to set
self._vscode_diff_process = None (preserving the original behavior).
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app._vscode_diff_process = MagicMock()
app._vscode_diff_process.terminate.side_effect = OSError("process already exited")
result = gui2_mod._close_vscode_diff_terminate_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._close_vscode_diff_terminate_result"
assert "process already exited" in err.message
def test_phase_10_l1647_focus_response_window_result_success():
"""
L1647 _focus_response_window_result returns Result(data=None) on success.
The helper extracts the imgui.set_window_focus("Response") try/except from
render_main_interface into a Result-returning helper. On success, returns
Result(data=None).
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
with patch("src.gui_2.imgui.set_window_focus", return_value=None):
result = gui2_mod._focus_response_window_result()
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
def test_phase_10_l1647_focus_response_window_result_failure():
"""
L1647 _focus_response_window_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When imgui.set_window_focus("Response") raises (e.g., native bundle
error, IM_ASSERT), the helper converts to ErrorInfo. The caller
(render_main_interface) drains to self._last_request_errors.
"""
from unittest.mock import patch
import src.gui_2 as gui2_mod
with patch("src.gui_2.imgui.set_window_focus", side_effect=RuntimeError("IM_ASSERT: window scope")):
result = gui2_mod._focus_response_window_result()
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._focus_response_window_result"
assert "IM_ASSERT" in err.message
def test_phase_10_l1693_autosave_flush_result_success():
"""
L1693 _autosave_flush_result returns Result(data=None) on success.
The helper extracts the auto-save flush_to_project + flush_to_config +
save_config try/except from render_main_interface into a Result-returning
helper. On success, returns Result(data=None). The legacy wrapper
continues with the GUI loop.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
result = gui2_mod._autosave_flush_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
app._flush_to_project.assert_called_once()
app._flush_to_config.assert_called_once()
app.save_config.assert_called_once()
def test_phase_10_l1693_autosave_flush_result_failure():
"""
L1693 _autosave_flush_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When any of _flush_to_project/_flush_to_config/save_config raises
(disk full, JSON parse error), the helper converts to ErrorInfo. The
caller (render_main_interface) drains to self._last_request_errors and
continues with the GUI loop (preserving the original "don't disrupt the
GUI loop" intent via the data plane rather than silent swallow).
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app._flush_to_project.side_effect = OSError("disk full")
result = gui2_mod._autosave_flush_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._autosave_flush_result"
assert "disk full" in err.message
def test_phase_10_l4911_on_warmup_complete_callback_result_success():
"""
L4911 _on_warmup_complete_callback_result returns Result(data=None) on success.
The helper extracts the warmup-completion try/except from
_on_warmup_complete_callback into a Result-returning helper. On success,
returns Result(data=None). The legacy callback (which runs on a
background _io_pool thread) sets app._warmup_completion_ts and appends
to app._warmup_toast_messages under the lock.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
status = {"pending": [], "completed": ["ai_client", "mcp_client"], "failed": []}
result = gui2_mod._on_warmup_complete_callback_result(app, status)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
assert app._warmup_toast_messages, "Expected warmup toast message to be appended"
def test_phase_10_l4911_on_warmup_complete_callback_result_failure():
"""
L4911 _on_warmup_complete_callback_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When the warmup-completion body raises (e.g., status dict corruption,
lock acquisition failure), the helper converts to ErrorInfo. The legacy
wrapper drains to app._worker_errors with the controller lock acquired
on append (thread-safety critical per sub-track 4 spec).
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
app.controller = MagicMock()
app.controller._worker_errors_lock = MagicMock()
# Force status.get to raise to trigger the except
bad_status = MagicMock()
bad_status.get.side_effect = RuntimeError("status dict corrupted")
result = gui2_mod._on_warmup_complete_callback_result(app, bad_status)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._on_warmup_complete_callback_result"
assert "status dict corrupted" in err.message
def test_phase_10_l6908_tier_stream_scroll_sync_result_success():
"""
L6908 _tier_stream_scroll_sync_result returns Result(data=None) on success.
The helper extracts the imgui.set_scroll_here_y + _tier_stream_last_len
update try/except from render_tier_stream_panel into a Result-returning
helper. On success, returns Result(data=None). The caller continues
the render loop normally.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
len_map = MagicMock()
len_map.get.return_value = 0
app._tier_stream_last_len = len_map
mock_imgui = MagicMock()
result = gui2_mod._tier_stream_scroll_sync_result(app, "stream_key", "content here", mock_imgui)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is None
def test_phase_10_l6908_tier_stream_scroll_sync_result_failure():
"""
L6908 _tier_stream_scroll_sync_result returns Result(data=None, errors=[ErrorInfo]) on failure.
When the comparison or imgui.set_scroll_here_y raises (TypeError on
bad content, AttributeError on missing key), the helper converts to
ErrorInfo. The caller drains to app._last_request_errors.
"""
from unittest.mock import MagicMock
import src.gui_2 as gui2_mod
app = MagicMock()
bad_app = MagicMock()
bad_app._tier_stream_last_len.get.side_effect = AttributeError("missing key")
mock_imgui = MagicMock()
result = gui2_mod._tier_stream_scroll_sync_result(bad_app, "stream_key", "content", mock_imgui)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is None
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._tier_stream_scroll_sync_result"
assert "missing key" in err.message
def test_phase_10_l7271_dag_cycle_check_result_no_cycle():
"""
L7271 _dag_cycle_check_result returns Result(data=False) when no cycle is found.
The helper extracts the TrackDAG() cycle check try/except from
render_task_dag_panel into a Result-returning helper. On a valid DAG
(no cycle), returns Result(data=False). The caller continues without
opening the "Cycle Detected!" popup.
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.active_tickets = [{"id": "T-001", "depends_on": []}]
mock_dag = MagicMock()
mock_dag.has_cycle.return_value = False
with patch("src.dag_engine.TrackDAG", return_value=mock_dag):
result = gui2_mod._dag_cycle_check_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is False
def test_phase_10_l7271_dag_cycle_check_result_cycle_detected():
"""
L7271 _dag_cycle_check_result returns Result(data=True) when a cycle IS found.
The helper detects cycles via TrackDAG.has_cycle(). On a cyclic DAG,
returns Result(data=True). The caller opens the "Cycle Detected!" popup.
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.active_tickets = [
{"id": "T-001", "depends_on": ["T-002"]},
{"id": "T-002", "depends_on": ["T-001"]},
]
mock_dag = MagicMock()
mock_dag.has_cycle.return_value = True
with patch("src.dag_engine.TrackDAG", return_value=mock_dag):
result = gui2_mod._dag_cycle_check_result(app)
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data is True
def test_phase_10_l7271_dag_cycle_check_result_failure():
"""
L7271 _dag_cycle_check_result returns Result(data=False, errors=[ErrorInfo]) on failure.
When TrackDAG() construction or has_cycle() raises (bad ticket dict,
dag engine error), the helper converts to ErrorInfo. The caller
drains to app._last_request_errors.
"""
from unittest.mock import MagicMock, patch
import src.gui_2 as gui2_mod
app = MagicMock()
app.active_tickets = []
with patch("src.dag_engine.TrackDAG", side_effect=RuntimeError("dag engine failure")):
result = gui2_mod._dag_cycle_check_result(app)
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data is False
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._dag_cycle_check_result"
assert "dag engine failure" in err.message
def test_phase_10_l7315_ticket_id_max_int_result_success():
"""
L7315 _ticket_id_max_int_result returns Result(data=int) on success.
The helper extracts the int(tid[2:]) parsing from the ticket-ID loop
in render_task_dag_panel into a Result-returning helper. On success
(valid T-XXX id), returns Result(data=int). The legacy loop continues
with the maximum value.
"""
import src.gui_2 as gui2_mod
result = gui2_mod._ticket_id_max_int_result("T-042")
assert result.ok, f"Expected ok=True on success, got errors: {result.errors}"
assert result.data == 42
def test_phase_10_l7315_ticket_id_max_int_result_failure():
"""
L7315 _ticket_id_max_int_result returns Result(data=0, errors=[ErrorInfo]) on failure.
When tid[2:] is not parseable as int (e.g., "T-abc", "T-"), the helper
converts to ErrorInfo. The legacy loop skips this ticket and continues
with the current max. The caller drains to app._last_request_errors.
"""
import src.gui_2 as gui2_mod
result = gui2_mod._ticket_id_max_int_result("T-abc")
assert not result.ok, f"Expected ok=False on failure, got data: {result.data}"
assert result.data == 0
assert result.errors, "Expected at least one error on failure"
err = result.errors[0]
assert err.source == "gui_2._ticket_id_max_int_result"
assert "invalid literal" in err.message or "T-abc" in err.message
# =============================================================================
# Phase 10 Invariant Tests (result_migration_gui_2_20260619)
# Lock the per-phase progress: 13 INTERNAL_SILENT_SWALLOW sites migrated to
# Result[T] with full propagation (NO narrowing+logging, NO pass-after-log).
# logging NOT a drain per the user's principle 2026-06-17.
# =============================================================================
def test_phase_10_invariant_silent_swallow_count_zero():
"""
Phase 10 invariant: the audit's INTERNAL_SILENT_SWALLOW count for src/gui_2.py
is now 0. All 13 sites in the inventory have been migrated to Result[T]
propagation. The new helpers are classified as INTERNAL_COMPLIANT (or
BOUNDARY_CONVERSION for the dispatcher wrappers), not as SILENT_SWALLOW.
Per the user's principle 2026-06-17 (logging NOT a drain), the
migration replaced each site with a full Result[T] pattern:
- Helper: returns Result(data=X, errors=[ErrorInfo]) on exception
- Wrapper: calls helper, drains errors to the appropriate data plane
(NOT silent swallow)
- Tests: 2 tests per site verify success + failure paths
Pre-Phase 10 baseline: 13. Post-Phase 10 baseline: 0. This test pins
the count to 0.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
silent_swallows = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_SILENT_SWALLOW"]
assert len(silent_swallows) == 0, (
f"Phase 10 invariant: expected 0 INTERNAL_SILENT_SWALLOW sites in src/gui_2.py "
f"(post-Phase 10 baseline; all 13 sites migrated to Result[T]); "
f"found {len(silent_swallows)}. Lines: {[f.get('line') for f in silent_swallows]}"
)
def test_phase_10_invariant_all_13_sites_have_tests():
"""
Phase 10 invariant: all 13 INTERNAL_SILENT_SWALLOW migration sites have
success and failure tests in this test file. Verifies the test coverage
for Phase 10 is complete.
The 13 sites (from PHASE1_SITE_INVENTORY.md):
- L216 _detect_refresh_rate_win32
- L241 _resolve_font_path
- L567 _post_init
- L683 run
- L974 shutdown
- L1074 _gui_func
- L1348 _close_vscode_diff
- L1504 render_main_interface (focus_response)
- L1530 render_main_interface (autosave)
- L4742 _on_warmup_complete_callback
- L6694 render_tier_stream_panel
- L7029 render_task_dag_panel (cycle_check)
- L7045 render_task_dag_panel (ticket_id_parse)
"""
import re
text = Path(__file__).read_text(encoding="utf-8")
sites = [
("L216", "detect_refresh_rate_win32"),
("L264", "resolve_font_path"),
("L612", "post_init_callback"),
("L728", "run_immapp"),
("L1052", "shutdown_save_ini"),
("L1152", "gui_func_entry_log"),
("L1466", "close_vscode_diff_terminate"),
("L1647", "focus_response_window"),
("L1693", "autosave_flush"),
("L4911", "on_warmup_complete_callback"),
("L6908", "tier_stream_scroll_sync"),
("L7271", "dag_cycle_check"),
("L7315", "ticket_id_max_int"),
]
for line, site in sites:
# Allow either _result_success/_result_failure OR descriptive suffixes
pattern = rf"def test_phase_10_{line.lower()}_{site}_result_(\w+)\("
matches = re.findall(pattern, text)
assert len(matches) >= 2, (
f"Phase 10 invariant: missing tests for {line} {site}. "
f"Found {len(matches)} tests matching {pattern}. "
f"Need at least 2 (one success-like, one failure-like)."
)
# =============================================================================
# Phase 11 Invariant Tests (result_migration_gui_2_20260619)
# Lock the INTERNAL_RETHROW count: 2 sites reclassified as INTERNAL_PROGRAMMER_RAISE
# via the dunder-method bare-raise heuristic in audit_exception_handling.py.
# The 2 sites are bare `raise AttributeError(name)` in the App class's
# __getattr__ dunder method (L778 and L781 in src/gui_2.py at the time
# of Phase 11 closure; line numbers may shift slightly with future edits
# but the audit's category-based invariant is stable).
# =============================================================================
def test_phase_11_invariant_rethrow_count_zero():
"""
Phase 11 invariant: the audit's INTERNAL_RETHROW count for src/gui_2.py
is now 0. The 2 sites in the App class's __getattr__ method have been
reclassified as INTERNAL_PROGRAMMER_RAISE via the new dunder-method
bare-raise heuristic in scripts/audit_exception_handling.py:_classify_raise.
Per the styleguide (error_handling.md lines 625-690, Re-Raise Patterns),
bare raises of AttributeError/NameError in __getattr__/__getattribute__/
__setattr__/__delattr__ are the canonical dunder-method programmer-error
pattern, NOT suspicious rethrows. The new heuristic recognizes this and
reclassifies the sites as INTERNAL_PROGRAMMER_RAISE.
Pre-Phase 11 baseline: 2. Post-Phase 11 baseline: 0.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
rethrows = [f for f in gui2.get("findings", []) if f.get("category") == "INTERNAL_RETHROW"]
assert len(rethrows) == 0, (
f"Phase 11 invariant: expected 0 INTERNAL_RETHROW sites in src/gui_2.py "
f"(post-Phase 11 baseline; the 2 dunder-method sites reclassified as "
f"INTERNAL_PROGRAMMER_RAISE); found {len(rethrows)}. "
f"Lines: {[f.get('line') for f in rethrows]}"
)
def test_phase_11_invariant_l757_l760_reclassified():
"""
Phase 11 invariant: the 2 dunder-method raise sites at L778 and L781
(formerly documented as L757/L760; line numbers shifted slightly with
subsequent edits) in src/gui_2.py are no longer in INTERNAL_RETHROW.
The heuristic reclassifies any `raise AttributeError` / `raise NameError`
in a dunder method (__getattr__/__getattribute__/__setattr__/__delattr__)
as INTERNAL_PROGRAMMER_RAISE. The audit must NOT report these sites as
INTERNAL_RETHROW after Phase 11.
Note: the line numbers may shift with future edits to src/gui_2.py.
This test scans ALL gui_2.py findings and asserts that no finding in
the __getattr__ / __setattr__ method context is INTERNAL_RETHROW.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
rethrows = [
f for f in gui2.get("findings", [])
if f.get("category") == "INTERNAL_RETHROW"
and f.get("context") in ("__getattr__", "__getattribute__", "__setattr__", "__delattr__")
]
assert len(rethrows) == 0, (
f"Phase 11 invariant: expected 0 INTERNAL_RETHROW findings in "
f"gui_2.py dunder-method contexts (__getattr__/__getattribute__/"
f"__setattr__/__delattr__); found {len(rethrows)}. "
f"Sites: {[(f.get('line'), f.get('context'), f.get('snippet')) for f in rethrows]}"
)
# =============================================================================
# Phase 12 Invariant Tests (result_migration_gui_2_20260619)
# Lock the UNCLEAR count: 2 sites in _LazyModule._resolve (L65, L69) reclassified
# as INTERNAL_COMPLIANT via the lazy-loading sentinel fallback heuristic in
# scripts/audit_exception_handling.py:_try_compliant_pattern (heuristic B).
# The 2 sites are the lazy-loading sentinel fallback pattern in src/gui_2.py
# where a proxy class defers a heavy import (numpy, tkinter, etc.) and falls
# back to a documented sentinel class instance (_FiledialogStub) with an
# `available: bool = False` flag when the import or attribute access fails.
# Per the styleguide (error_handling.md:625-690, Re-Raise Patterns) and the
# lazy-loading pattern guidance, this is the canonical graceful-degradation
# pattern — NOT silent sliming.
# =============================================================================
def test_phase_12_invariant_unclear_count_zero():
"""
Phase 12 invariant: the audit's UNCLEAR count for src/gui_2.py is now 0.
The 2 sites in _LazyModule._resolve (L65, L69) have been reclassified as
INTERNAL_COMPLIANT via the new lazy-loading sentinel fallback heuristic
in scripts/audit_exception_handling.py:_try_compliant_pattern (heuristic B).
The heuristic recognizes the canonical pattern:
- Enclosing function is in LAZY_LOADER_METHOD_NAMES
({_resolve, _load, _get, _try_load}) — the standard naming for
proxy classes that defer a heavy import until first use.
- The except body does NOT re-raise.
- The except set is in {AttributeError, ImportError, ModuleNotFoundError}.
- The except body assigns to a self.<attr> (directly or via nested try).
Pre-Phase 12 baseline: 2. Post-Phase 12 baseline: 0.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
unclear = [f for f in gui2.get("findings", []) if f.get("category") == "UNCLEAR"]
assert len(unclear) == 0, (
f"Phase 12 invariant: expected 0 UNCLEAR sites in src/gui_2.py "
f"(post-Phase 12 baseline; the 2 lazy-loading sentinel fallback sites "
f"in _LazyModule._resolve reclassified as INTERNAL_COMPLIANT); "
f"found {len(unclear)}. "
f"Lines: {[f.get('line') for f in unclear]}"
)
def test_phase_12_invariant_l65_l69_reclassified():
"""
Phase 12 invariant: the 2 lazy-loading sentinel fallback sites at L65 and
L69 in src/gui_2.py (the _LazyModule._resolve method) are no longer UNCLEAR.
The heuristic reclassifies except sites in methods named
_resolve/_load/_get/_try_load that fall back to a documented sentinel
class instance as INTERNAL_COMPLIANT. The audit must NOT report the
_LazyModule._resolve sites (L65, L69) as UNCLEAR after Phase 12.
Note: the line numbers may shift with future edits to src/gui_2.py.
This test scans ALL gui_2.py findings and asserts that no finding in
the _LazyModule._resolve method context is UNCLEAR.
"""
result = subprocess.run(
["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"audit_exception_handling.py exited {result.returncode}; stderr:\n"
f"{result.stderr[:2000]}"
)
data = json.loads(result.stdout)
gui2 = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0]
unclear_in_resolve = [
f for f in gui2.get("findings", [])
if f.get("category") == "UNCLEAR"
and f.get("context") == "_resolve"
]
assert len(unclear_in_resolve) == 0, (
f"Phase 12 invariant: expected 0 UNCLEAR findings in "
f"_LazyModule._resolve method context; found {len(unclear_in_resolve)}. "
f"Sites: {[(f.get('line'), f.get('context'), f.get('snippet')) for f in unclear_in_resolve]}"
)