feat(engines): fondation 'capabilities moteurs' testable et partagée
Utilitaire neutre (ni CLI ni GUI) qui dit la vérité sur les moteurs réellement disponibles dans le build COURANT (la sonde reflète l'exécutable qui tourne, sans présumer d'un autre build). Consommé séparément par chaque axe produit. - `EngineCapability(key, label, available, required, reason)`. - Sondes légères `importlib.util.find_spec` (pas d'import lourd au démarrage) + présence du modèle ONNX pour CamemBERT (gère _MEIPASS en frozen). - camembert=requis ; eds (edsnlp+spacy) / gliner=optionnels. Sondes injectables, fail-closed. `capabilities_map()` / `available_engines()`. 6 tests (sondes injectables dispo/indispo, required, reasons, sondes réelles). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
tests/unit/test_engine_capabilities.py
Normal file
66
tests/unit/test_engine_capabilities.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Couche 'capabilities moteurs' : vérité testable sur les moteurs disponibles.
|
||||
|
||||
Un moteur n'est *disponible* que si ses dépendances (et son modèle, pour
|
||||
CamemBERT) chargent réellement dans l'environnement courant. Cette couche est
|
||||
consommée par la GUI (afficher/désactiver) et le CLI (`--engines`) pour que
|
||||
l'application ne promette jamais un moteur qu'elle n'embarque pas.
|
||||
|
||||
Sondes injectables → aucun modèle, aucun réseau dans les tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import engine_capabilities as ec
|
||||
|
||||
|
||||
def _probes(camembert=True, eds=False, gliner=False):
|
||||
return {
|
||||
"camembert": lambda: (camembert, "ok" if camembert else "modèle absent"),
|
||||
"eds": lambda: (eds, "ok" if eds else "edsnlp non embarqué"),
|
||||
"gliner": lambda: (gliner, "ok" if gliner else "gliner non embarqué"),
|
||||
}
|
||||
|
||||
|
||||
def test_capabilities_map_reads_injected_probes():
|
||||
caps = ec.capabilities_map(probes=_probes(camembert=True, eds=False, gliner=False))
|
||||
assert set(caps) == {"camembert", "eds", "gliner"}
|
||||
assert caps["camembert"].available is True
|
||||
assert caps["eds"].available is False
|
||||
assert caps["gliner"].available is False
|
||||
|
||||
|
||||
def test_camembert_required_others_optional():
|
||||
caps = ec.capabilities_map(probes=_probes())
|
||||
assert caps["camembert"].required is True
|
||||
assert caps["eds"].required is False
|
||||
assert caps["gliner"].required is False
|
||||
|
||||
|
||||
def test_reason_surfaced_when_unavailable():
|
||||
caps = ec.capabilities_map(probes=_probes(eds=False))
|
||||
assert "edsnlp" in caps["eds"].reason # explication présentable à l'utilisateur
|
||||
# un moteur disponible expose aussi une raison non vide
|
||||
assert caps["camembert"].reason
|
||||
|
||||
|
||||
def test_available_engines_filters_unavailable():
|
||||
avail = ec.available_engines(probes=_probes(camembert=True, eds=True, gliner=False))
|
||||
keys = {c.key for c in avail}
|
||||
assert keys == {"camembert", "eds"}
|
||||
|
||||
|
||||
def test_labels_are_human_readable():
|
||||
caps = ec.capabilities_map(probes=_probes())
|
||||
assert "CamemBERT" in caps["camembert"].label
|
||||
assert "EDS" in caps["eds"].label
|
||||
assert "GLiNER" in caps["gliner"].label
|
||||
|
||||
|
||||
def test_default_probes_run_without_crash_and_are_consistent():
|
||||
"""Les sondes par défaut (find_spec + fichier modèle) ne crashent pas et
|
||||
renvoient un booléen + une raison non vide pour chaque moteur."""
|
||||
caps = ec.capabilities_map() # sondes réelles de l'environnement
|
||||
assert set(caps) == {"camembert", "eds", "gliner"}
|
||||
for cap in caps.values():
|
||||
assert isinstance(cap.available, bool)
|
||||
assert isinstance(cap.reason, str) and cap.reason
|
||||
Reference in New Issue
Block a user