build: specs GUI V6 + CLI torch-free — retrait optimum, excludes explicites (P0-3)
Suppression des 5 hiddenimports optimum* dans les deux specs V6/CLI. Ajout de EXCLUDED_TORCH_STACK + excludes=EXCLUDED_TORCH_STACK dans Analysis() pour éviter que PyInstaller embarque torch (~+2 Go) via optimum à l'analyse statique. Spec GUI V5 legacy inchangée (garde optimum légitimement). Test anti-dérive ajouté (5 cas). Correctif import pytest inutilisé (version.py). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
68
tests/unit/test_build_specs_torch_free.py
Normal file
68
tests/unit/test_build_specs_torch_free.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""Anti-dérive P0-3 (Plan 3) : les specs frozen GUI V6 et CLI doivent être torch-free.
|
||||
|
||||
On vérifie le TEXTE des specs (pas d'exécution PyInstaller sous Linux) :
|
||||
- aucun hiddenimport optimum*/torch*/doctr* ;
|
||||
- excludes explicites présents (torch, torchvision, optimum, doctr) — ceinture
|
||||
et bretelles : même si le venv de build contient torch, l'analyse l'exclut
|
||||
(le core fait un `import torch` lazy dans _configure_torch_threads, que
|
||||
l'analyse statique de PyInstaller suivrait sans excludes).
|
||||
La spec GUI V5 legacy (anonymisation_onefile.spec) n'est PAS concernée.
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TORCH_FREE_SPECS = [
|
||||
ROOT / "anonymisation_gui_v6_onefile.spec",
|
||||
ROOT / "anonymisation_cli_onefile.spec",
|
||||
]
|
||||
FORBIDDEN_HIDDEN = ("optimum", "torch", "torchvision", "doctr")
|
||||
REQUIRED_EXCLUDES = ("torch", "torchvision", "optimum", "doctr")
|
||||
|
||||
|
||||
def _hiddenimport_strings(text):
|
||||
"""Retourne les chaînes littérales présentes dans la section hiddenimports=[...].
|
||||
|
||||
On extrait uniquement la portion du texte entre `hiddenimports = [` et le `]`
|
||||
de fermeture correspondant (premier `]` sur une ligne seule), afin d'éviter
|
||||
les faux positifs générés par le bloc EXCLUDED_TORCH_STACK lui-même qui
|
||||
contient légitimement "torch", "optimum", etc. comme valeurs d'exclusion.
|
||||
"""
|
||||
# Cherche le bloc hiddenimports = [ ... ]
|
||||
match = re.search(r"hiddenimports\s*=\s*\[([^\]]*)\]", text, re.DOTALL)
|
||||
if not match:
|
||||
return []
|
||||
block = match.group(1)
|
||||
return re.findall(r"[\"']([A-Za-z0-9_.]+)[\"']", block)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec_path", TORCH_FREE_SPECS, ids=lambda p: p.name)
|
||||
def test_spec_sans_hiddenimport_torch_optimum_doctr(spec_path):
|
||||
text = spec_path.read_text(encoding="utf-8")
|
||||
hits = [
|
||||
s for s in _hiddenimport_strings(text)
|
||||
if s.split(".")[0] in FORBIDDEN_HIDDEN
|
||||
]
|
||||
assert hits == [], f"{spec_path.name} référence encore : {hits}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec_path", TORCH_FREE_SPECS, ids=lambda p: p.name)
|
||||
def test_spec_declare_excludes_torch(spec_path):
|
||||
text = spec_path.read_text(encoding="utf-8")
|
||||
assert "excludes=EXCLUDED_TORCH_STACK" in text, (
|
||||
f"{spec_path.name} : Analysis() sans excludes=EXCLUDED_TORCH_STACK"
|
||||
)
|
||||
for name in REQUIRED_EXCLUDES:
|
||||
assert f'"{name}"' in text.split("EXCLUDED_TORCH_STACK")[1].split("]")[0], (
|
||||
f"{spec_path.name} : '{name}' absent de EXCLUDED_TORCH_STACK"
|
||||
)
|
||||
|
||||
|
||||
def test_spec_legacy_v5_garde_optimum():
|
||||
text = (ROOT / "anonymisation_onefile.spec").read_text(encoding="utf-8")
|
||||
assert '"optimum"' in text, (
|
||||
"anonymisation_onefile.spec (GUI V5 legacy) doit GARDER optimum — "
|
||||
"si ce test casse, quelqu'un a modifié la spec legacy par erreur."
|
||||
)
|
||||
@@ -6,8 +6,6 @@ gui_v6/_build_version.py (non commité). En dev, repli sur la version par défau
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
from gui_v6.version import DEFAULT_VERSION, resolve_version
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user