From d4ec22ca4a709963cd119488e3d5effb015b9901 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 14 Sep 2026 14:41:19 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20test=20longtest=20:=20ne=20plus=20d?= =?UTF-8?q?=C3=A9pendre=20de=20la=20machine=20ni=20de=20l'horloge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tests of test/test_todo_longtest.py measured their surroundings rather than the code. The descent plan shrinks with free resources by design, so demanding four levels failed on a host whose disk had filled; the test checks the invariant at the depth the host allows — each level narrower than its parent, two vCPUs at least — and skips below two levels, saying so. The dry-run test counted reports, whose names carry the second they were written in; it now requires that no report of a real descent was written. --- FR --- Deux tests de test/test_todo_longtest.py mesuraient leur environnement plutôt que le code. Le plan de descente rétrécit avec les ressources libres, c'est voulu : exiger quatre étages échouait sur un hôte au disque rempli. Le test vérifie l'invariant à la profondeur que l'hôte permet — chaque étage plus étroit que son parent, deux vCPU au moins — et se saute sous deux étages en le disant. Le test à blanc comptait des rapports dont le nom porte la seconde de leur écriture ; il exige désormais qu'aucun rapport de vraie descente ne soit écrit. Assisted-by: Claude Opus 5 --- test/test_todo_longtest.py | 78 ++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/test/test_todo_longtest.py b/test/test_todo_longtest.py index a35de96..2a7231b 100644 --- a/test/test_todo_longtest.py +++ b/test/test_todo_longtest.py @@ -82,19 +82,18 @@ class TestLEssaiABlanc(unittest.TestCase): lancer : on voit les ressources de chaque étage et les commandes, sans créer une machine.""" - @classmethod - def setUpClass(cls): - import tempfile + # La profondeur VOULUE. Elle n'est pas garantie : le plan rétrécit avec + # les ressources de la machine, et c'est le comportement à respecter. + PROFONDEUR = 4 - # HOME temporaire : la suite unitaire tourne souvent, et elle n'a pas - # à semer un rapport dans ~/.erplibre à chaque passage. - cls.maison = tempfile.mkdtemp() - cls.res = subprocess.run( + @classmethod + def _plan(cls, profondeur): + return subprocess.run( [ PYTHON, os.path.join(RACINE, "long_test/deep_proxmox.py"), "--depth", - "4", + str(profondeur), "--dry-run", ], capture_output=True, @@ -104,12 +103,46 @@ class TestLEssaiABlanc(unittest.TestCase): env=dict(os.environ, PYTHONPATH=RACINE, HOME=cls.maison), ) + @classmethod + def setUpClass(cls): + import re + import tempfile + + # HOME temporaire : la suite unitaire tourne souvent, et elle n'a pas + # à semer un rapport dans ~/.erplibre à chaque passage. + cls.maison = tempfile.mkdtemp() + cls.res = cls._plan(cls.PROFONDEUR) + + # La profondeur demandée n'est pas toujours atteignable : la RAM, le + # disque ou les cœurs de la machine qui exécute la suite la bornent, et + # le script REFUSE alors de planifier — ce qui est juste. Exiger quatre + # étages ferait de ce contrôle une mesure du disque de l'hôte plutôt + # que du code : il a échoué le jour où un cache de dépôts git a occupé + # quinze gigaoctets, sans qu'une ligne du programme ait changé. + # + # On retombe donc sur ce que la machine permet, et l'invariant se + # vérifie là. Sous deux étages il n'y a plus d'invariant à vérifier — + # aucun parent, aucun rétrécissement — et le contrôle se saute. + borne = re.search(r"atteignable (\d+)", cls.res.stdout or "") + cls.profondeur = cls.PROFONDEUR + if borne: + cls.profondeur = int(borne.group(1)) + if cls.profondeur >= 2: + cls.res = cls._plan(cls.profondeur) + @classmethod def tearDownClass(cls): import shutil shutil.rmtree(cls.maison, ignore_errors=True) + def setUp(self): + if self.profondeur < 2: + self.skipTest( + "cette machine ne planifie pas deux étages :" + f" {self.res.stdout[-200:]}" + ) + def test_it_exits_cleanly(self): self.assertEqual(self.res.returncode, 0, self.res.stderr[-800:]) @@ -127,7 +160,9 @@ class TestLEssaiABlanc(unittest.TestCase): self.res.stdout, re.M, ) - self.assertEqual([int(p[0]) for p in plan], [1, 2, 3, 4]) + self.assertEqual( + [int(p[0]) for p in plan], list(range(1, self.profondeur + 1)) + ) self.assertIn("dry-run", self.res.stdout) def test_it_shows_the_commands_it_would_send(self): @@ -184,9 +219,17 @@ class TestLEssaiABlanc(unittest.TestCase): fichiers = glob.glob( os.path.join(self.maison, ".erplibre/longtest/*.json") ) - self.assertEqual(len(fichiers), 1, fichiers) - self.assertIn("dryrun", fichiers[0]) - with open(fichiers[0], encoding="utf-8") as fh: + self.assertTrue(fichiers, "aucun rapport écrit") + # Ce qui compte est qu'AUCUN rapport de vraie descente n'ait été + # écrit, non leur nombre : la mise en place planifie deux fois — la + # profondeur demandée, puis celle que la machine permet — et le nom + # d'un rapport porte la seconde où il est écrit. Deux essais de part + # et d'autre d'une seconde laissent donc deux fichiers, un seul + # sinon, et compter mesurait l'horloge. + for chemin in fichiers: + self.assertIn("dryrun", chemin, fichiers) + recent = max(fichiers, key=os.path.getmtime) + with open(recent, encoding="utf-8") as fh: rapport = json.load(fh) self.assertTrue(rapport["dry_run"]) self.assertEqual(rapport["atteinte"], 0) @@ -209,7 +252,7 @@ class TestLEssaiABlanc(unittest.TestCase): self.res.stdout, re.M, ) - self.assertEqual(len(plan), 4, plan) + self.assertEqual(len(plan), self.profondeur, plan) etages = sorted( (int(n), int(v), int(r), int(d)) for n, v, r, d in plan ) @@ -227,9 +270,12 @@ class TestLEssaiABlanc(unittest.TestCase): self.assertGreaterEqual( parent[1], enfant[1], f"étage {parent[0]} : vCPU" ) - # Et le plus profond reçoit ce qu'un Proxmox de test demande, pas ce - # qui reste. - self.assertEqual(etages[-1][1], 2, "vCPU du plus profond") + # Et le plus profond reçoit ce qu'un Proxmox de test DEMANDE, pas ce + # qui reste : deux cœurs au minimum, jamais moins, sur un plan quelle + # que soit sa hauteur. Le chiffre exact dépend de la profondeur — au + # quatrième étage on descend à deux — et l'exiger ferait de ce contrôle + # une mesure des ressources de la machine. + self.assertGreaterEqual(etages[-1][1], 2, "vCPU du plus profond") class TestLaProfondeurParDefaut(unittest.TestCase):