erplibre/test/test_i18n_duplicates.py

84 lines
2.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Une clé de traduction en double écrase la précédente, sans un mot.
Python construit un dictionnaire littéral de haut en bas : la dernière
occurrence gagne. Une clé posée deux fois avec des valeurs différentes fait
donc qu'un menu affiche la traduction d'un autre, et rien ne le signale — ni
au chargement, ni à l'exécution, ni au test qui exerce le premier menu.
C'est arrivé : « running » et « stopped » existaient déjà, et des clés
homonymes ajoutées pour un service ont été avalées par elles. Le symptôme
était un « Service : en cours » là où le fichier disait « actif ».
Le contrôle lit le fichier par son ARBRE et non par une expression
régulière : la mienne n'attrapait que la forme multiligne, et la collision
était écrite sur une seule ligne.
"""
import ast
import collections
import sys
import unittest
from pathlib import Path
RACINE = Path(__file__).resolve().parent.parent
I18N = RACINE / "script" / "todo" / "todo_i18n.py"
sys.path.insert(0, str(RACINE / "script" / "todo"))
def cles_litterales():
"""Les clés du dictionnaire TRANSLATIONS, dans l'ordre du fichier.
Par l'arbre syntaxique : une clé écrite sur une ligne et une clé écrite
sur plusieurs sont le même nœud, alors qu'elles n'ont pas la même forme
dans le texte.
"""
arbre = ast.parse(I18N.read_text(encoding="utf-8"))
for noeud in ast.walk(arbre):
if not isinstance(noeud, ast.Assign):
continue
cibles = [c.id for c in noeud.targets if isinstance(c, ast.Name)]
if "TRANSLATIONS" not in cibles:
continue
if not isinstance(noeud.value, ast.Dict):
continue
return [
k.value
for k in noeud.value.keys
if isinstance(k, ast.Constant) and isinstance(k.value, str)
]
raise AssertionError("TRANSLATIONS introuvable")
class TestClesUniques(unittest.TestCase):
def test_aucune_cle_nest_ecrite_deux_fois(self):
compte = collections.Counter(cles_litterales())
doubles = sorted(k for k, n in compte.items() if n > 1)
self.assertEqual(
doubles,
[],
"clés en double — la dernière écrase les précédentes, sans un "
f"mot : {doubles}",
)
def test_le_controle_lit_bien_quelque_chose(self):
"""Un test qui ne trouve aucune clé passerait toujours."""
self.assertGreater(len(cles_litterales()), 1000)
def test_chaque_cle_porte_les_deux_langues(self):
import todo_i18n
manques = [
k
for k, v in todo_i18n.TRANSLATIONS.items()
if not isinstance(v, dict) or "fr" not in v or "en" not in v
]
self.assertEqual(manques, [], f"traductions incomplètes : {manques}")
if __name__ == "__main__":
unittest.main()