Compare commits
7 Commits
8f9107a27f
...
5dbad699bc
| Author | SHA1 | Date | |
|---|---|---|---|
| 5dbad699bc | |||
| 7dba4014c4 | |||
| 2c48d95c1f | |||
| f9b6f21923 | |||
| e4bc9166be | |||
| 65c97a39b3 | |||
| 5f05ba0fb8 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -49,6 +49,10 @@ models/
|
||||
# build_info.py : régénéré automatiquement par scripts/rebuild_anon.ps1
|
||||
# avec date/commit/branch. Ne pas versionner.
|
||||
build_info.py
|
||||
|
||||
# gui_v6/_build_version.py : généré au build Windows par build_windows_oneclick.ps1
|
||||
# (contient BUILD_VERSION = "2026.MM.JJ.HHMM"). Ne pas commiter.
|
||||
gui_v6/_build_version.py
|
||||
*.mp3
|
||||
*.wav
|
||||
*.mp4
|
||||
|
||||
@@ -114,11 +114,6 @@ hiddenimports = [
|
||||
"yaml",
|
||||
"loguru",
|
||||
"regex",
|
||||
"optimum",
|
||||
"optimum.onnxruntime",
|
||||
"optimum.pipelines",
|
||||
"optimum.modeling_base",
|
||||
"optimum.exporters.onnx",
|
||||
]
|
||||
|
||||
|
||||
@@ -153,12 +148,24 @@ for _package_name in [
|
||||
_collect_optional_package(_package_name)
|
||||
|
||||
|
||||
# P0-3 (Plan 3) : exclusion dure de la pile torch. Le core fait un
|
||||
# `import torch` lazy (try/except no-op) dans _configure_torch_threads que
|
||||
# l'analyse statique suivrait ; en frozen l'ImportError est attendue et gérée.
|
||||
EXCLUDED_TORCH_STACK = [
|
||||
"torch",
|
||||
"torchvision",
|
||||
"optimum",
|
||||
"doctr",
|
||||
]
|
||||
|
||||
|
||||
a = Analysis(
|
||||
[str(project_dir / "scripts" / "anonymize_cli.py")],
|
||||
pathex=[str(project_dir)],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
excludes=EXCLUDED_TORCH_STACK,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
@@ -78,6 +78,8 @@ hiddenimports = [
|
||||
"gui_v6.machine_id",
|
||||
"gui_v6.engine_bridge",
|
||||
"gui_v6.config_state",
|
||||
"gui_v6.version",
|
||||
"gui_v6._build_version",
|
||||
"gui_v6.processing_runner",
|
||||
"gui_v6.tabs",
|
||||
"gui_v6.tabs.tab_about",
|
||||
@@ -133,11 +135,16 @@ hiddenimports = [
|
||||
"yaml",
|
||||
"loguru",
|
||||
"regex",
|
||||
]
|
||||
|
||||
# P0-3 (Plan 3) : exclusion dure de la pile torch. Le core fait un
|
||||
# `import torch` lazy (try/except no-op) dans _configure_torch_threads que
|
||||
# l'analyse statique suivrait ; en frozen l'ImportError est attendue et gérée.
|
||||
EXCLUDED_TORCH_STACK = [
|
||||
"torch",
|
||||
"torchvision",
|
||||
"optimum",
|
||||
"optimum.onnxruntime",
|
||||
"optimum.pipelines",
|
||||
"optimum.modeling_base",
|
||||
"optimum.exporters.onnx",
|
||||
"doctr",
|
||||
]
|
||||
|
||||
|
||||
@@ -146,6 +153,7 @@ a = Analysis(
|
||||
pathex=[str(project_dir)],
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
excludes=EXCLUDED_TORCH_STACK,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
37
docs/beta/2026-07-02_plan3-reference-docs-reels.md
Normal file
37
docs/beta/2026-07-02_plan3-reference-docs-reels.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Référence détections — 6 documents réels (Plan 3, avant build torch-free)
|
||||
|
||||
Établie sur HEAD `7dba401` (.venv Linux, chemin dev). Sert de comparaison au smoke
|
||||
Windows torch-free (Task 8). **AUCUNE valeur PII — compteurs uniquement.**
|
||||
|
||||
Les documents sont identifiés par leur **numéro de dossier** du corpus interne
|
||||
(jamais par un nom de patient). Les sorties de traitement sont restées dans `/tmp`
|
||||
(non commité). Les compteurs proviennent du champ `kind` de l'audit `.audit.jsonl`
|
||||
produit par `scripts/anonymize_cli.py` (contrat de production : burn raster +
|
||||
texte pseudonymisé + audit JSONL). Le champ `original` (la valeur détectée) n'a
|
||||
jamais été lu ni recopié.
|
||||
|
||||
## Environnement de référence
|
||||
|
||||
- `pytest tests/unit` = **499 passed / 0 failed** (7 warnings, 0 fail).
|
||||
- `pytest -k synthetic_regression` = **PASS** (11 passed, 488 deselected) — gate strict de non-régression du masquage.
|
||||
- CamemBERT-bio ONNX v3 chargé (obligatoire) ✓ ; EDS-Pseudo chargé (optionnel) ✓ ; GLiNER désactivé (défaut).
|
||||
- Chemin dev : `torch=2.10.0+cu128` présent dans l'environnement (c'est précisément ce que le build Windows torch-free retirera). CamemBERT-bio tourne déjà via onnxruntime, indépendant de torch.
|
||||
|
||||
## Détections par document
|
||||
|
||||
| Doc (n° dossier) | Type | Pages | ocr_used | Code | Total dét. | Détections par type (kind) |
|
||||
|------------------|--------|-------|----------|------|------------|-----------------------------|
|
||||
| 102_23056463 | natif | 2 | False | 0 | 104 | NOM:31 RPPS:24 ETAB:18 TEL:10 VILLE_GAZ:4 CODE_POSTAL:3 DATE_NAISSANCE:3 EMAIL:2 EPISODE:2 FAX:2 FINESS:2 IPP:2 VILLE:1 |
|
||||
| 101_23041413 | natif | 1 | False | 0 | 22 | NOM:7 CODE_POSTAL:6 NOM_INITIAL:4 ADRESSE:2 NOM_FORCE:2 VILLE:1 |
|
||||
| 103_23056749 | natif | 2 | False | 0 | 109 | NOM:33 RPPS:24 ETAB:18 TEL:10 VILLE_GAZ:5 CODE_POSTAL:3 DATE_NAISSANCE:3 EMAIL:2 EPISODE:2 FAX:2 FINESS:2 IPP:2 ADRESSE:1 ETAB_FINESS:1 VILLE:1 |
|
||||
| 192_23132490 | scanné | 1 | True | 0 | 17 | NOM:7 CODE_POSTAL:3 ETAB_FINESS:2 TEL:2 DATE_NAISSANCE:1 NOM_FORCE:1 URL:1 |
|
||||
| 19_23103383 | scanné | 1 | True | 0 | 15 | NOM:4 CODE_POSTAL:2 ETAB_FINESS:2 TEL:4 DATE_NAISSANCE:1 NOM_FORCE:1 VILLE_GAZ:1 |
|
||||
| 258_23208848 | scanné | 1 | True | 0 | 16 | ETAB:4 NOM:3 AGE:2 CODE_POSTAL:2 NOM_FORCE:2 ETAB_FINESS:1 TEL:1 VILLE:1 |
|
||||
|
||||
## Notes
|
||||
|
||||
- **3 natifs / 3 scannés confirmés** : les 3 natifs ont `ocr_used=False` (texte extractible directement) ; les 3 scannés ont `ocr_used=True` (image-only → OCR docTR/OnnxTR). Les 6 codes retour CLI = **0**.
|
||||
- Tous les documents ont `quarantine_flags=[]` (aucune mise en quarantaine).
|
||||
- **Tout écart de compteur au smoke Windows (Task 8) = signal de régression torch-free à investiguer.** La comparaison doit se faire par (doc, type de kind, nombre), pas seulement sur le total.
|
||||
- **Point de vigilance edsnlp/drugs.json (revue Task 2)** : si des compteurs de type médicament (ou une variation des NOM/ETAB liée au filtrage médicaments) diffèrent en frozen, c'est potentiellement la perte du gazetteer edsnlp — voir mission Qwen. Aucun `kind` explicitement « médicament » n'apparaît dans l'audit de ces 6 docs (les médicaments servent de stop-words/whitelist, pas de type détecté), donc surveiller surtout une **hausse anormale de NOM/ETAB** en frozen (faux positifs par perte du filtre).
|
||||
- Rappel : les compteurs d'audit incluent l'ensemble du pipeline (NER multi-signal + regex + gazetteers après cross-validation), ce qui explique un total supérieur au seul « NER-first: N détections » visible dans les logs.
|
||||
@@ -20,6 +20,15 @@ récupérer la GUI et d'activer sa licence.
|
||||
(le cookie Secure confirme que le fix `884661a` tourne — APP_ENV=production).
|
||||
|
||||
## 1. Publier l'installateur GUI comme artefact actif
|
||||
|
||||
**Avant l'upload — vérifications obligatoires :**
|
||||
|
||||
- [ ] Vérifier l'URL portail embarquée dans l'EXE fraîchement buildé :
|
||||
lancer l'EXE avec `--self-test` et contrôler dans le log que
|
||||
`resolve_portal_url()` retourne `https://app.aivanov.eu` (pas localhost).
|
||||
- [ ] Mettre à jour le SHA-256 dans `note-beta-client.md` (le SHA change à
|
||||
chaque rebuild — l'ancienne note devient caduque, P1-8).
|
||||
|
||||
**Pré-requis : l'EXE Windows doit d'abord être copié sur le serveur Linux** (il est
|
||||
aujourd'hui sur la machine de build Windows, non diffusé). Une fois sur le serveur,
|
||||
depuis `/home/dom/ai/app_aivanov` avec l'environnement prod chargé :
|
||||
|
||||
@@ -94,6 +94,33 @@ Sorties attendues identiques :
|
||||
diffusion ;
|
||||
- `release\Anonymisation.exe.sha256.txt` : hash de l'exécutable.
|
||||
|
||||
## Build GUI V6 torch-free (Plan 3)
|
||||
|
||||
Depuis le Plan 3 (2026-07), le flavor `-GuiV6` :
|
||||
|
||||
1. **Purge torch/optimum du venv de build** (P0-3) : `optimum[onnxruntime]`
|
||||
(requirements.txt) tire `torch>=1.11` en dépendance cœur ; la GUI V6 ne
|
||||
l'utilise jamais (NER = onnxruntime brut, OCR = OnnxTR). Le script échoue
|
||||
si `torch` reste importable après purge. La spec legacy V5
|
||||
(`anonymisation_onefile.spec`) garde torch — ne pas builder V5 et V6 dans
|
||||
le même venv sans réinstaller les requirements.
|
||||
2. **Précache les poids OnnxTR** (P0-4) : `db_resnet50` + `crnn_vgg16_bn`
|
||||
téléchargés explicitement avant PyInstaller (la spec raise s'ils manquent).
|
||||
Le build ne dépend plus du cache résiduel de la machine.
|
||||
3. **Injecte la version release** (P1-7) : `yyyy.MM.dd.HHmm` calculée une fois,
|
||||
écrite dans `build_info.py` (BUILD_VERSION), `gui_v6/_build_version.py`
|
||||
(affichage GUI + télémétrie) et l'installeur (`/DAppVersion`). En dev,
|
||||
`gui_v6.__version__` retombe sur `6.0.0-dev`.
|
||||
|
||||
### Validation torch-free (à chaque build)
|
||||
|
||||
- Taille EXE mesurée et comparée au build précédent (~697 MB avec torch ;
|
||||
attendu nettement inférieur — consigner la valeur).
|
||||
- `Select-String -Path build\anonymisation_gui_v6_onefile\xref-*.html -Pattern "torch|optimum"`
|
||||
→ 0 résultat (l'arbo PyInstaller fait foi, pas le diff de la spec).
|
||||
- Smoke OCR sur PDF scanné (`ocr_used=True`) : les poids OnnxTR viennent de
|
||||
`_MEIPASS/models/onnxtr/models`, aucun téléchargement runtime.
|
||||
|
||||
## Important
|
||||
|
||||
- les utilisateurs finaux n'ont pas besoin d'installer Python
|
||||
|
||||
@@ -8,4 +8,6 @@ Lot G1 (socle) : thème, client/stockage licence, shell minimal, onglet À propo
|
||||
|
||||
__all__ = ["__version__"]
|
||||
|
||||
__version__ = "6.0.0-g1"
|
||||
from gui_v6.version import resolve_version
|
||||
|
||||
__version__ = resolve_version()
|
||||
|
||||
18
gui_v6/version.py
Normal file
18
gui_v6/version.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Résolution de la version affichée/télémesurée de la GUI V6 (P1-7, Plan 3).
|
||||
|
||||
Au build Windows, scripts/build_windows_oneclick.ps1 génère gui_v6/_build_version.py
|
||||
contenant BUILD_VERSION = "2026.MM.JJ.HHMM" (même valeur que l'AppVersion de
|
||||
l'installeur et que build_info.BUILD_VERSION). Ce fichier n'est PAS commité
|
||||
(.gitignore). En dev, repli sur DEFAULT_VERSION.
|
||||
"""
|
||||
|
||||
DEFAULT_VERSION = "6.0.0-dev"
|
||||
|
||||
|
||||
def resolve_version(default: str = DEFAULT_VERSION) -> str:
|
||||
try:
|
||||
from gui_v6._build_version import BUILD_VERSION
|
||||
except Exception:
|
||||
return default
|
||||
version = str(BUILD_VERSION).strip()
|
||||
return version if version else default
|
||||
@@ -24,6 +24,11 @@ SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
; D8 (Plan 3) : MAJ propre — ferme l'app avant remplacement de l'EXE.
|
||||
; AppMutex = gui_v6/single_instance.py:APP_MUTEX_NAME (NE PAS désynchroniser).
|
||||
AppMutex=AivanonymAnonymisationV6
|
||||
CloseApplications=yes
|
||||
RestartApplications=no
|
||||
|
||||
[Languages]
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
|
||||
@@ -266,6 +266,11 @@ Require-Path -PathValue $VenvPython -Label "Python du venv"
|
||||
|
||||
Push-Location $ProjectRoot
|
||||
try {
|
||||
# P1-7 (Plan 3) : version release unique, réutilisée par build_info.py,
|
||||
# gui_v6/_build_version.py et l'installeur Inno Setup (/DAppVersion).
|
||||
$ReleaseVersion = (Get-Date -Format "yyyy.MM.dd.HHmm")
|
||||
Write-Host "Version release : $ReleaseVersion"
|
||||
|
||||
Write-Step "Installation des dépendances de build"
|
||||
& $VenvPython -m pip install --upgrade pip setuptools wheel
|
||||
if (-not $SkipRequirements) {
|
||||
@@ -273,6 +278,28 @@ try {
|
||||
}
|
||||
& $VenvPython -m pip install pyinstaller
|
||||
|
||||
if ($GuiV6) {
|
||||
Write-Step "Purge torch/optimum du venv de build (P0-3, GUI V6 torch-free)"
|
||||
# optimum[onnxruntime] (requirements.txt) tire torch en dépendance cœur ;
|
||||
# la GUI V6 ne l'utilise jamais (NER = onnxruntime brut, OCR = OnnxTR).
|
||||
# La spec legacy V5 garde torch : purge limitée au flavor GUI V6.
|
||||
& $VenvPython -m pip uninstall -y torch torchvision optimum 2>$null
|
||||
& $VenvPython -c "import importlib.util,sys; sys.exit(1 if importlib.util.find_spec('torch') else 0)"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "torch encore importable dans le venv de build : purge P0-3 échouée."
|
||||
}
|
||||
Write-Host "Venv de build torch-free : OK"
|
||||
}
|
||||
|
||||
Write-Step "Précache des poids OnnxTR (P0-4)"
|
||||
# La spec PyInstaller raise FileNotFoundError si db_resnet50/crnn_vgg16_bn
|
||||
# sont absents du cache : on les télécharge explicitement au lieu de
|
||||
# dépendre du cache résiduel de la machine.
|
||||
& $VenvPython -c "from onnxtr.models import ocr_predictor; ocr_predictor(det_arch='db_resnet50', reco_arch='crnn_vgg16_bn'); print('Poids OnnxTR en cache : OK')"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Précache OnnxTR échoué (réseau ? proxy ?) : le build frozen échouerait sur les poids manquants."
|
||||
}
|
||||
|
||||
Write-Step "Génération de build_info.py"
|
||||
$commit = "local"
|
||||
$branch = "local"
|
||||
@@ -291,10 +318,21 @@ BUILD_DATE = "$buildDate"
|
||||
BUILD_COMMIT = "$commit"
|
||||
BUILD_BRANCH = "$branch"
|
||||
BUILD_FLAVOR = "$BuildFlavor"
|
||||
BUILD_VERSION = "$ReleaseVersion"
|
||||
"@
|
||||
Set-Content -Path $BuildInfoPath -Value $buildInfo -Encoding UTF8
|
||||
Write-Host "Build info : $buildDate / $branch / $commit"
|
||||
|
||||
if ($GuiV6) {
|
||||
$BuildVersionPath = Join-Path $ProjectRoot "gui_v6\_build_version.py"
|
||||
$buildVersionContent = @"
|
||||
"""Version release - généré automatiquement par build_windows_oneclick.ps1 (P1-7)."""
|
||||
BUILD_VERSION = "$ReleaseVersion"
|
||||
"@
|
||||
Set-Content -Path $BuildVersionPath -Value $buildVersionContent -Encoding UTF8
|
||||
Write-Host "gui_v6/_build_version.py : $ReleaseVersion"
|
||||
}
|
||||
|
||||
Write-Step "Nettoyage des anciens artefacts"
|
||||
foreach ($PathValue in @($BuildDir, $DistDir, $PackageDir)) {
|
||||
if (Test-Path $PathValue) {
|
||||
@@ -367,8 +405,7 @@ Build :
|
||||
$innoCompiler = Resolve-InnoCompiler
|
||||
if ($innoCompiler) {
|
||||
Write-Host "Inno Setup Compiler : $innoCompiler"
|
||||
$installerVersion = (Get-Date -Format "yyyy.MM.dd.HHmm")
|
||||
& $innoCompiler "/DAppVersion=$installerVersion" $InstallerScriptPath
|
||||
& $innoCompiler "/DAppVersion=$ReleaseVersion" $InstallerScriptPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Inno Setup a échoué avec le code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
71
tests/unit/test_build_specs_torch_free.py
Normal file
71
tests/unit/test_build_specs_torch_free.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""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, spec_name):
|
||||
"""Retourne les chaînes littérales présentes dans la section hiddenimports=[...].
|
||||
|
||||
On extrait la portion entre `hiddenimports = [` et le PREMIER `]` rencontré
|
||||
où qu'il soit (`[^\\]]*` ne gère pas l'imbrication : un `]` dans un
|
||||
commentaire au sein de la liste tronquerait le bloc — acceptable ici, les
|
||||
specs n'en contiennent pas). Cette restriction évite les faux positifs du
|
||||
bloc EXCLUDED_TORCH_STACK qui contient légitimement "torch", "optimum", etc.
|
||||
|
||||
Garde-fou : si le bloc hiddenimports est introuvable (renommage, refonte de
|
||||
la spec), on échoue BRUYAMMENT au lieu de retourner [] — sinon le test
|
||||
passerait à vide sans plus rien vérifier.
|
||||
"""
|
||||
match = re.search(r"hiddenimports\s*=\s*\[([^\]]*)\]", text, re.DOTALL)
|
||||
assert match is not None, f"bloc hiddenimports introuvable dans {spec_name}"
|
||||
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, spec_path.name)
|
||||
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."
|
||||
)
|
||||
35
tests/unit/test_gui_v6_version.py
Normal file
35
tests/unit/test_gui_v6_version.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Tests de la résolution de version GUI V6 (P1-7, Plan 3).
|
||||
|
||||
La version release (schéma 2026.MM.JJ.HHMM) est générée au build Windows dans
|
||||
gui_v6/_build_version.py (non commité). En dev, repli sur la version par défaut.
|
||||
"""
|
||||
import sys
|
||||
import types
|
||||
|
||||
from gui_v6.version import DEFAULT_VERSION, resolve_version
|
||||
|
||||
|
||||
def test_resolve_version_sans_module_build_retourne_defaut(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "gui_v6._build_version", None)
|
||||
# sys.modules[name] = None => ImportError au `from ... import`
|
||||
assert resolve_version() == DEFAULT_VERSION
|
||||
|
||||
|
||||
def test_resolve_version_avec_module_build_retourne_version_injectee(monkeypatch):
|
||||
fake = types.ModuleType("gui_v6._build_version")
|
||||
fake.BUILD_VERSION = "2026.07.02.1130"
|
||||
monkeypatch.setitem(sys.modules, "gui_v6._build_version", fake)
|
||||
assert resolve_version() == "2026.07.02.1130"
|
||||
|
||||
|
||||
def test_resolve_version_build_version_vide_retourne_defaut(monkeypatch):
|
||||
fake = types.ModuleType("gui_v6._build_version")
|
||||
fake.BUILD_VERSION = ""
|
||||
monkeypatch.setitem(sys.modules, "gui_v6._build_version", fake)
|
||||
assert resolve_version() == DEFAULT_VERSION
|
||||
|
||||
|
||||
def test_dunder_version_est_cable_sur_resolve_version():
|
||||
import gui_v6
|
||||
# En dev (pas de _build_version généré), __version__ == défaut.
|
||||
assert gui_v6.__version__ == DEFAULT_VERSION
|
||||
27
tests/unit/test_installer_iss_d8.py
Normal file
27
tests/unit/test_installer_iss_d8.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Anti-dérive D8 (Plan 3) : l'installeur GUI doit fermer l'app avant MAJ.
|
||||
|
||||
AppMutex DOIT valoir gui_v6.single_instance.APP_MUTEX_NAME (P0-7) — le commentaire
|
||||
de single_instance.py:15 exige la synchro. CloseApplications ferme l'app qui
|
||||
verrouille l'EXE pendant l'upgrade en place (AppId fixe).
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from gui_v6.single_instance import APP_MUTEX_NAME
|
||||
|
||||
ISS = Path(__file__).resolve().parents[2] / "installer" / "Anonymisation.iss"
|
||||
|
||||
|
||||
def test_appmutex_synchronise_avec_single_instance():
|
||||
text = ISS.read_text(encoding="utf-8")
|
||||
assert f"AppMutex={APP_MUTEX_NAME}" in text
|
||||
|
||||
|
||||
def test_closeapplications_actif():
|
||||
text = ISS.read_text(encoding="utf-8")
|
||||
assert "CloseApplications=yes" in text
|
||||
|
||||
|
||||
def test_appid_fixe_inchange():
|
||||
# L'upgrade en place repose sur l'AppId stable — ne jamais le régénérer.
|
||||
text = ISS.read_text(encoding="utf-8")
|
||||
assert "AppId={{6D11E4F8-26D8-4CFB-9F19-5A81E0637F56}" in text
|
||||
Reference in New Issue
Block a user