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}" + )