[FIX] test longtest : ne plus dépendre de la machine ni de l'horloge

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
This commit is contained in:
Mathieu Benoit 2026-09-14 14:41:19 -04:00
parent 1d891e6e33
commit d4ec22ca4a

View file

@ -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):