From 416032fe7b75031971fead6c8203d2b60986d136 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 17 Sep 2026 07:14:52 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20test=20long=20cache=20:=20ne=20compter?= =?UTF-8?q?=20vue=20qu'une=20URL=20vraiment=20livr=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un même paquet vit sous deux chemins selon le miroir, et la clé du magasin porte le chemin : la première VM peut recevoir « 504 » sur l'un — amont jugé muet — puis être servie du disque par l'autre. Rien n'entre alors au magasin sous le premier chemin, et le téléchargement honnête de la seconde y était compté en faute, faisant échouer une mesure où le cache avait tout servi. Une URL n'est « déjà vue » que si la première VM en a obtenu les octets ; un statut absent vaut livré, les journaux d'avant ne l'écrivant pas toujours. Vérifié : trois tests, deux mutations attrapées. --- EN --- One package lives under two paths depending on the mirror, and the store key carries the path: the first VM can get « 504 » on one — upstream judged mute — then be served from disk by the other. Nothing enters the store under the first path, and the second VM's honest download was counted a fault there, failing a measurement where the cache had served everything. A URL is « already seen » only if the first VM obtained its bytes; a missing status counts as delivered, older logs not always writing it. Checked: three tests, two mutations caught. Assisted-by: Claude Opus 5 --- long_test/qemu_cache.py | 27 +++++++++++++++++-------- test/test_qemu_cache_mesure.py | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/long_test/qemu_cache.py b/long_test/qemu_cache.py index 7a08791..d8dc014 100755 --- a/long_test/qemu_cache.py +++ b/long_test/qemu_cache.py @@ -826,11 +826,28 @@ def paquets_seulement(lignes): return [l for l in lignes if l.get("class") == "immutable"] +def livre(ligne): + """La réponse a-t-elle livré le fichier ? + + Un statut absent vaut oui : les journaux d'avant ne l'écrivaient pas + toujours, et le prendre pour un échec ferait disparaître des mesures + entières. Tout ce qui n'est pas une livraison — refus d'un miroir, « 504 » + d'un amont jugé muet — ne compte ni comme faute ni comme fichier déjà vu. + """ + statut = ligne.get("status") + return not isinstance(statut, int) or 200 <= statut < 400 + + def verdict(premier, second, journal): """Le critère, puis la manchette. Rend True si le cache a servi.""" p1 = paquets_seulement(premier) p2 = paquets_seulement(second) - vues1 = {l["url"] for l in p1} + # « Déjà vue » suppose que la PREMIÈRE VM en a obtenu les octets. Un même + # paquet vit sous deux chemins selon le miroir, et la clé porte le chemin : + # la première peut recevoir « 504 » sur l'un — amont jugé muet — puis être + # servie du disque par l'autre. Le téléchargement honnête de la seconde sur + # le premier chemin n'est pas une faute : rien n'était rangé sous cette clé. + vues1 = {l["url"] for l in p1 if livre(l)} # Le critère : ce que les DEUX ont demandé ne doit pas être ressorti. # @@ -839,13 +856,7 @@ def verdict(premier, second, journal): # miroir suivant, et le cache sert celui-là du disque. Il n'a rien livré # que le cache aurait dû garder — un 404 figé masquerait le fichier publié # ensuite. Le compter en faute fait échouer une mesure où tout a été servi. - refus = [ - l - for l in p2 - if l.get("upstream") - and isinstance(l.get("status"), int) - and l["status"] >= 400 - ] + refus = [l for l in p2 if l.get("upstream") and not livre(l)] fautes = [ l for l in p2 diff --git a/test/test_qemu_cache_mesure.py b/test/test_qemu_cache_mesure.py index e21b9b9..cc1b74b 100644 --- a/test/test_qemu_cache_mesure.py +++ b/test/test_qemu_cache_mesure.py @@ -620,5 +620,42 @@ class TestLeGainSeCalculeParCondition(unittest.TestCase): self.assertNotIn("gain", texte) +class UneUrlQueLaPremiereNaJamaisObtenue(unittest.TestCase): + """Deux miroirs rangent le même paquet sous deux chemins, et la clé porte + le chemin. La première VM reçoit « 504 » sur l'un — amont jugé muet — puis + est servie du disque par l'autre ; rien n'entre au magasin sous le premier + chemin. Le téléchargement de la seconde n'y est donc pas une faute. + """ + + AUTRE = PAQUET_A.replace("miroir.example", "autre-miroir.example") + + def test_un_504_chez_la_premiere_ne_rend_pas_la_seconde_fautive(self): + premier = [ + ligne( + self.AUTRE, True, issue="offline-miss", octets=0, statut=504 + ), + ligne(PAQUET_A, False, issue="hit", statut=200), + ] + second = [ligne(self.AUTRE, True, issue="stored", statut=200)] + self.assertTrue( + QC.verdict(premier, second, None), + "un 504 de la première VM fait accuser le cache d'un fichier" + " qu'il n'a jamais eu à garder", + ) + + def test_un_fichier_livre_deux_fois_reste_une_faute(self): + premier = [ligne(PAQUET_A, True, statut=200)] + second = [ligne(PAQUET_A, True, statut=200)] + self.assertFalse(QC.verdict(premier, second, None)) + + def test_un_statut_absent_vaut_livre(self): + """Les journaux d'avant ne l'écrivaient pas toujours : le prendre pour + un échec effacerait des mesures entières.""" + self.assertTrue(QC.livre({"url": PAQUET_A})) + self.assertTrue(QC.livre({"url": PAQUET_A, "status": 206})) + self.assertFalse(QC.livre({"url": PAQUET_A, "status": 504})) + self.assertFalse(QC.livre({"url": PAQUET_A, "status": 404})) + + if __name__ == "__main__": unittest.main()