Files
TieMeasureFlow/src/vision/tests/test_runner.py
T
Adriano 0a3d1f5222 fix(vision): aggancia i test alla suite di default, skip pulito su 3.11
testpaths include src/vision/tests: prima uv run pytest (365 test) non ci
passava mai. Niente conftest.py: un pytest.importorskip a livello di modulo
in conftest.py rompe l'intera sessione, perche' Skipped eredita da
BaseException e _importconftest cattura solo Exception (verificato con
traceback, provato sia su src/vision/tests sia su src/vision come radice).
La guardia sta invece in cima a test_runner.py, prima di numpy (assente
anch'esso su 3.11) - idioma standard di pytest, cattura pulita a livello di
collection. README aggiornato con il comando reale per farli girare.
2026-08-16 17:39:57 +02:00

77 lines
2.8 KiB
Python

"""Il runner: grafo più immagine, numeri fuori. Niente HTTP, niente database."""
import pytest
# The vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d declare it
# upstream, see pyproject.toml's `vision` extra), while backend and frontend
# stay on 3.11. This must run before `import numpy` - numpy itself is only
# installed alongside the vision extra - so that `uv run pytest` on the
# default 3.11 environment reports this module as skipped, with a reason,
# instead of erroring out of the whole collection.
pytest.importorskip(
"visionsuite.task",
reason=(
"the vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d "
"declare it upstream); run it with "
"`uv run --python 3.13 --extra vision --extra dev pytest src/vision/tests`"
),
)
import numpy as np
from src.vision.runner import RunOutcome, engine_version, run_graph
def a_disc(radius: int = 60, size: int = 400) -> np.ndarray:
"""Un disco chiaro su fondo scuro: un pezzo finto ma misurabile."""
image = np.zeros((size, size), dtype=np.uint8)
yy, xx = np.ogrid[:size, :size]
centre = size // 2
image[(yy - centre) ** 2 + (xx - centre) ** 2 <= radius ** 2] = 255
return image
def a_circle_graph() -> dict:
"""Un grafo di due strumenti, costruito con l'API di vs-task."""
from visionsuite.task import Task
from visionsuite.task.tools import DetectCircleTool, DiameterTool
task = Task(name="prova")
found = task.add(DetectCircleTool(name="disco"))
found.center_row, found.center_col, found.radius = 200.0, 200.0, 60.0
found.scan_size = 30.0
quota = task.add(DiameterTool(name="diametro"))
quota.link("circle", found, "circle")
return task.to_dict()
def test_the_graph_produces_named_outputs():
outcome = run_graph(a_disc(), a_circle_graph())
assert isinstance(outcome, RunOutcome)
assert outcome.failures == []
# Le chiavi sono «id dello strumento punto nome dell'uscita»: è la stessa
# forma che la subtask salva in vision_output.
assert any(key.endswith(".diameter") for key in outcome.outputs)
diameter = next(v for k, v in outcome.outputs.items() if k.endswith(".diameter"))
assert diameter == pytest.approx(120.0, abs=4.0)
def test_a_broken_tool_does_not_sink_the_others():
"""vs-task non ferma l'esecuzione su un errore: il runner lo riporta."""
graph = a_circle_graph()
graph["tools"].append(
{"id": "ghost", "kind": "DetectCircle", "name": "assente",
"properties": {"center_row": 5.0, "center_col": 5.0, "radius": 900.0}}
)
outcome = run_graph(a_disc(), graph)
assert [f.tool_id for f in outcome.failures] == ["ghost"]
assert any(key.endswith(".diameter") for key in outcome.outputs)
def test_the_engine_says_which_version_it_is():
version = engine_version()
assert len(version) == 40 # un SHA di git, per intero