test(vision): chiude il punto cieco del confine, "Acquire" era un fantasma
FORBIDDEN confrontava solo il primo segmento del path importato: "from src.vision.runner import ..." dava radice "src", mai vietata (il backend importa se stesso di continuo), quindi il test non l'avrebbe mai vista. Confermato manualmente: la logica vecchia su quell'import restituisce un insieme vuoto di violazioni. Ora si confrontano i path puntati per intero contro "src.vision"/"src.vision_worker" come prefissi, non solo la prima radice. "Acquire" in FORBIDDEN era un nome di classe/SDK (Balluff "mvIMPACT Acquire"), mai una radice di import: non poteva mai far scattare nulla. vs-camera importa sotto lo stesso namespace visionsuite di vs-core (vendor/visionsuite/packages/vs-camera/pyproject.toml: "il codice si importa come visionsuite.camera...."), già coperto da "visionsuite": non c'era una radice separata da aggiungere, quindi è stato tolto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
This commit is contained in:
@@ -8,18 +8,54 @@ import ast
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
BACKEND = Path(__file__).resolve().parents[1]
|
BACKEND = Path(__file__).resolve().parents[1]
|
||||||
FORBIDDEN = {"visionsuite", "pm2d", "dxf_compare", "Acquire", "torch"}
|
|
||||||
|
# Full dotted prefixes, not bare roots: "src" alone would forbid every import
|
||||||
|
# the backend legitimately makes of itself (src.backend.*), so the entries
|
||||||
|
# for the vision tree are multi-segment ("src.vision", "src.vision_worker")
|
||||||
|
# while VisionSuite's own top-level packages stay single-segment.
|
||||||
|
#
|
||||||
|
# M2: "Acquire" used to sit here as a stand-in for vs-camera, but it is a
|
||||||
|
# class/SDK name (Balluff's "mvIMPACT Acquire" SDK), never an import root -
|
||||||
|
# it could never match. vs-camera imports under the same `visionsuite`
|
||||||
|
# namespace as vs-core (see
|
||||||
|
# vendor/visionsuite/packages/vs-camera/pyproject.toml: "il codice si importa
|
||||||
|
# come visionsuite.camera...."), which "visionsuite" below already forbids,
|
||||||
|
# so there is nothing separate to add for it.
|
||||||
|
FORBIDDEN = {
|
||||||
|
"visionsuite", "pm2d", "dxf_compare", "torch",
|
||||||
|
"src.vision", "src.vision_worker",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _imported_roots(source: Path) -> set[str]:
|
def _imported_modules(source: Path) -> set[str]:
|
||||||
|
"""Full dotted module paths this file imports - not just their first
|
||||||
|
segment. `from src.vision.runner import ...` must be checked against
|
||||||
|
"src.vision.runner" (inside the forbidden "src.vision"), not against
|
||||||
|
"src" alone, which is never forbidden since the backend imports itself
|
||||||
|
constantly.
|
||||||
|
"""
|
||||||
tree = ast.parse(source.read_text(encoding="utf-8"), filename=str(source))
|
tree = ast.parse(source.read_text(encoding="utf-8"), filename=str(source))
|
||||||
roots: set[str] = set()
|
modules: set[str] = set()
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
roots.update(alias.name.split(".")[0] for alias in node.names)
|
modules.update(alias.name for alias in node.names)
|
||||||
elif isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
|
elif isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
|
||||||
roots.add(node.module.split(".")[0])
|
modules.add(node.module)
|
||||||
return roots
|
return modules
|
||||||
|
|
||||||
|
|
||||||
|
def _forbidden_hits(modules: set[str]) -> set[str]:
|
||||||
|
"""Which FORBIDDEN entries a set of imported module paths triggers.
|
||||||
|
|
||||||
|
A module counts as forbidden if it equals a forbidden entry exactly, or
|
||||||
|
lives inside it ("src.vision.runner" is inside "src.vision").
|
||||||
|
"""
|
||||||
|
hits: set[str] = set()
|
||||||
|
for module in modules:
|
||||||
|
for entry in FORBIDDEN:
|
||||||
|
if module == entry or module.startswith(entry + "."):
|
||||||
|
hits.add(entry)
|
||||||
|
return hits
|
||||||
|
|
||||||
|
|
||||||
def test_the_server_never_imports_visionsuite():
|
def test_the_server_never_imports_visionsuite():
|
||||||
@@ -27,7 +63,7 @@ def test_the_server_never_imports_visionsuite():
|
|||||||
for source in BACKEND.rglob("*.py"):
|
for source in BACKEND.rglob("*.py"):
|
||||||
if "tests" in source.parts or "migrations" in source.parts:
|
if "tests" in source.parts or "migrations" in source.parts:
|
||||||
continue
|
continue
|
||||||
forbidden = _imported_roots(source) & FORBIDDEN
|
forbidden = _forbidden_hits(_imported_modules(source))
|
||||||
if forbidden:
|
if forbidden:
|
||||||
offenders.append(f"{source.relative_to(BACKEND)}: {sorted(forbidden)}")
|
offenders.append(f"{source.relative_to(BACKEND)}: {sorted(forbidden)}")
|
||||||
|
|
||||||
@@ -42,3 +78,29 @@ def test_the_submodule_is_pinned():
|
|||||||
gitmodules = Path(__file__).resolve().parents[3] / ".gitmodules"
|
gitmodules = Path(__file__).resolve().parents[3] / ".gitmodules"
|
||||||
assert gitmodules.exists(), "vendor/visionsuite non è un sottomodulo"
|
assert gitmodules.exists(), "vendor/visionsuite non è un sottomodulo"
|
||||||
assert "vendor/visionsuite" in gitmodules.read_text(encoding="utf-8")
|
assert "vendor/visionsuite" in gitmodules.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_full_module_path_import_is_caught_not_just_its_root(tmp_path):
|
||||||
|
"""M2: `from src.vision.runner import ...` inside src/backend/** must be
|
||||||
|
caught. Checking only the first dotted segment ("src") would miss it -
|
||||||
|
"src" itself is never forbidden, the backend imports itself all the time.
|
||||||
|
This is the blind spot the reviewer found: same repository, no install
|
||||||
|
needed, the most tempting shortcut of all.
|
||||||
|
"""
|
||||||
|
offender = tmp_path / "sneaky.py"
|
||||||
|
offender.write_text("from src.vision.runner import run_graph\n")
|
||||||
|
assert _forbidden_hits(_imported_modules(offender)) == {"src.vision"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_vision_worker_import_is_also_caught(tmp_path):
|
||||||
|
offender = tmp_path / "sneaky.py"
|
||||||
|
offender.write_text("import src.vision_worker.main\n")
|
||||||
|
assert _forbidden_hits(_imported_modules(offender)) == {"src.vision_worker"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_unrelated_src_backend_import_is_not_caught(tmp_path):
|
||||||
|
"""The fix must not turn every self-import of the backend into an
|
||||||
|
offender - only the vision subtrees are forbidden."""
|
||||||
|
innocent = tmp_path / "innocent.py"
|
||||||
|
innocent.write_text("from src.backend.config import settings\n")
|
||||||
|
assert _forbidden_hits(_imported_modules(innocent)) == set()
|
||||||
|
|||||||
Reference in New Issue
Block a user