Files
2026-07-13 12:49:17 +08:00

76 lines
2.9 KiB
Python

"""Symmetry guard: every MCP tool module must have test coverage.
Enforces the CLAUDE.md rule that "Every new feature needs tests". A tool module
(any file under ``services/tools/`` that exposes an ``@mcp_for_unity_tool``) is
considered covered when a ``test_*.py`` file under ``Server/tests/`` references it
by module name -- a dedicated ``test_<module>.py`` or a characterization test
counts. Non-``test_*.py`` scripts (e.g. ``tests/e2e/bridge_smoke.py``) are not
scanned, so a tool exercised only there still needs a unit/characterization test.
Tools that are genuinely untested today live in ``KNOWN_UNTESTED`` so this guard
fails for *new* untested tools without forcing a full backfill first. SHRINK the
quarantine list as coverage lands -- a stale entry (a module that now *has*
coverage) also fails the guard, so the list can only get smaller, never grow
silently.
"""
import re
from pathlib import Path
import pytest
TOOLS_DIR = Path(__file__).resolve().parents[1] / "src" / "services" / "tools"
TESTS_DIR = Path(__file__).resolve().parent
# Modules under tools/ that are infrastructure, not user-facing tool surfaces.
NOT_TOOLS = {"__init__", "utils", "preflight", "debug_request_context"}
# Tool modules without any test reference today. Do NOT add to this list for new
# tools -- new tools must ship with a test. Remove an entry once coverage lands.
KNOWN_UNTESTED = {
"execute_menu_item",
"manage_shader",
"manage_tools",
}
def _tool_modules() -> list[str]:
mods = []
for path in sorted(TOOLS_DIR.glob("*.py")):
if path.stem in NOT_TOOLS:
continue
if "@mcp_for_unity_tool" in path.read_text(encoding="utf-8"):
mods.append(path.stem)
return mods
def _is_referenced(stem: str) -> bool:
pattern = re.compile(rf"\b{re.escape(stem)}\b")
for test_file in TESTS_DIR.rglob("test_*.py"):
if test_file.resolve() == Path(__file__).resolve():
continue
if pattern.search(test_file.read_text(encoding="utf-8")):
return True
return False
@pytest.mark.parametrize("module", _tool_modules())
def test_every_tool_module_has_test_coverage(module):
if module in KNOWN_UNTESTED:
pytest.skip(f"{module} is quarantined in KNOWN_UNTESTED; add a test and remove it")
assert _is_referenced(module), (
f"Tool module '{module}' has no test referencing it. Every tool needs a "
f"test (CLAUDE.md). Add a Server/tests/test_{module}.py, or -- only if you "
f"have a justified reason -- add '{module}' to KNOWN_UNTESTED."
)
@pytest.mark.parametrize("module", sorted(KNOWN_UNTESTED))
def test_quarantine_list_has_no_stale_entries(module):
assert module in set(_tool_modules()), (
f"KNOWN_UNTESTED entry '{module}' is not a real tool module -- remove it."
)
assert not _is_referenced(module), (
f"'{module}' now has test coverage -- remove it from KNOWN_UNTESTED so the "
f"quarantine list stays honest."
)