diff --git a/scripts/audit_main_thread_imports.py b/scripts/audit_main_thread_imports.py index 4db81db6..074ccf18 100644 --- a/scripts/audit_main_thread_imports.py +++ b/scripts/audit_main_thread_imports.py @@ -43,6 +43,8 @@ LEAN_ALLOWLIST: set[str] = { "src.events", "src.config", "src.module_loader", + "src.startup_profiler", + "src.api_hooks", } diff --git a/tests/test_audit_allowlist_2d.py b/tests/test_audit_allowlist_2d.py new file mode 100644 index 00000000..ba5d2522 --- /dev/null +++ b/tests/test_audit_allowlist_2d.py @@ -0,0 +1,88 @@ +"""Tests that scripts/audit_main_thread_imports.py LEAN_ALLOWLIST is kept in sync with actually-lean src.* modules. + +The audit script has a conservative allowlist of src.* modules known to be +safe at top-level import. When a module is refactored to remove heavy +imports (sub-tracks 2A-2F of startup_speedup_20260606), it should be added +to the allowlist so sloppy.py can import it without being flagged. + +Modules currently in the allowlist: + - src.imgui_scopes, src.theme_2, src.theme_models, src.paths, src.models, + src.events, src.config, src.module_loader (added sub-track 2C) + - src.startup_profiler, src.api_hooks (added sub-track 2D) + +Modules NOT yet in the allowlist (awaiting sub-tracks 2E and 2F): + - src.app_controller (23 violations remaining) + - src.gui_2 (24 violations remaining) +""" + +import subprocess +import sys +import textwrap +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess: + script = textwrap.dedent(snippet) + return subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + cwd=str(ROOT), + timeout=30, + ) + + +def test_startup_profiler_is_lean_and_in_allowlist() -> None: + res = _run_in_subprocess(""" + import sys + import src.startup_profiler + heavy = {'google.genai', 'anthropic', 'openai', 'fastapi', 'requests', 'numpy', 'tkinter', 'pydantic'} + leaked = [m for m in heavy if m in sys.modules or any(k in sys.modules for k in [m, m + '.types', m + '.security'])] + for m in heavy: + top = m.split('.')[0] + if top in sys.modules: + leaked.append(top) + print('LEAKED', leaked) + """) + assert res.returncode == 0, f"stderr: {res.stderr}" + assert res.stdout.strip() == "LEAKED []", f"src.startup_profiler leaks heavy: {res.stdout}" + + +def test_api_hooks_is_lean_and_in_allowlist() -> None: + res = _run_in_subprocess(""" + import sys + import src.api_hooks + heavy_top = {'google', 'anthropic', 'openai', 'fastapi', 'requests', 'numpy', 'tkinter', 'pydantic', 'websockets'} + leaked = [m for m in heavy_top if m in sys.modules] + print('LEAKED', leaked) + """) + assert res.returncode == 0, f"stderr: {res.stderr}" + assert res.stdout.strip() == "LEAKED []", f"src.api_hooks leaks heavy: {res.stdout}" + + +def test_allowlist_includes_startup_profiler_and_api_hooks() -> None: + res = _run_in_subprocess(""" + from scripts.audit_main_thread_imports import LEAN_ALLOWLIST + print('startup_profiler', 'src.startup_profiler' in LEAN_ALLOWLIST) + print('api_hooks', 'src.api_hooks' in LEAN_ALLOWLIST) + print('module_loader', 'src.module_loader' in LEAN_ALLOWLIST) + """) + assert res.returncode == 0, f"stderr: {res.stderr}" + lines = res.stdout.strip().splitlines() + assert lines[0] == "startup_profiler True", f"src.startup_profiler not in allowlist: {res.stdout}" + assert lines[1] == "api_hooks True", f"src.api_hooks not in allowlist: {res.stdout}" + assert lines[2] == "module_loader True", f"src.module_loader not in allowlist: {res.stdout}" + + +def test_app_controller_and_gui_2_not_yet_in_allowlist() -> None: + res = _run_in_subprocess(""" + from scripts.audit_main_thread_imports import LEAN_ALLOWLIST + print('app_controller', 'src.app_controller' in LEAN_ALLOWLIST) + print('gui_2', 'src.gui_2' in LEAN_ALLOWLIST) + """) + assert res.returncode == 0, f"stderr: {res.stderr}" + lines = res.stdout.strip().splitlines() + assert lines[0] == "app_controller False", f"src.app_controller should NOT be in allowlist yet: {res.stdout}" + assert lines[1] == "gui_2 False", f"src.gui_2 should NOT be in allowlist yet: {res.stdout}"