Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,151 @@
# ============================================================
# Omika - Tests de scripts\Completer-Packs-Langues.ps1
# ============================================================
# AUCUN acces reseau : la recuperation des fichiers est injectee (scriptblock),
# ce qui permet de simuler un modele identique, un modele different et une
# panne reseau — les trois seuls cas qui decident du sort d'un pack.
#
# Ce que ces tests protegent :
# - le nom du depot deduit du code et du sens (une inversion produirait
# opus-mt-fr-es au lieu d'opus-mt-es-fr, donc le vocabulaire d'un AUTRE
# modele, c'est-a-dire un charabia silencieux) ;
# - le garde-fou : rien n'est ecrit quand le source.spm telecharge differe ;
# - le fait que -Verifier n'ecrive jamais.
# ============================================================
$ErrorActionPreference = 'Stop'
$scriptTeste = Join-Path (Split-Path -Parent $PSScriptRoot) 'Completer-Packs-Langues.ps1'
$tests = 0
$fails = 0
function Assert([bool]$condition, [string]$label) {
$script:tests++
if ($condition) {
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
}
else {
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
$script:fails++
}
}
# Les fonctions du script sont chargees sans executer le traitement.
. $scriptTeste
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-completion-test-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
$packs = Join-Path $bac 'packs'
try {
# --- Un pack par cas -----------------------------------------------------
foreach ($couple in @(
@{ Code = 'es'; Sens = 'versFrancais'; ContenuSpm = 'source-espagnol' },
@{ Code = 'pt'; Sens = 'versFrancais'; ContenuSpm = 'source-portugais' },
@{ Code = 'it'; Sens = 'depuisFrancais'; ContenuSpm = 'source-italien' })) {
$dossier = Join-Path (Join-Path $packs $couple.Code) $couple.Sens
$null = New-Item -ItemType Directory -Path $dossier -Force
Set-Content -LiteralPath (Join-Path $dossier 'source.spm') -Value $couple.ContenuSpm -NoNewline
Set-Content -LiteralPath (Join-Path $dossier 'target.spm') -Value 'cible' -NoNewline
Set-Content -LiteralPath (Join-Path $dossier 'encoder_model.onnx') -Value 'faux' -NoNewline
}
# Dossier technique : ce n'est pas une langue, il doit etre ignore.
$null = New-Item -ItemType Directory -Path (Join-Path $packs '.hf-cache\hub') -Force
# --- 1. Nom du depot deduit ---------------------------------------------
Assert ((Get-NomModele -Code 'es' -Sens 'versFrancais') -eq 'Helsinki-NLP/opus-mt-es-fr') 'versFrancais -> opus-mt-es-fr'
Assert ((Get-NomModele -Code 'it' -Sens 'depuisFrancais') -eq 'Helsinki-NLP/opus-mt-fr-it') 'depuisFrancais -> opus-mt-fr-it'
Assert ((Get-NomModele -Code 'ES' -Sens 'versFrancais') -eq 'Helsinki-NLP/opus-mt-es-fr') 'code mis en minuscules'
Assert ((Get-NomModele -Code 'pt-br' -Sens 'depuisFrancais') -eq 'Helsinki-NLP/opus-mt-fr-pt-br') 'code compose conserve'
# --- 2. Inventaire -------------------------------------------------------
$presents = @(Get-PacksPresents -Racine $packs)
Assert ($presents.Count -eq 3) 'trois couples (code, sens) trouves'
Assert (@($presents | Where-Object { $_.Code -eq '.hf-cache' }).Count -eq 0) 'dossier technique ignore'
$filtres = @(Get-PacksPresents -Racine $packs -CodesFiltres @('pt'))
Assert ($filtres.Count -eq 1 -and $filtres[0].Code -eq 'pt') 'filtre par code applique'
# --- 3. Faux recuperateur : es identique, pt different, it en panne ------
$appels = New-Object 'System.Collections.Generic.List[string]'
$recuperer = {
param($modele, $fichier, $destination)
$script:appels.Add(('{0}|{1}' -f $modele, $fichier))
if ($modele -like '*fr-it') { return $false } # it : panne reseau
$contenu = if ($fichier -eq 'source.spm') {
# pt : le depot deduit n'est pas celui du pack.
if ($modele -like '*pt-fr') { 'source-autre-modele' } else { 'source-espagnol' }
}
elseif ($fichier -eq 'vocab.json') { '{ "piece": 584 }' }
else { '{ "decoder_start_token_id": 74821, "eos_token_id": 0 }' }
Set-Content -LiteralPath $destination -Value $contenu -NoNewline
return $true
}
# --- 4. Verification seule : rien n'est ecrit ----------------------------
$bilanVerif = Invoke-Completion -Racine $packs -Recuperer $recuperer -SansEcrire
Assert ($bilanVerif.Packs -eq 3) 'verification : trois packs examines'
Assert ($bilanVerif.Alignes -eq 1) 'verification : un pack aligne (es)'
Assert ($bilanVerif.Ecartes -eq 1) 'verification : un pack ecarte (pt)'
Assert ($bilanVerif.Echecs -eq 1) 'verification : un echec (it)'
Assert ($bilanVerif.Ecrits -eq 0) 'verification : aucune ecriture'
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json'))) 'verification : vocab.json absent du pack aligne'
# --- 5. Ecriture reelle --------------------------------------------------
$bilan = Invoke-Completion -Racine $packs -Recuperer $recuperer
Assert ($bilan.Ecrits -eq 1) 'ecriture : un pack complete'
Assert (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json')) 'ecriture : vocab.json pose'
Assert (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\config.json')) 'ecriture : config.json pose'
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'pt\versFrancais\vocab.json'))) 'ecart : aucun fichier ecrit'
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'it\depuisFrancais\vocab.json'))) 'echec : aucun fichier ecrit'
# --- 6. Le vocabulaire n'est jamais pris pour la configuration -----------
$config = Get-Content -LiteralPath (Join-Path $packs 'es\versFrancais\config.json') -Raw
Assert ($config -like '*decoder_start_token_id*') 'config.json recopie tel quel'
$vocab = Get-Content -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json') -Raw
Assert ($vocab -like '*584*') 'vocab.json recopie tel quel'
# --- 7. Les bons fichiers ont ete demandes --------------------------------
Assert ($appels -contains 'Helsinki-NLP/opus-mt-es-fr|vocab.json') 'vocabulaire demande au bon depot (es)'
Assert ($appels -contains 'Helsinki-NLP/opus-mt-fr-it|source.spm') 'source demandee au bon depot (it)'
# --- 8. Idempotence : relancer ne casse rien -----------------------------
$bilan2 = Invoke-Completion -Racine $packs -Recuperer $recuperer
Assert ($bilan2.Ecrits -eq 1 -and $bilan2.Packs -eq 3) 'deuxieme passage : meme resultat'
# --- 9. Un pack familial (commun/) n'est pas un pack a completer ---------
# Un modele familial range ses poids dans <code>\commun\ et ne laisse qu'un
# modele.json par sens : il n'a AUCUN source.spm par sens, donc rien a
# aligner. Le completer fabriquerait sinon un [ECART] trompeur (la paire
# bilingue deduite n'existe pas) et ferait croire le pack casse.
$dossierFamille = Join-Path (Join-Path $packs 'de') 'commun'
$null = New-Item -ItemType Directory -Path $dossierFamille -Force
Set-Content -LiteralPath (Join-Path $dossierFamille 'source.spm') -Value 'source-famille' -NoNewline
Set-Content -LiteralPath (Join-Path $dossierFamille 'encoder_model.onnx') -Value 'faux' -NoNewline
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
$dossierSens = Join-Path (Join-Path $packs 'de') $sens
$null = New-Item -ItemType Directory -Path $dossierSens -Force
Set-Content -LiteralPath (Join-Path $dossierSens 'modele.json') -Value '{ "jetonCible": ">>fra<<" }' -NoNewline
}
$presentsFamille = @(Get-PacksPresents -Racine $packs)
Assert ($presentsFamille.Count -eq 3) 'pack familial : aucun couple (code, sens) a completer'
$bilanFamille = Invoke-Completion -Racine $packs -Recuperer $recuperer -SansEcrire
Assert ($bilanFamille.Packs -eq 3 -and $bilanFamille.Ecartes -eq 1 -and $bilanFamille.Echecs -eq 1) `
'pack familial : le bilan ne change pas (aucun ecart invente)'
}
finally {
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Host ''
if ($fails -eq 0) {
Write-Host ("{0} tests, 0 echec" -f $tests) -ForegroundColor Green
exit 0
}
Write-Host ("{0} tests, {1} echec(s)" -f $tests, $fails) -ForegroundColor Red
exit 1
@@ -0,0 +1,538 @@
# ============================================================
# Omika — Tests de scripts\Exporter-Packs-Langues.ps1
# ============================================================
# AUCUN acces reseau, AUCUN Python, AUCUN optimum : l'export est simule par un
# faux « optimum-cli » place dans un dossier temporaire ajoute au PATH. Ce qui
# est verifie ici est donc la logique du script, pas le contenu des modeles.
#
# Ce que ces tests protegent :
# - le nom des modeles demande a HuggingFace (un code de langue mal substitue
# produirait « opus-mt-e-fr » au lieu de « opus-mt-es-fr ») ;
# - les fichiers REELLEMENT ranges dans le pack (decoder_with_past_model.onnx
# ne doit jamais y entrer : il serait telecharge par chaque telephone) ;
# - l'idempotence (relancer ne reexporte pas) ;
# - -Simulation : le plan, sans rien ecrire.
# ============================================================
$ErrorActionPreference = 'Stop'
$scriptTeste = Join-Path (Split-Path -Parent $PSScriptRoot) 'Exporter-Packs-Langues.ps1'
$tests = 0
$fails = 0
function Assert([bool]$condition, [string]$label) {
$script:tests++
if ($condition) {
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
}
else {
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
$script:fails++
}
}
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-export-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
$shim = Join-Path $bac 'shim'
$packs = Join-Path $bac 'packs'
try {
$null = New-Item -ItemType Directory -Path $shim -Force
# --- Faux outils : reproduisent la sortie REELLE d'un export optimum -------
# optimum produit encoder_model.onnx, decoder_model.onnx,
# decoder_with_past_model.onnx (a NE PAS livrer), source.spm, target.spm et
# des fichiers de tokenizer.
@'
@echo off
set OUT=%~5
mkdir "%OUT%" 2>nul
echo fake-encoder > "%OUT%\encoder_model.onnx"
echo fake-decoder > "%OUT%\decoder_model.onnx"
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
echo fake-source > "%OUT%\source.spm"
echo fake-target > "%OUT%\target.spm"
echo { "piece": 1 } > "%OUT%\vocab.json"
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
echo fake-junk > "%OUT%\tokenizer.json"
if not "%OMIKA_TEST_HFHOME_FILE%"=="" echo %HF_HOME%> "%OMIKA_TEST_HFHOME_FILE%"
exit /b 0
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
@'
@echo off
if "%~1"=="-c" (
if not "%OMIKA_TEST_SCRIPTS_PYTHON%"=="" echo %OMIKA_TEST_SCRIPTS_PYTHON%
if not "%OMIKA_TEST_SCRIPTS_NTUSER%"=="" echo %OMIKA_TEST_SCRIPTS_NTUSER%
exit /b 0
)
echo Python 3.11.0
exit /b 0
'@ | Set-Content -LiteralPath (Join-Path $shim 'python.cmd') -Encoding ASCII
# --- 1. Syntaxe ---------------------------------------------------------
Write-Host '=== 1. Syntaxe PowerShell 5.1 ==='
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($scriptTeste, [ref]$tokens, [ref]$errors) | Out-Null
Assert ((@($errors).Count) -eq 0) 'Syntaxe : 0 erreur'
# --- 2. -Simulation : le plan, et RIEN sur le disque --------------------
Write-Host ''
Write-Host '=== 2. -Simulation ==='
$plan = (& $scriptTeste -Simulation -RacinePacks $packs 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) '-Simulation : code retour 0'
Assert ($plan -match 'Anglais') '-Simulation : la table des langues est listee'
Assert ($plan -match 'opus-mt-uk-fr' -and $plan -match 'opus-mt-ru-fr') '-Simulation : ukrainien et russe presents'
Assert ($plan -match 'Modeles a exporter\s+:\s+102') '-Simulation : 102 modeles (2 par langue, 51 langues) annonces'
Assert ($plan -match 'opus-mt-ar-fr' -and $plan -match 'opus-mt-fr-ar') '-Simulation : l''arabe est dans la table'
Assert ($plan -match 'opus-mt-tc-big') '-Simulation : la famille de repli « tc-big » est annoncee'
Assert ($plan -match 'Repli familial pour 6 langue') '-Simulation : les 6 langues a modele familial sont annoncees'
Assert ($plan -match 'opus-mt-tc-big-itc-itc') '-Simulation : le modele de famille est nomme'
Assert (-not (Test-Path -LiteralPath $packs)) '-Simulation : aucun dossier de sortie cree'
$zone = (& $scriptTeste -Simulation -Zone Est -RacinePacks $packs 6>&1 | Out-String)
Assert ($zone -match 'Langues selectionnees : 3') '-Zone Est : 3 langues (ru, uk, be)'
Assert ($zone -notmatch 'Espagnol') '-Zone Est : les autres zones sont exclues'
$codes = (& $scriptTeste -Simulation -Codes es,zzz -RacinePacks $packs 6>&1 | Out-String)
Assert ($codes -match 'code inconnu, ignore : zzz') '-Codes : un code inconnu est signale'
Assert ($codes -match 'Langues selectionnees : 1') '-Codes : seule la langue connue est retenue'
# -Codes "es,pt" (une seule chaine, entre guillemets ou via une variable) :
# piege reel — sans decoupage, la chaine entiere etait prise pour un code
# unique et le script s'arretait sur « Aucune langue selectionnee ».
$chaine = (& $scriptTeste -Simulation -Codes 'es,pt' -RacinePacks $packs 6>&1 | Out-String)
Assert ($chaine -match 'Langues selectionnees : 2') '-Codes : la liste entre guillemets est bien decoupee'
Assert ($chaine -notmatch 'code inconnu') '-Codes : aucune alerte sur une liste entre guillemets'
# --- 3. Export reel (simule) --------------------------------------------
Write-Host ''
Write-Host '=== 3. Export reel avec un faux optimum-cli ==='
$env:PATH = $shim + ';' + $env:PATH
# Cache HuggingFace : le script doit le placer a cote des packs (et non sur C:
# dans %USERPROFILE%), et le vider apres un export reussi — sinon chaque
# modele occupe deux fois sa taille.
$traceHfHome = Join-Path $bac 'hfhome.txt'
$env:OMIKA_TEST_HFHOME_FILE = $traceHfHome
$cacheFaux = Join-Path (Join-Path $packs '.hf-cache') 'hub\models--Helsinki-NLP--opus-mt-es-fr'
$null = New-Item -ItemType Directory -Path $cacheFaux -Force
[System.IO.File]::WriteAllBytes((Join-Path $cacheFaux 'blob-pytorch'), (New-Object byte[] 2048))
$env:HF_HOME = ''
$sortie = (& $scriptTeste -SansVerifierHuggingFace -Codes es,pt -RacinePacks $packs `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Export : code retour 0'
Assert (Test-Path -LiteralPath $traceHfHome) 'Cache HuggingFace : HF_HOME est transmis a l''export'
$hfTransmis = (Get-Content -LiteralPath $traceHfHome -Raw).Trim()
Assert ($hfTransmis -eq (Join-Path $packs '.hf-cache')) `
('Cache HuggingFace : place a cote des packs ({0})' -f $hfTransmis)
Assert (-not (Test-Path -LiteralPath $cacheFaux)) 'Cache HuggingFace : la copie du modele est supprimee apres export'
Assert ($sortie -match 'Cache HuggingFace libere') 'Cache HuggingFace : l''espace libere est annonce'
# Le nom du modele : le bug « opus-mt-e-fr » venait du deroulement PowerShell
# d'un tableau a un seul element ('es'[0] = 'e').
Assert ($sortie -match 'Helsinki-NLP/opus-mt-es-fr') 'Export : modele « opus-mt-es-fr » demande'
Assert ($sortie -match 'Helsinki-NLP/opus-mt-fr-es') 'Export : modele « opus-mt-fr-es » demande'
Assert ($sortie -notmatch 'opus-mt-e-fr') 'Export : aucun code de langue tronque en un caractere'
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
$dossier = Join-Path (Join-Path $packs 'es') $sens
Assert (Test-Path -LiteralPath (Join-Path $dossier 'encoder_model.onnx')) ("es/{0} : modele principal present" -f $sens)
Assert (Test-Path -LiteralPath (Join-Path $dossier 'decoder_model.onnx')) ("es/{0} : decodeur separe present" -f $sens)
Assert (Test-Path -LiteralPath (Join-Path $dossier 'source.spm')) ("es/{0} : source.spm present" -f $sens)
Assert (Test-Path -LiteralPath (Join-Path $dossier 'target.spm')) ("es/{0} : target.spm present" -f $sens)
# Le moteur a besoin de ces deux referentiels : sans eux il envoie au
# modele les identifiants du .spm, qui ne sont pas les siens (mesure :
# ▁Ho = 5293 cote .spm contre 584 cote modele) — traduction fausse et
# silencieuse. Les oublier ici reproduirait exactement ce defaut.
Assert (Test-Path -LiteralPath (Join-Path $dossier 'vocab.json')) ("es/{0} : vocab.json present" -f $sens)
Assert (Test-Path -LiteralPath (Join-Path $dossier 'config.json')) ("es/{0} : config.json present" -f $sens)
}
Assert ((@(Get-ChildItem -LiteralPath $packs -Recurse -Filter '*with_past*')).Count -eq 0) `
'Export : decoder_with_past_model.onnx JAMAIS livre'
Assert ((@(Get-ChildItem -LiteralPath $packs -Recurse -Filter 'tokenizer.json')).Count -eq 0) `
'Export : fichiers de tokenizer non livres'
Assert ((@(Get-ChildItem -LiteralPath $packs -Directory -Filter '.tmp-*')).Count -eq 0) `
'Export : dossiers temporaires nettoyes'
# --- 4. Idempotence et -Forcer ------------------------------------------
Write-Host ''
Write-Host '=== 4. Idempotence ==='
$sortie2 = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($sortie2 -match 'deja present') 'Idempotence : un pack deja present est ignore'
Assert ($sortie2 -notmatch 'export : Helsinki') 'Idempotence : aucun export relance'
$sortie3 = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs -Forcer `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($sortie3 -match 'export : Helsinki') '-Forcer : l''export est relance'
# -GarderCacheHuggingFace : on conserve la copie telechargee (utile pour
# enchainer plusieurs -Forcer sans retelecharger 300 Mo a chaque fois).
$cacheConserve = Join-Path (Join-Path $packs '.hf-cache') 'hub\models--Helsinki-NLP--opus-mt-fr-es'
$null = New-Item -ItemType Directory -Path $cacheConserve -Force
[System.IO.File]::WriteAllBytes((Join-Path $cacheConserve 'blob-pytorch'), (New-Object byte[] 1024))
$null = & $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs -Forcer -GarderCacheHuggingFace `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1
Assert (Test-Path -LiteralPath $cacheConserve) '-GarderCacheHuggingFace : le cache est conserve'
# --- 4bis. Langue a moitie faite : ne pas repayer le sens deja la --------
# Panne reelle : -InclureDirectionsPartielles sert a completer une langue qui
# n'a qu'un sens. Comme le test d'idempotence porte sur la LANGUE ENTIERE, la
# version precedente reexportait le sens deja present : 11 langues a moitie
# faites = 11 telechargements de 500 Mo inutiles.
Write-Host ''
Write-Host '=== 4bis. Sens deja present dans une langue incomplete ==='
$packsPartiel = Join-Path $bac 'packs-partiel'
$sensDejaLa = Join-Path $packsPartiel 'es\versFrancais'
$null = New-Item -ItemType Directory -Path $sensDejaLa -Force
foreach ($nom in @('encoder_model.onnx', 'decoder_model.onnx', 'source.spm', 'target.spm')) {
[System.IO.File]::WriteAllText((Join-Path $sensDejaLa $nom), 'MARQUEUR-INTACT')
}
$sortiePartiel = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsPartiel `
-InclureDirectionsPartielles -OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Partiel : code retour 0'
Assert ($sortiePartiel -match 'versFrancais : deja present, ignore') `
'Partiel : le sens deja present est ignore, pas reexporte'
Assert ((Get-Content -LiteralPath (Join-Path $sensDejaLa 'encoder_model.onnx') -Raw).Trim() -eq 'MARQUEUR-INTACT') `
'Partiel : le sens deja present n''est pas ecrase'
Assert (Test-Path -LiteralPath (Join-Path $packsPartiel 'es\depuisFrancais\encoder_model.onnx')) `
'Partiel : le sens manquant est bien construit'
# --- 5. Rapport ---------------------------------------------------------
Write-Host ''
Write-Host '=== 5. Rapport ==='
$rapport = Join-Path $packs 'RAPPORT-LANGUES.md'
Assert (Test-Path -LiteralPath $rapport) 'Rapport : RAPPORT-LANGUES.md ecrit'
$contenu = Get-Content -LiteralPath $rapport -Raw -Encoding UTF8
Assert ($contenu -match '\| es \| Espagnol') 'Rapport : la langue exportee y figure'
Assert ($contenu -match 'Paires indisponibles') 'Rapport : section des paires indisponibles presente'
Assert ($contenu -match 'New-OmikaLanguesManifeste') 'Rapport : rappelle la generation du manifeste'
# --- 6. optimum-cli hors PATH (Python du Microsoft Store) ---------------
# Cas reel : « pip install optimum » reussit, mais pip range le programme
# dans un dossier utilisateur qui n'est PAS dans le PATH. Toutes les fenetres
# repondent alors « optimum-cli introuvable » et le script s'arrete sans rien
# faire — l'utilisateur croit que le script ne fonctionne pas. Il doit donc
# aller le chercher la ou pip l'ecrit.
Write-Host ''
Write-Host '=== 6. optimum-cli hors PATH ==='
$scriptsPython = Join-Path $bac 'scripts-python'
$null = New-Item -ItemType Directory -Path $scriptsPython -Force
Copy-Item -LiteralPath (Join-Path $shim 'optimum-cli.cmd') `
-Destination (Join-Path $scriptsPython 'optimum-cli.cmd') -Force
$pathSansShim = (@($env:PATH -split ';' | Where-Object { $_ -and $_ -ne $shim }) -join ';')
$env:PATH = $pathSansShim
$env:OMIKA_TEST_SCRIPTS_PYTHON = $scriptsPython
$pythonHorsPath = Join-Path $shim 'python.cmd'
$packsHorsPath = Join-Path $bac 'packs-horspath'
$sortieHorsPath = (& $scriptTeste -SansVerifierHuggingFace -Codes es `
-RacinePacks $packsHorsPath -Python $pythonHorsPath 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Hors PATH : le script aboutit quand meme'
Assert ($sortieHorsPath -match 'trouve hors PATH') 'Hors PATH : la recherche est annoncee'
Assert (Test-Path -LiteralPath (Join-Path $packsHorsPath 'es\versFrancais\encoder_model.onnx')) `
'Hors PATH : le pack est construit malgre tout'
# Environnement virtuel (venv) : c'est la solution quand l'installation bute
# sur la limite de 260 caracteres de Windows. Le Python du venv annonce
# D'ABORD son propre dossier de scripts, PUIS le dossier utilisateur. L'outil
# peut n'etre que dans le second : le script doit balayer les deux, sinon il
# conclut « introuvable » alors que l'installation est bonne.
$venvVide = Join-Path $bac 'venv-vide'
$null = New-Item -ItemType Directory -Path $venvVide -Force
$env:OMIKA_TEST_SCRIPTS_PYTHON = $venvVide
$env:OMIKA_TEST_SCRIPTS_NTUSER = $scriptsPython
$packsVenv = Join-Path $bac 'packs-venv'
$sortieVenv = (& $scriptTeste -SansVerifierHuggingFace -Codes pt `
-RacinePacks $packsVenv -Python $pythonHorsPath 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Venv : l''outil est trouve dans le SECOND dossier annonce'
Assert (Test-Path -LiteralPath (Join-Path $packsVenv 'pt\versFrancais\encoder_model.onnx')) `
'Venv : le pack est construit'
# Outil reellement absent : le message doit dire QUOI installer ET OU le
# chercher, sinon l'utilisateur reinstalle en boucle sans comprendre.
$scriptsVides = Join-Path $bac 'scripts-vides'
$null = New-Item -ItemType Directory -Path $scriptsVides -Force
$env:OMIKA_TEST_SCRIPTS_PYTHON = $scriptsVides
$env:OMIKA_TEST_SCRIPTS_NTUSER = $scriptsVides
$absent = (& $scriptTeste -Codes es -RacinePacks (Join-Path $bac 'packs-absent') `
-Python $pythonHorsPath 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 1) 'Outil absent : code retour 1'
Assert ($absent -match 'pip install') 'Outil absent : la commande d''installation est indiquee'
Assert ($absent -match 'pas dans le PATH') 'Outil absent : le piege du PATH est explique'
Assert ($absent -notmatch 'Test-Path') 'Outil absent : aucune erreur interne affichee'
$env:PATH = $shim + ';' + $pathSansShim
# --- 7. -SansExporter : l'inventaire des paires -------------------------
# Repond a « quelles langues sont manquantes ? » SANS telecharger 30 Go. Il
# ne doit donc exiger NI Python NI optimum : la question se pose AVANT
# d'installer les outils.
Write-Host ''
Write-Host '=== 7. -SansExporter (inventaire) ==='
$contradiction = (& $scriptTeste -SansExporter -SansVerifierHuggingFace -Codes es `
-RacinePacks $packs 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 1) '-SansExporter : la combinaison contradictoire est refusee'
Assert ($contradiction -match 'a besoin de la verification') '-SansExporter : la raison est expliquee'
# Langue deja presente : aucun reseau, aucun export, rapport ecrit.
$null = (& $scriptTeste -SansExporter -Codes es -RacinePacks $packs 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) '-SansExporter : aboutit sans Python ni optimum'
$rapportInventaire = Get-Content -LiteralPath (Join-Path $packs 'RAPPORT-LANGUES.md') -Raw -Encoding UTF8
Assert ($rapportInventaire -match 'inventaire') '-SansExporter : le rapport annonce le mode inventaire'
Assert ($rapportInventaire -match 'Paires disponibles') '-SansExporter : la section est renommee'
# --- 8. Interprétation des réponses HuggingFace --------------------------
# Mesure sur l'export des 50 langues : HuggingFace répond 401 (JAMAIS 404)
# pour un dépôt absent. Un « 401 » seul étant ambigu, c'est le CORPS de la
# réponse qui tranche — une erreur de lecture ferait écrire au rapport que
# des langues livrables n'existent pas. Fonction pure : testable sans réseau.
Write-Host ''
Write-Host '=== 8. Verdict HuggingFace ==='
$texteScript = Get-Content -LiteralPath $scriptTeste -Raw -Encoding UTF8
$definition = [regex]::Match($texteScript, '(?s)function Get-VerdictEchecHuggingFace \{.*?\r?\n\}').Value
Assert ($definition.Length -gt 0) 'Verdict : la fonction est extractible du script'
if ($definition.Length -gt 0) {
. ([scriptblock]::Create($definition))
$vAbsent = Get-VerdictEchecHuggingFace -Code 401 -Corps '{"error":"Repository not found"}' -MessageErreur 'x'
Assert (-not $vAbsent.Existe -and -not $vAbsent.Injoignable) 'Verdict : 401 « Repository not found » = paire inexistante'
Assert ($vAbsent.Detail -match 'depot introuvable') 'Verdict : le motif exact est inscrit au rapport'
$v404 = Get-VerdictEchecHuggingFace -Code 404 -Corps '' -MessageErreur 'x'
Assert (-not $v404.Existe -and -not $v404.Injoignable) 'Verdict : 404 = paire inexistante'
# Mesure du 16/10, meme client, meme instant : un depot EXISTANT repond
# 200 (opus-mt-es-fr) et un depot absent repond 401, sur l'API COMME sur
# la page publique. Un 401 inexplique est donc traite comme une absence,
# mais le rapport dit sur quoi il se fonde — le temoin 200.
$vDoute = Get-VerdictEchecHuggingFace -Code 401 -Corps '{"error":"Invalid credentials"}' -MessageErreur 'x'
Assert (-not $vDoute.Existe -and -not $vDoute.Injoignable) `
'Verdict : 401 sans « not found » = paire non publiee'
Assert ($vDoute.Detail -match 'repond 200') 'Verdict : le rapport cite le temoin qui justifie le 401'
$vQuota = Get-VerdictEchecHuggingFace -Code 429 -Corps '' -MessageErreur 'x'
Assert ($vQuota.Injoignable) 'Verdict : 429 (quota) n''est PAS une absence'
$vServeur = Get-VerdictEchecHuggingFace -Code 503 -Corps '' -MessageErreur 'x'
Assert ($vServeur.Injoignable) 'Verdict : 503 n''est PAS une absence'
$vReseau = Get-VerdictEchecHuggingFace -Code 0 -Corps '' -MessageErreur 'connexion fermee'
Assert ($vReseau.Injoignable -and $vReseau.Detail -match 'connexion') 'Verdict : panne reseau = « on ne sait pas »'
}
# --- 9. Un avertissement sur stderr ne doit JAMAIS tuer un export --------
# Panne reelle : optimum-cli ecrit un UserWarning de huggingface_hub sur
# stderr. Lance avec « 2>&1 | Tee-Object » (naturel pour garder une trace sur
# plusieurs heures), le script transformait cette ligne en erreur FATALE
# ($ErrorActionPreference = 'Stop') et tuait les 11 exports juste apres le
# config.json : 0 octet telecharge, et un rapport annoncant 11 echecs qui
# n'en etaient pas. Seul le code de sortie doit juger un export.
Write-Host ''
Write-Host '=== 9. Avertissement stderr pendant l''export ==='
@'
@echo off
set OUT=%~5
mkdir "%OUT%" 2>nul
echo UserWarning: the cache-system uses symlinks >&2
echo fake-encoder > "%OUT%\encoder_model.onnx"
echo fake-decoder > "%OUT%\decoder_model.onnx"
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
echo fake-source > "%OUT%\source.spm"
echo fake-target > "%OUT%\target.spm"
echo { "piece": 1 } > "%OUT%\vocab.json"
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
exit /b 0
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
$packsBruyants = Join-Path $bac 'packs-stderr'
# *>&1 : TOUS les flux, erreur comprise — c'est exactement la redirection qui
# rendait la panne possible (« 2>&1 | Tee-Object » pour garder une trace).
$sortieBruyante = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsBruyants `
-OptimumCli 'optimum-cli' -Python 'python' *>&1 | Out-String)
$sensBruyant = Join-Path $packsBruyants 'es\versFrancais'
Assert ($LASTEXITCODE -eq 0) 'stderr : l''export aboutit malgre l''avertissement (code 0)'
Assert (Test-Path -LiteralPath (Join-Path $sensBruyant 'encoder_model.onnx')) `
'stderr : le pack est ecrit malgre l''avertissement'
# 6 fichiers : encoder + decoder + source.spm + target.spm + vocab.json +
# config.json. Les deux derniers sont les referentiels du modele (identifiants
# et conventions du decodeur), pas des poids.
Assert ($sortieBruyante -match 'versFrancais : 6 fichier') `
'stderr : le sens est compte comme construit, pas comme un echec'
# --- 10. Le catalogue : la preuve d'existence ---------------------------
# Troisieme et derniere evolution, apres mesures reelles : un depot absent
# repond 401 sur l'API, la page publique s'est mise a repondre 401 elle
# aussi, alors que le meme client obtenait 200 sur un depot existant au meme
# instant. Plus aucun code d'erreur ne discrimine. On lit donc LA LISTE
# publique des depots (une requete, toujours 200) : l'existence devient une
# egalite de chaine, et « absent » devient une preuve.
Write-Host ''
Write-Host '=== 10. Catalogue Helsinki-NLP ==='
$definitionCatalogue = [regex]::Match($texteScript, '(?s)function Get-CatalogueHelsinkiNLP \{.*?\r?\n\}').Value
Assert ($definitionCatalogue.Length -gt 0) 'Catalogue : la fonction est extractible du script'
Assert ($definitionCatalogue -match 'api/models\?author=') 'Catalogue : lit la liste publique des depots'
Assert ($definitionCatalogue -match 'Headers\[' -and $definitionCatalogue -match 'next') `
'Catalogue : suit la pagination annoncee par l''entete Link'
# Garde-fou mesure : une reponse a 2 entrees a fait declarer 66 modeles
# absents, dont 11 presents sur le disque. Un catalogue trop court est rejete.
Assert ($definitionCatalogue -match 'Count -lt 100') `
'Catalogue : une reponse tronquee est refusee (jamais « absent » sur 2 entrees)'
$definitionSonde = [regex]::Match($texteScript, '(?s)function Test-ModeleHuggingFace \{.*?\r?\n\}').Value
Assert ($definitionSonde.Length -gt 0) 'Catalogue : la sonde est extractible du script'
if ($definitionSonde.Length -gt 0) {
. ([scriptblock]::Create($definitionSonde))
$tableDepots = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
$null = $tableDepots.Add('Helsinki-NLP/opus-mt-es-fr')
$vPresent = Test-ModeleHuggingFace -Modele 'Helsinki-NLP/opus-mt-es-fr' -Catalogue $tableDepots
Assert ($vPresent.Existe -and -not $vPresent.Injoignable) 'Catalogue : un depot present est reconnu'
$vAbsent = Test-ModeleHuggingFace -Modele 'Helsinki-NLP/opus-mt-fr-nl' -Catalogue $tableDepots
Assert (-not $vAbsent.Existe -and -not $vAbsent.Injoignable) `
'Catalogue : un depot absent est une PREUVE, pas un doute'
Assert ($vAbsent.Detail -match 'catalogue') 'Catalogue : le rapport cite la source du verdict'
$vCasse = Test-ModeleHuggingFace -Modele 'helsinki-nlp/OPUS-MT-ES-FR' -Catalogue $tableDepots
Assert ($vCasse.Existe) 'Catalogue : la comparaison ignore la casse'
}
# --- 11. Repli de famille a l'export -------------------------------------
# Certaines langues (georgien, armenien, azari…) ne sont couvertes que par
# « opus-mt-tc-big ». En mode verifie le catalogue tranche ; sans
# verification, c'est l'export qui doit essayer la petite famille PUIS la
# « tc-big », et garder la premiere qui aboutit. Avant, il n'essayait que la
# petite : ces langues etaient declarées en echec sans avoir ete tentees.
Write-Host ''
Write-Host '=== 11. Repli opus-mt -> tc-big a l''export ==='
@'
@echo off
set MODELE=%~4
set OUT=%~5
echo %MODELE% | findstr /C:"tc-big" >nul
if errorlevel 1 (
echo Error: 401 Repository not found 1>&2
exit /b 1
)
mkdir "%OUT%" 2>nul
echo fake-encoder > "%OUT%\encoder_model.onnx"
echo fake-decoder > "%OUT%\decoder_model.onnx"
echo fake-source > "%OUT%\source.spm"
echo fake-target > "%OUT%\target.spm"
echo { "piece": 1 } > "%OUT%\vocab.json"
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
exit /b 0
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
$packsTcBig = Join-Path $bac 'packs-tcbig'
$sortieTcBig = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsTcBig `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'tc-big : le run aboutit'
Assert ($sortieTcBig -match 'opus-mt-es-fr indisponible') 'tc-big : la famille standard est essayee d''abord'
Assert ($sortieTcBig -match 'opus-mt-tc-big-es-fr') 'tc-big : la famille « tc-big » est essayee ensuite'
Assert (Test-Path -LiteralPath (Join-Path $packsTcBig 'es\versFrancais\encoder_model.onnx')) `
'tc-big : le pack est construit avec le modele de repli'
# --- 12. Modele familial : un export, deux sens, poids partages ----------
# pt et gl n'ont AUCUNE paire bilingue ; it n'en a qu'une. Le modele de
# famille (opus-mt-tc-big-itc-itc) les couvre dans les deux sens via un jeton
# de cible. Ses 1,15 Go doivent etre ecrits UNE seule fois (commun/) : les
# dupliquer par sens doublerait ce que telecharge chaque telephone, ce que
# l'utilisateur refuse explicitement.
Write-Host ''
Write-Host '=== 12. Modele familial (commun/ + modele.json par sens) ==='
@'
@echo off
set MODELE=%~4
set OUT=%~5
echo %MODELE% | findstr /C:"tc-big-itc-itc" >nul
if errorlevel 1 (
echo Error: 401 Repository not found 1>&2
exit /b 1
)
mkdir "%OUT%" 2>nul
echo fake-encoder > "%OUT%\encoder_model.onnx"
echo fake-decoder > "%OUT%\decoder_model.onnx"
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
echo fake-source > "%OUT%\source.spm"
echo fake-target > "%OUT%\target.spm"
echo { "piece": 1 } > "%OUT%\vocab.json"
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
exit /b 0
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
$env:HF_HOME = ''
$packsFamille = Join-Path $bac 'packs-famille'
$sortieFamille = (& $scriptTeste -SansVerifierHuggingFace -Codes pt -RacinePacks $packsFamille `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($LASTEXITCODE -eq 0) 'Familial : le run aboutit (code 0)'
Assert ($sortieFamille -match 'opus-mt-tc-big-itc-itc') 'Familial : le modele de famille est employe'
Assert ($sortieFamille -match 'aucune paire bilingue exploitable') 'Familial : le repli est annonce'
$commun = Join-Path $packsFamille 'pt\commun'
foreach ($nom in @('encoder_model.onnx', 'decoder_model.onnx', 'source.spm', 'target.spm', 'vocab.json', 'config.json')) {
Assert (Test-Path -LiteralPath (Join-Path $commun $nom)) ("Familial : {0} present dans commun/" -f $nom)
}
Assert ((@(Get-ChildItem -LiteralPath $packsFamille -Recurse -Filter '*with_past*')).Count -eq 0) `
'Familial : decoder_with_past_model.onnx jamais livre'
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
$dossierSens = Join-Path $packsFamille ('pt\' + $sens)
Assert (Test-Path -LiteralPath (Join-Path $dossierSens 'modele.json')) ("Familial : pt/{0}/modele.json present" -f $sens)
Assert ((@(Get-ChildItem -LiteralPath $dossierSens -Filter '*.onnx')).Count -eq 0) `
("Familial : pt/{0} ne duplique AUCUN poids" -f $sens)
}
$metaVers = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\versFrancais\modele.json') -Raw | ConvertFrom-Json
$metaDepuis = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\depuisFrancais\modele.json') -Raw | ConvertFrom-Json
Assert ($metaVers.jetonCible -eq '>>fra<<') 'Familial : jeton de l''aller = >>fra<<'
Assert ($metaDepuis.jetonCible -eq '>>por<<') 'Familial : jeton du retour = >>por<<'
Assert ($metaVers.modeleSource -eq 'Helsinki-NLP/opus-mt-tc-big-itc-itc') 'Familial : le depot d''origine est inscrit'
Assert ($metaVers.famille -eq 'itc') 'Familial : la famille est inscrite'
Assert ($metaVers.version -eq 1) 'Familial : version de metadonnees = 1'
# Le jeton doit rester LISIBLE : ConvertTo-Json l'ecrirait \u003e\u003efra\u003c\u003c,
# et c'est exactement la valeur qu'un humain doit pouvoir relire au diagnostic.
$brut = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\versFrancais\modele.json') -Raw
Assert ($brut -match '"jetonCible": ">>fra<<"') 'Familial : le jeton est ecrit en clair (pas en \u003e)'
# Reexport : 1,15 Go ne doivent pas etre repayes a chaque relance.
$sortieFamille2 = (& $scriptTeste -SansVerifierHuggingFace -Codes pt -RacinePacks $packsFamille `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
Assert ($sortieFamille2 -match 'familial\), ignore') 'Familial : un pack familial deja present est ignore'
Assert ($sortieFamille2 -notmatch 'export : Helsinki') 'Familial : aucun export relance'
# Une langue NON couverte (uk) ne doit pas se voir inventer un modele familial.
$packsSansFamille = Join-Path $bac 'packs-sans-famille'
$null = & $scriptTeste -SansVerifierHuggingFace -Codes uk -RacinePacks $packsSansFamille `
-OptimumCli 'optimum-cli' -Python 'python' 6>&1
Assert ($LASTEXITCODE -eq 1) 'Familial : une langue non couverte echoue toujours (code 1)'
Assert (-not (Test-Path -LiteralPath (Join-Path $packsSansFamille 'uk\commun'))) `
'Familial : aucune commun/ inventee pour une langue non couverte'
# Quand les deux paires bilingues existent (es), le familial ne doit PAS etre
# employe : il est deux fois plus lourd que les deux bilingues reunis.
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'es\commun'))) `
'Familial : pas de commun/ quand les deux paires bilingues existent'
}
finally {
if (Test-Path -LiteralPath $bac) {
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
}
}
Write-Host ''
Write-Host ("=== RESULTAT : {0} tests, {1} echec(s) ===" -f $tests, $fails)
if ($fails -gt 0) { exit 1 } else { exit 0 }