From 28a55ea51c7f6d3b1e635ed50244ffaadd0fd341 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 20 Jun 2026 02:24:18 -0400 Subject: [PATCH] test(audit_heuristics): add 3 regression tests for lazy-loading (Phase 12) Three regression-guard tests in tests/test_audit_heuristics.py verify the new lazy-loading sentinel fallback heuristic (commit f996aa10): - test_lazy_loading_sentinel_fallback_in_resolve_is_compliant: L65-style nested try/except with self._cached = _FiledialogStub() in _resolve (mirrors the actual site in src/gui_2.py:65) -> expects INTERNAL_COMPLIANT - test_lazy_loading_sentinel_fallback_in_load_is_compliant: direct self._cached = _FooStub() in _load -> expects INTERNAL_COMPLIANT - test_lazy_loading_sentinel_fallback_in_get_is_compliant: direct self._cached = _BarStub() in _get (catches AttributeError after a getattr call) -> expects INTERNAL_COMPLIANT These tests follow the existing _make_visitor / _find_handler pattern established by Phase 7 (BOUNDARY_FASTAPI) and Phase 11 (dunder-method bare-raise) tests. They lock the heuristic's behavior so future edits to scripts/audit_exception_handling.py cannot accidentally reclassify the 2 gui_2.py sites (L65, L69) back to UNCLEAR. Pre-Phase 12: 3 tests in this file (Phase 7 + Phase 11). Post-Phase 12: 6 tests. 13/13 tests pass (3 new + 10 existing). Phase 12 result_migration_gui_2_20260619. --- tests/test_audit_heuristics.py | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_audit_heuristics.py b/tests/test_audit_heuristics.py index d22913f2..f79ad8bc 100644 --- a/tests/test_audit_heuristics.py +++ b/tests/test_audit_heuristics.py @@ -221,3 +221,78 @@ def test_bare_raise_in_getattribute_is_programmer_raise(): f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " f"got {category}. Hint: {hint}" ) + + +# Phase 12 Task 12.1 - Regression-guard tests for the lazy-loading sentinel +# fallback heuristic. +# Per Phase 12 spec (INTERNAL_COMPLIANT classification for lazy-loading +# sentinel fallbacks in methods named _resolve/_load/_get/_try_load): +# - The except body must NOT re-raise +# - The except body must assign to a self. (directly or via nested try) +# - The except set must be in {AttributeError, ImportError, ModuleNotFoundError} +# - The enclosing function name must be in the lazy-loader set +# Pre-Phase 12 baseline: 2 UNCLEAR sites in src/gui_2.py at L65, L69 +# (both in _LazyModule._resolve). Post-Phase 12: 0 UNCLEAR. + +def test_lazy_loading_sentinel_fallback_in_resolve_is_compliant(): + src = ( + "def _resolve(self):\n" + " try:\n" + " self._cached = getattr(self._mod, self._attr_name)\n" + " except AttributeError:\n" + " try:\n" + " self._cached = _importlib.import_module(self._sub_name)\n" + " except (ImportError, ModuleNotFoundError):\n" + " self._cached = _FiledialogStub()\n" + " return self._cached\n" + ) + visitor = _make_visitor(src, "_resolve") + try_node = _find_handler(visitor) + handler = try_node.handlers[0] + category, hint = visitor._classify_except(handler, try_node) + assert category == "INTERNAL_COMPLIANT", ( + f"Phase 12 regression: lazy-loading sentinel fallback in `_resolve` " + f"(L65-style nested try with `self._cached = _FiledialogStub()`) " + f"should be INTERNAL_COMPLIANT (canonical graceful-degradation pattern); " + f"got {category}. Hint: {hint}" + ) + + +def test_lazy_loading_sentinel_fallback_in_load_is_compliant(): + src = ( + "def _load(self, name):\n" + " try:\n" + " self._cached = _importlib.import_module(name)\n" + " except (ImportError, ModuleNotFoundError):\n" + " self._cached = _FooStub()\n" + " return self._cached\n" + ) + visitor = _make_visitor(src, "_load") + try_node = _find_handler(visitor) + handler = try_node.handlers[0] + category, hint = visitor._classify_except(handler, try_node) + assert category == "INTERNAL_COMPLIANT", ( + f"Phase 12 regression: lazy-loading sentinel fallback in `_load` " + f"(direct `self._cached = _FooStub()`) should be INTERNAL_COMPLIANT " + f"(canonical graceful-degradation pattern); got {category}. Hint: {hint}" + ) + + +def test_lazy_loading_sentinel_fallback_in_get_is_compliant(): + src = ( + "def _get(self, attr_name):\n" + " try:\n" + " return getattr(self._module, attr_name)\n" + " except AttributeError:\n" + " self._cached = _BarStub()\n" + " return self._cached\n" + ) + visitor = _make_visitor(src, "_get") + try_node = _find_handler(visitor) + handler = try_node.handlers[0] + category, hint = visitor._classify_except(handler, try_node) + assert category == "INTERNAL_COMPLIANT", ( + f"Phase 12 regression: lazy-loading sentinel fallback in `_get` " + f"(direct `self._cached = _BarStub()`) should be INTERNAL_COMPLIANT " + f"(canonical graceful-degradation pattern); got {category}. Hint: {hint}" + )