Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier, d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient presque entièrement aux chaînes coupées à la main que ruff recolle quand elles tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune valeur ne change, et les clés de traduction non plus. Vérifié : la suite unitaire reste verte après le passage, et le contrôle de syntaxe ne signale rien. --- EN --- This repository's formatter is ruff since it replaced black, which knows no target beyond py313; this pass applies its standard to the whole tree at once, so that no substantive commit has to carry style. The difference is almost entirely the hand-split strings ruff joins back when they fit on one line, and the multiple "with" it merges: no value changes, nor do the translation keys. Checked: the unit suite stays green after the pass, and the syntax check reports nothing. Assisted-by: Claude Opus 5
546 lines
21 KiB
Python
546 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""L'outil voit-il ce qu'il doit, et se tait-il sur le reste ?
|
|
|
|
Un outil de style qui crie pour rien se fait ignorer en entier : ces tests
|
|
pèsent donc les silences autant que les trouvailles. Le présent de l'indicatif
|
|
(« mesure le temps »), une version Odoo à quatre nombres, la boucle locale et
|
|
un chemin en gabarit doivent passer sans un mot.
|
|
|
|
La part qu'aucun motif ne juge — « cette phrase énonce-t-elle un fait durable
|
|
ou raconte-t-elle une journée » — n'est pas testée : elle n'est pas décidable.
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(
|
|
0, os.path.join(os.path.dirname(__file__), "..", "script", "analyse")
|
|
)
|
|
|
|
import check_comment_hygiene as hygiene # noqa: E402
|
|
|
|
OUTIL = os.path.join(
|
|
os.path.dirname(__file__),
|
|
"..",
|
|
"script",
|
|
"analyse",
|
|
"check_comment_hygiene.py",
|
|
)
|
|
|
|
|
|
def genres(trouvailles):
|
|
return {f["kind"] for f in trouvailles}
|
|
|
|
|
|
def motifs(trouvailles):
|
|
return {f["pattern"] for f in trouvailles}
|
|
|
|
|
|
class TestLesAdresses(unittest.TestCase):
|
|
"""Quatre nombres séparés par des points ne font pas une machine."""
|
|
|
|
def test_une_adresse_de_machine(self):
|
|
self.assertTrue(hygiene.adresse_de_machine("172.31.7.42"))
|
|
self.assertTrue(hygiene.adresse_de_machine("172.20.4.9"))
|
|
|
|
def test_une_version_odoo(self):
|
|
for version in ("18.0.1.3", "17.0.1.0", "12.0.2.1"):
|
|
self.assertFalse(hygiene.adresse_de_machine(version), version)
|
|
|
|
def test_la_boucle_locale_et_les_masques(self):
|
|
for valeur in ("127.0.0.1", "127.0.1.1", "0.0.0.0", "255.255.255.255"):
|
|
self.assertFalse(hygiene.adresse_de_machine(valeur), valeur)
|
|
|
|
def test_une_adresse_de_reseau(self):
|
|
"""Un dernier octet nul nomme une plage, pas un hôte."""
|
|
self.assertFalse(hygiene.adresse_de_machine("192.168.122.0"))
|
|
|
|
def test_les_blocs_documentaires(self):
|
|
self.assertFalse(hygiene.adresse_de_machine("192.0.2.5"))
|
|
self.assertFalse(hygiene.adresse_de_machine("203.0.113.9"))
|
|
|
|
def test_un_octet_hors_bornes(self):
|
|
self.assertFalse(hygiene.adresse_de_machine("999.1.1.1"))
|
|
|
|
|
|
class TestLeTemoignage(unittest.TestCase):
|
|
"""L'accent sépare le passé qui témoigne du présent qui décrit."""
|
|
|
|
def _recits(self, texte):
|
|
return hygiene.recits(texte)
|
|
|
|
def test_le_participe_passe(self):
|
|
for phrase in (
|
|
"Vécu sur une machine du parc.",
|
|
"Mesuré : trois secondes.",
|
|
"Vécu, sur la base intermédiaire.",
|
|
"Rapporté au premier essai.",
|
|
"Mesuré — deux fois de suite.",
|
|
):
|
|
self.assertTrue(self._recits(phrase), phrase)
|
|
|
|
def test_le_present_de_lindicatif(self):
|
|
"""« mesure le temps » dit ce que le code fait : rien à signaler."""
|
|
for phrase in (
|
|
"La sonde mesure le temps de réponse.",
|
|
"Le pilote signale au menu que l'étape est finie.",
|
|
"L'écran constate au démarrage que le service répond.",
|
|
):
|
|
self.assertEqual([], self._recits(phrase), phrase)
|
|
|
|
def test_une_date_absolue(self):
|
|
self.assertIn(
|
|
"date", {t[0] for t in self._recits("Relevé le 2026-08-12.")}
|
|
)
|
|
self.assertIn(
|
|
"date", {t[0] for t in self._recits("Le 24 août 2026, la VM.")}
|
|
)
|
|
|
|
def test_la_premiere_personne(self):
|
|
for phrase in (
|
|
"Ma conclusion était fausse.",
|
|
"j'avais écrit le contraire.",
|
|
):
|
|
self.assertIn(
|
|
"personne", {t[0] for t in self._recits(phrase)}, phrase
|
|
)
|
|
|
|
def test_le_nom_releve_nest_pas_le_verbe(self):
|
|
"""« dans le relevé du serveur » nomme une chose, ne témoigne pas."""
|
|
for phrase in (
|
|
"Dans le relevé du serveur, la valeur est vide.",
|
|
"Le constaté au démarrage sert de référence.",
|
|
):
|
|
self.assertEqual([], self._recits(phrase), phrase)
|
|
|
|
def test_hier_ne_se_trouve_pas_dans_hierarchie(self):
|
|
for phrase in (
|
|
"La hiérarchie des modèles.",
|
|
"The hierarchy of models.",
|
|
):
|
|
self.assertEqual([], self._recits(phrase), phrase)
|
|
|
|
def test_reproduit_est_aussi_du_present(self):
|
|
"""Présent et participe s'écrivent pareil : le motif ne tranche pas."""
|
|
self.assertEqual([], self._recits("Le pilote reproduit la config."))
|
|
|
|
def test_toutes_les_occurrences_dun_bloc(self):
|
|
"""Un bloc qui répète le marqueur ne cache pas le reste du travail."""
|
|
trouves = self._recits("Mesuré sur la base. Puis vécu sur la copie.")
|
|
self.assertEqual(2, len({t[1].lower() for t in trouves}))
|
|
|
|
|
|
class TestLeGo(unittest.TestCase):
|
|
"""Go est entré dans le dépôt, et l'outil ne le lisait pas.
|
|
|
|
Sondé avec des motifs interdits, il restait muet sur un « .go » et sortait
|
|
0 : les commentaires du cache de téléchargement n'ont donc jamais été
|
|
relus par personne d'autre que leur auteur.
|
|
"""
|
|
|
|
def texte(self, source):
|
|
"""Le texte recollé de tous les blocs. Un bloc est un dict :
|
|
« line », « text », « lines », « offsets »."""
|
|
return " ".join(b["text"] for b in hygiene.blocs_go(source))
|
|
|
|
def test_un_commentaire_de_ligne(self):
|
|
trouve = hygiene.inspect("x.go", "// Vécu : la panne\npackage main\n")
|
|
self.assertTrue(trouve, "un commentaire Go n'est pas inspecté")
|
|
|
|
def test_une_url_en_chaine_nouvre_rien(self):
|
|
"""« https:// » porte deux barres obliques : c'est LE piège du Go."""
|
|
source = 'package main\n\nconst a = "https://vecu.example/hier"\n'
|
|
self.assertEqual(hygiene.blocs_go(source), [])
|
|
|
|
def test_une_chaine_brute_nouvre_rien(self):
|
|
"""L'accent grave délimite une chaîne où rien ne s'échappe."""
|
|
source = (
|
|
"package main\n\nconst a = `https://mesure.example/nous avons`\n"
|
|
)
|
|
self.assertEqual(hygiene.blocs_go(source), [])
|
|
|
|
def test_un_commentaire_apres_une_chaine(self):
|
|
source = (
|
|
'package main\n\nconst a = "https://x.example" // Vécu : ici\n'
|
|
)
|
|
trouve = hygiene.recits(self.texte(source))
|
|
self.assertTrue(trouve, "le commentaire qui suit une URL est perdu")
|
|
|
|
def test_un_bloc_sur_plusieurs_lignes(self):
|
|
source = (
|
|
"package main\n\n/*\nVécu : la semaine où\nnous avons vu.\n*/\n"
|
|
)
|
|
self.assertTrue(
|
|
hygiene.blocs_go(source), "un commentaire /* */ n'est pas lu"
|
|
)
|
|
texte = self.texte(source)
|
|
self.assertIn("Vécu", texte)
|
|
self.assertIn("nous avons", texte)
|
|
|
|
def test_un_bloc_sur_une_seule_ligne(self):
|
|
source = "package main\n\nvar x = 1 /* Vécu : ici */\nvar y = 2\n"
|
|
self.assertTrue(hygiene.blocs_go(source))
|
|
self.assertIn("Vécu", self.texte(source))
|
|
|
|
def test_le_code_apres_un_bloc_ferme_est_relu(self):
|
|
"""Un « // » qui suit un bloc fermé sur la même ligne compte encore."""
|
|
source = "package main\n\nvar x = 1 /* rien */ // Vécu : là\n"
|
|
self.assertIn("Vécu", self.texte(source))
|
|
|
|
def test_le_suffixe_est_balaye(self):
|
|
self.assertIn(".go", hygiene.SUFFIXES)
|
|
|
|
|
|
class TestLesIdentifiants(unittest.TestCase):
|
|
def test_un_courriel(self):
|
|
self.assertIn(
|
|
"courriel", {t[0] for t in hygiene.identifiants("a@exemple.ca")}
|
|
)
|
|
|
|
def test_le_courriel_du_proprietaire_passe(self):
|
|
self.assertEqual([], hygiene.identifiants("contact@technolibre.ca"))
|
|
|
|
def test_un_chemin_de_compte(self):
|
|
self.assertIn(
|
|
"compte",
|
|
{t[0] for t in hygiene.identifiants("/home/quelquun/git/")},
|
|
)
|
|
|
|
def test_un_chemin_en_gabarit_passe(self):
|
|
for chemin in ("/home/<utilisateur>/git/", "/home/$USER/git/"):
|
|
self.assertEqual([], hygiene.identifiants(chemin), chemin)
|
|
|
|
def test_la_liste_privee(self):
|
|
trouves = hygiene.identifiants(
|
|
"migration de AcmeCorp", termes=["acmecorp"]
|
|
)
|
|
self.assertEqual([("nom privé", "acmecorp", 13)], trouves)
|
|
|
|
def test_sans_liste_privee_rien_nest_refuse(self):
|
|
self.assertEqual([], hygiene.identifiants("migration de AcmeCorp"))
|
|
|
|
|
|
class TestCeQuiEstLu(unittest.TestCase):
|
|
"""Les commentaires et les docstrings, et rien d'autre du code."""
|
|
|
|
def test_une_docstring_de_module(self):
|
|
source = '"""Vécu sur une machine du parc."""\n\n\nX = 1\n'
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual({"récit"}, genres(trouvailles))
|
|
|
|
def test_une_docstring_de_fonction(self):
|
|
source = 'def f():\n """Mesuré sur la copie."""\n return 1\n'
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual(2, trouvailles[0]["line"])
|
|
|
|
def test_une_chaine_de_code_nest_pas_un_commentaire(self):
|
|
"""Seule la PREMIÈRE expression d'une portée est une docstring."""
|
|
source = 'def f():\n return "Vécu sur la copie"\n'
|
|
self.assertEqual([], hygiene.inspect("x.py", source=source, termes=[]))
|
|
|
|
def test_les_lignes_consecutives_forment_un_bloc(self):
|
|
"""Une phrase coupée en deux lignes reste une phrase."""
|
|
source = (
|
|
"# une raison, puis mesuré\n# sur la copie de la base\nX = 1\n"
|
|
)
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual(1, len(trouvailles))
|
|
|
|
def test_un_trou_separe_deux_blocs(self):
|
|
source = "# mesuré sur la copie\nX = 1\n# vécu sur la copie\nY = 2\n"
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual([1, 3], [f["line"] for f in trouvailles])
|
|
|
|
def test_la_ligne_pointee_est_celle_du_marqueur(self):
|
|
"""Pointer le début d'un bloc de vingt lignes ne guide personne."""
|
|
source = "# une raison\n# une autre\n# vécu sur la copie\nX = 1\n"
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual([3], [f["line"] for f in trouvailles])
|
|
|
|
def test_un_source_illisible_se_replie_sur_les_lignes(self):
|
|
"""Rendre un rapport vide dirait « propre » d'un fichier non lu."""
|
|
source = "def f(:\n # vécu sur la copie\n"
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual([2], [f["line"] for f in trouvailles])
|
|
|
|
def test_un_mot_qui_en_contient_un_autre_ne_pointe_rien(self):
|
|
"""« je » vit dans « sujet » et « projeté » : pas une trouvaille."""
|
|
source = (
|
|
"# Le sujet est la decision, pas la connexion. Avec une ABI\n"
|
|
"# injectee, rien ne bouge. Le trajet reste projete.\n"
|
|
"# Ici seulement je regarde le resultat.\n"
|
|
"X = 1\n"
|
|
)
|
|
trouvailles = hygiene.inspect("x.py", source=source, termes=[])
|
|
self.assertEqual([3], [f["line"] for f in trouvailles])
|
|
|
|
def test_un_commentaire_shell_de_fin_de_ligne(self):
|
|
source = "#!/bin/sh\nrsync -a src dst # copie vers 10.0.0.42\n"
|
|
trouvailles = hygiene.inspect("x.sh", source=source, termes=[])
|
|
self.assertEqual(
|
|
[(2, "adresse")], [(f["line"], f["pattern"]) for f in trouvailles]
|
|
)
|
|
|
|
def test_un_diese_entre_guillemets_nouvre_rien(self):
|
|
source = '#!/bin/sh\necho "# vécu sur la copie"\n'
|
|
self.assertEqual([], hygiene.inspect("x.sh", source=source, termes=[]))
|
|
|
|
def test_un_diese_colle_a_un_mot_nouvre_rien(self):
|
|
"""`${VAR#prefixe}` et une URL à ancre ne sont pas des commentaires."""
|
|
source = "#!/bin/sh\necho ${CHEMIN#vécu sur la copie}\n"
|
|
self.assertEqual([], hygiene.inspect("x.sh", source=source, termes=[]))
|
|
|
|
def test_un_script_shell(self):
|
|
source = "#!/bin/bash\n# vécu sur la copie\necho ok\n"
|
|
trouvailles = hygiene.inspect("x.sh", source=source, termes=[])
|
|
self.assertEqual([2], [f["line"] for f in trouvailles])
|
|
|
|
def test_le_shebang_nest_pas_un_commentaire(self):
|
|
self.assertEqual(
|
|
[],
|
|
hygiene.inspect(
|
|
"x.sh", source="#!/bin/bash\necho ok\n", termes=[]
|
|
),
|
|
)
|
|
|
|
|
|
class TestLePerimetre(unittest.TestCase):
|
|
def test_le_code_tiers_est_hors_perimetre(self):
|
|
for chemin in (
|
|
"script/OCA_maintainer-tools/tools/x.py",
|
|
"addons/quelque_chose/models/x.py",
|
|
".venv.erplibre/lib/x.py",
|
|
):
|
|
self.assertFalse(hygiene.a_balayer(chemin), chemin)
|
|
|
|
def test_le_code_du_depot_est_dans_le_perimetre(self):
|
|
for chemin in (
|
|
"script/todo/todo.py",
|
|
"test/test_x.py",
|
|
"long_test/x.py",
|
|
):
|
|
self.assertTrue(hygiene.a_balayer(chemin), chemin)
|
|
|
|
|
|
class TestLaLigneDeCommande(unittest.TestCase):
|
|
"""Le module peut être juste et le programme faux : on l'exécute."""
|
|
|
|
def _lancer(self, source, suffixe=".py", args=()):
|
|
with tempfile.NamedTemporaryFile(
|
|
"w", suffix=suffixe, delete=False, encoding="utf-8"
|
|
) as fh:
|
|
fh.write(source)
|
|
chemin = fh.name
|
|
try:
|
|
return subprocess.run(
|
|
[sys.executable, OUTIL, chemin, "--no-color", *args],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
finally:
|
|
os.unlink(chemin)
|
|
|
|
def test_zero_quand_il_ny_a_rien_a_signaler(self):
|
|
r = self._lancer("# la sonde mesure le temps\nX = 1\n")
|
|
self.assertEqual(0, r.returncode, r.stdout + r.stderr)
|
|
self.assertEqual("", r.stdout.strip())
|
|
|
|
def test_un_quand_il_y_a_des_trouvailles(self):
|
|
"""0 rien à signaler, 1 des trouvailles, 2 l'outil a échoué."""
|
|
r = self._lancer("# vécu sur la copie\nX = 1\n")
|
|
self.assertEqual(1, r.returncode)
|
|
self.assertIn("témoignage", r.stdout)
|
|
|
|
def test_le_json_porte_le_compte_et_les_trouvailles(self):
|
|
import json
|
|
|
|
r = self._lancer("# vécu sur la copie\nX = 1\n", args=("--json",))
|
|
rendu = json.loads(r.stdout)
|
|
self.assertEqual(1, rendu["scanned"])
|
|
self.assertEqual(1, len(rendu["findings"]))
|
|
|
|
def test_identifying_only_laisse_le_recit_dehors(self):
|
|
r = self._lancer(
|
|
"# vécu sur la copie\nX = 1\n", args=("--identifying-only",)
|
|
)
|
|
self.assertEqual(0, r.returncode)
|
|
|
|
def test_sans_chemin_il_le_dit(self):
|
|
r = subprocess.run(
|
|
[sys.executable, OUTIL], capture_output=True, text=True
|
|
)
|
|
self.assertEqual(2, r.returncode)
|
|
|
|
|
|
class TestLeHook(unittest.TestCase):
|
|
"""Le hook informe : il ne bloque jamais un commit sur du style."""
|
|
|
|
HOOK = os.path.join(
|
|
os.path.dirname(__file__), "..", "script", "git", "hooks", "pre-commit"
|
|
)
|
|
|
|
def _depot_jetable(self, contenu):
|
|
"""Un dépôt git neuf, l'outil et le hook posés où le hook les cherche."""
|
|
import shutil
|
|
|
|
racine = tempfile.mkdtemp()
|
|
self.addCleanup(shutil.rmtree, racine, True)
|
|
for relatif in ("script/analyse", "script/git/hooks"):
|
|
os.makedirs(os.path.join(racine, relatif))
|
|
shutil.copy(OUTIL, os.path.join(racine, "script", "analyse"))
|
|
shutil.copy(
|
|
os.path.join(os.path.dirname(OUTIL), "..", "lib_identifiant.py"),
|
|
os.path.join(racine, "script"),
|
|
)
|
|
shutil.copy(self.HOOK, os.path.join(racine, "script", "git", "hooks"))
|
|
chemin = os.path.join(racine, "exemple.py")
|
|
with open(chemin, "w", encoding="utf-8") as fh:
|
|
fh.write(contenu)
|
|
for commande in (
|
|
["git", "init", "-q"],
|
|
["git", "add", "exemple.py"],
|
|
):
|
|
subprocess.run(
|
|
commande, cwd=racine, check=True, capture_output=True
|
|
)
|
|
return racine
|
|
|
|
def _lancer_dans(self, racine):
|
|
return subprocess.run(
|
|
[
|
|
sys.executable,
|
|
os.path.join(racine, "script/git/hooks/pre-commit"),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=racine,
|
|
)
|
|
|
|
def test_il_rapporte_ce_qui_est_indexe(self):
|
|
racine = self._depot_jetable(
|
|
"# vécu sur la copie, en 10.0.0.42\nX = 1\n"
|
|
)
|
|
r = self._lancer_dans(racine)
|
|
self.assertEqual(0, r.returncode, r.stderr)
|
|
self.assertIn("exemple.py", r.stderr)
|
|
self.assertIn("10.0.0.42", r.stderr)
|
|
self.assertIn("PAS bloqué", r.stderr)
|
|
|
|
def test_il_se_tait_sur_un_fichier_propre(self):
|
|
racine = self._depot_jetable('"""Rend le code de sortie."""\nX = 1\n')
|
|
r = self._lancer_dans(racine)
|
|
self.assertEqual(0, r.returncode)
|
|
self.assertEqual("", r.stderr.strip())
|
|
|
|
def test_il_sort_toujours_en_zero(self):
|
|
r = subprocess.run(
|
|
[sys.executable, self.HOOK], capture_output=True, text=True
|
|
)
|
|
self.assertEqual(0, r.returncode, r.stderr)
|
|
|
|
|
|
class TestLesNomsDHote(unittest.TestCase):
|
|
"""La classe interdite qu'aucun motif ne tranche vraiment.
|
|
|
|
Un nom d'hôte NU — « rig », « la-machine-de-tests » — ne se distingue
|
|
mécaniquement ni d'un mot ordinaire ni du nom d'un logiciel : ces tests
|
|
fixent donc ce que l'outil VOIT, la forme pleinement qualifiée, et ce
|
|
qu'il continue de ne pas voir. Confondre les deux ferait croire la classe
|
|
couverte.
|
|
|
|
Les noms sont inventés et ne paraissent nulle part ailleurs dans le
|
|
dépôt, comme l'exige la règle pour l'exemple qui illustre un interdit.
|
|
"""
|
|
|
|
def _noms(self, texte):
|
|
return [extrait for _, extrait, _ in hygiene.noms_dhote(texte)]
|
|
|
|
def test_un_nom_pleinement_qualifie(self):
|
|
self.assertEqual(
|
|
self._noms("# le service tourne sur garance-01.interne.lan"),
|
|
["garance-01.interne.lan"],
|
|
)
|
|
|
|
def test_un_domaine_de_client(self):
|
|
self.assertEqual(
|
|
self._noms("# la base de airelle-conseil.ca"),
|
|
["airelle-conseil.ca"],
|
|
)
|
|
|
|
def test_le_domaine_du_proprietaire_passe(self):
|
|
"""L'en-tête de copyright est l'exception nommée par la convention."""
|
|
self.assertEqual(self._noms("# © TechnoLibre www.technolibre.ca"), [])
|
|
|
|
def test_le_domaine_de_la_licence_passe(self):
|
|
self.assertEqual(
|
|
self._noms("# License AGPL, www.gnu.org/licenses"), []
|
|
)
|
|
|
|
def test_un_domaine_de_documentation_passe(self):
|
|
"""La RFC 2606 les réserve : ils ne désignent aucune machine."""
|
|
self.assertEqual(self._noms("# par exemple vpn.example.com"), [])
|
|
self.assertEqual(self._noms("# ou bien hote.exemple.com"), [])
|
|
|
|
def test_une_url_passe(self):
|
|
"""Une URL désigne une ressource publique, non une machine du parc."""
|
|
self.assertEqual(
|
|
self._noms("# voir https://docs.airelle-conseil.ca/guide"), []
|
|
)
|
|
|
|
def test_un_attribut_pointe_ne_pointe_rien(self):
|
|
"""Le suffixe se compare à une liste fermée, sinon tout correspond."""
|
|
self.assertEqual(self._noms("# logging.info dit la version"), [])
|
|
self.assertEqual(self._noms("# asyncio.wait attend un futur"), [])
|
|
self.assertEqual(self._noms("# chemin.home est le répertoire"), [])
|
|
|
|
def test_un_fichier_ne_pointe_rien(self):
|
|
self.assertEqual(self._noms("# voir todo.py et les autres"), [])
|
|
|
|
def test_un_nom_nu_reste_invisible(self):
|
|
"""La limite, énoncée plutôt que cachée.
|
|
|
|
« rig » est un nom de machine de ce parc, et rien ne le distingue
|
|
d'un mot. L'absence de trouvaille ne prouve donc rien sur les noms —
|
|
c'est pourquoi la famille est un signal 🟡 et non une trouvaille."""
|
|
self.assertEqual(self._noms("# le calcul tourne sur rig"), [])
|
|
|
|
def test_la_famille_est_un_signal_a_relire(self):
|
|
"""Le genre décide de l'icône et de --identifying-only."""
|
|
trouvailles = hygiene.inspect(
|
|
"essai.py", source='"""Sur garance-01.interne.lan."""\n'
|
|
)
|
|
self.assertEqual(genres(trouvailles), {"nom"})
|
|
durs = [f for f in trouvailles if f["kind"] == "identifiant"]
|
|
self.assertEqual(durs, [])
|
|
|
|
def test_le_genre_sort_de_identifying_only(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
"w", suffix=".py", delete=False, encoding="utf-8"
|
|
) as fh:
|
|
fh.write('"""Sur garance-01.interne.lan."""\n')
|
|
chemin = fh.name
|
|
try:
|
|
sortie = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
OUTIL,
|
|
chemin,
|
|
"--identifying-only",
|
|
"--no-color",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
self.assertEqual(sortie.returncode, 0)
|
|
self.assertNotIn("garance-01", sortie.stdout)
|
|
finally:
|
|
os.unlink(chemin)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|