# ============================================================ # 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 }