From a5a06f851691d4a05650d0ebafe015f4d1e6b874 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 20 Jun 2026 01:57:33 -0400 Subject: [PATCH] test(audit_heuristics): add 5 regression tests for dunder raise (Phase 11) Five regression-guard tests verify the new dunder-method bare-raise heuristic in scripts/audit_exception_handling.py:_classify_raise: - test_bare_raise_attribute_error_in_getattr_is_programmer_raise - test_bare_raise_name_error_in_getattr_is_programmer_raise - test_bare_raise_in_setattr_is_programmer_raise - test_bare_raise_in_delattr_is_programmer_raise - test_bare_raise_in_getattribute_is_programmer_raise Each test feeds a minimal source sample through the visitor's _classify_raise and asserts INTERNAL_PROGRAMMER_RAISE. The tests cover all 4 dunder methods (__getattr__, __getattribute__, __setattr__, __delattr__) and both programmer-error exception types (AttributeError, NameError). Phase 11 result_migration_gui_2_20260619. --- tests/test_audit_heuristics.py | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/test_audit_heuristics.py b/tests/test_audit_heuristics.py index 241192d9..d22913f2 100644 --- a/tests/test_audit_heuristics.py +++ b/tests/test_audit_heuristics.py @@ -137,3 +137,87 @@ def test_phase7_migrated_sites_no_longer_silent_swallow(): f"Phase 7 regression: L{f.line} should not be " f"INTERNAL_SILENT_SWALLOW after migration; got {f.category}" ) + + +# Phase 11 Task 11.4 - Regression-guard tests for dunder-method bare-raise heuristic. +# Per Phase 11 spec (INTERNAL_RETHROW classification for dunder methods): +# - Bare `raise AttributeError(name)` / `raise NameError(name)` in +# `__getattr__`, `__getattribute__`, `__setattr__`, `__delattr__` is the +# canonical dunder-method programmer-error pattern (per styleguide +# "Re-Raise Patterns": bare raises are reserved for programmer errors). +# - The audit previously classified these as INTERNAL_RETHROW (suspicious); +# the heuristic must reclassify them as INTERNAL_PROGRAMMER_RAISE. + +DUNDER_RAISE_TESTS = { + "__getattr__": "def __getattr__(self, name):\n if name == 'controller':\n raise AttributeError(name)\n raise AttributeError(name)", + "__getattribute__": "def __getattribute__(self, name):\n if name == 'controller':\n raise AttributeError(name)\n raise AttributeError(name)", + "__setattr__": "def __setattr__(self, name, value):\n raise AttributeError(name)", + "__delattr__": "def __delattr__(self, name):\n raise AttributeError(name)", +} + + +def _classify_first_raise(source, func_name): + tree = ast.parse(source) + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef) and node.name == func_name: + visitor = ExceptionVisitor(str(ROOT / "src" / "_test_dummy.py")) + visitor._func_stack = [node] + for sub in ast.walk(node): + if isinstance(sub, ast.Raise) and sub.exc is not None: + return visitor._classify_raise(sub) + raise AssertionError(f"No raise found in {func_name}") + + +def test_bare_raise_attribute_error_in_getattr_is_programmer_raise(): + src = DUNDER_RAISE_TESTS["__getattr__"] + category, hint = _classify_first_raise(src, "__getattr__") + assert category == "INTERNAL_PROGRAMMER_RAISE", ( + f"Phase 11 regression: bare `raise AttributeError(name)` in __getattr__ " + f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " + f"got {category}. Hint: {hint}" + ) + + +def test_bare_raise_name_error_in_getattr_is_programmer_raise(): + src = ( + "def __getattr__(self, name):\n" + " if not hasattr(self, 'x'):\n" + " raise NameError(name)\n" + " return self.x" + ) + category, hint = _classify_first_raise(src, "__getattr__") + assert category == "INTERNAL_PROGRAMMER_RAISE", ( + f"Phase 11 regression: bare `raise NameError(name)` in __getattr__ " + f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " + f"got {category}. Hint: {hint}" + ) + + +def test_bare_raise_in_setattr_is_programmer_raise(): + src = DUNDER_RAISE_TESTS["__setattr__"] + category, hint = _classify_first_raise(src, "__setattr__") + assert category == "INTERNAL_PROGRAMMER_RAISE", ( + f"Phase 11 regression: bare `raise AttributeError` in __setattr__ " + f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " + f"got {category}. Hint: {hint}" + ) + + +def test_bare_raise_in_delattr_is_programmer_raise(): + src = DUNDER_RAISE_TESTS["__delattr__"] + category, hint = _classify_first_raise(src, "__delattr__") + assert category == "INTERNAL_PROGRAMMER_RAISE", ( + f"Phase 11 regression: bare `raise AttributeError` in __delattr__ " + f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " + f"got {category}. Hint: {hint}" + ) + + +def test_bare_raise_in_getattribute_is_programmer_raise(): + src = DUNDER_RAISE_TESTS["__getattribute__"] + category, hint = _classify_first_raise(src, "__getattribute__") + assert category == "INTERNAL_PROGRAMMER_RAISE", ( + f"Phase 11 regression: bare `raise AttributeError(name)` in __getattribute__ " + f"should be INTERNAL_PROGRAMMER_RAISE (canonical dunder-method pattern); " + f"got {category}. Hint: {hint}" + )