Private
Public Access
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.
This commit is contained in:
@@ -2478,3 +2478,93 @@ def test_phase_11_invariant_l757_l760_reclassified():
|
||||
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]}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user