mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-11-04 03:52:34 +00:00
Codex caught three deleted public methods earlier and a later scan found twelve more, because nothing tested for them. The golden records every public module function, class and method; the test fails only on a removal, and accepts a name that still resolves through a base class or re-export.
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
"""Characterization: every public name under modules/ that local plugins could use.
|
|
|
|
Local plugins and services (local/) may call any public module function,
|
|
class or method, so a refactor must not remove one. New names are fine; the
|
|
golden file only fails on removals (regenerate with UPDATE_GOLDEN=1 when a
|
|
removal is intentional).
|
|
"""
|
|
|
|
import ast
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from tests.characterization.golden_util import GOLDEN_DIR
|
|
|
|
MODULES = Path(__file__).resolve().parents[2] / "modules"
|
|
GOLDEN = GOLDEN_DIR / "public_api.json"
|
|
|
|
|
|
def _public_names() -> dict[str, list[str]]:
|
|
names: dict[str, list[str]] = {}
|
|
for path in sorted(MODULES.rglob("*.py")):
|
|
rel = path.relative_to(MODULES.parent).as_posix()
|
|
found = set()
|
|
for node in ast.parse(path.read_text(encoding="utf-8")).body:
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and not node.name.startswith("_"):
|
|
found.add(node.name)
|
|
elif isinstance(node, ast.ClassDef) and not node.name.startswith("_"):
|
|
found.add(node.name)
|
|
for member in node.body:
|
|
if isinstance(member, (ast.FunctionDef, ast.AsyncFunctionDef)) and not member.name.startswith("_"):
|
|
found.add(f"{node.name}.{member.name}")
|
|
if found:
|
|
names[rel] = sorted(found)
|
|
return names
|
|
|
|
|
|
def _resolves(module: str, name: str) -> bool:
|
|
import importlib
|
|
|
|
obj = importlib.import_module(module[:-3].replace("/", "."))
|
|
for part in name.split("."):
|
|
if not hasattr(obj, part):
|
|
return False
|
|
obj = getattr(obj, part)
|
|
return True
|
|
|
|
|
|
def test_no_public_name_was_removed():
|
|
import os
|
|
|
|
current = _public_names()
|
|
if os.environ.get("UPDATE_GOLDEN") == "1" or not GOLDEN.exists():
|
|
GOLDEN.write_text(json.dumps(current, indent=1, sort_keys=True) + "\n", encoding="utf-8")
|
|
return
|
|
recorded = json.loads(GOLDEN.read_text(encoding="utf-8"))
|
|
missing = []
|
|
for module, names in recorded.items():
|
|
defined = set(current.get(module, []))
|
|
for name in names:
|
|
# A method moved to a base class, or a function re-exported, still counts.
|
|
if name not in defined and not _resolves(module, name):
|
|
missing.append(f"{module}: {name}")
|
|
assert missing == [], "public names removed:\n " + "\n ".join(missing)
|