From 156956a1f027dfbf18aed4072aba25359c69cb26 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 16 Sep 2026 03:00:57 -0400 Subject: [PATCH] [FIX] test long cache : ne pas compter le refus d'un miroir en faute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verdict counted every upstream line of an already seen package as a file the cache failed to serve. A mirror that publishes the distribution under another path answers 404 to each package; the client moves to the next mirror, which the cache serves from disk. The 404 goes upstream on both VMs without anything missing, and an openSUSE campaign failed on six such lines. An upstream answer of 400 or more is now reported apart and not counted. Checked: a test for the refusal, and one where a delivered 200 still fails. --- FR --- Le verdict comptait toute ligne d'amont d'un paquet déjà vu comme un fichier que le cache n'a pas servi. Un miroir qui publie la distribution sous un autre chemin répond 404 à chaque paquet ; le client passe au miroir suivant, que le cache sert du disque. Le 404 repart à l'amont aux deux VM sans que rien ne manque, et une campagne openSUSE échouait sur six de ces lignes. Une réponse d'amont de 400 ou plus est désormais signalée à part, sans compter. Vérifié : un test du refus, et un où un 200 livré échoue toujours. Assisted-by: Claude Opus 5 --- long_test/qemu_cache.py | 25 ++++++++++++++++++++++- test/test_qemu_cache_mesure.py | 36 +++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/long_test/qemu_cache.py b/long_test/qemu_cache.py index e2d5259..b259f17 100755 --- a/long_test/qemu_cache.py +++ b/long_test/qemu_cache.py @@ -828,7 +828,24 @@ def verdict(premier, second, journal): vues1 = {l["url"] for l in p1} # Le critère : ce que les DEUX ont demandé ne doit pas être ressorti. - fautes = [l for l in p2 if l["url"] in vues1 and l.get("upstream")] + # + # Un REFUS de l'amont n'en est pas : un miroir qui range sa distribution + # sous un autre chemin répond 404 à chaque paquet, le client passe au + # 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 + ] + fautes = [ + l + for l in p2 + if l["url"] in vues1 and l.get("upstream") and l not in refus + ] # Ce que la seconde a découvert seule : légitime sur une publication # continue, montré pour que personne ne prenne un miroir qui bouge pour # une panne de cache. @@ -901,6 +918,12 @@ def verdict(premier, second, journal): ) for l in neufs[:5]: dire(f" + {l['url'].rsplit('/', 1)[-1]}", journal) + if refus: + dire( + f" ({len(refus)} refus de l'amont, non comptés : un miroir a" + " répondu par une erreur, le fichier est venu d'ailleurs)", + journal, + ) if fautes: dire("", journal) dire( diff --git a/test/test_qemu_cache_mesure.py b/test/test_qemu_cache_mesure.py index 92fdde4..e21b9b9 100644 --- a/test/test_qemu_cache_mesure.py +++ b/test/test_qemu_cache_mesure.py @@ -42,20 +42,25 @@ def charger(): QC = charger() -def ligne(url, upstream, octets=1000, issue="stored", classe="immutable"): +def ligne( + url, upstream, octets=1000, issue="stored", classe="immutable", statut=None +): """Une ligne du journal d'accès du cache. La CLASSE est ce que le filtre lit désormais : c'est le cache qui décide si un fichier est figé, et la tenir ici en dur revenait à laisser un index - se faire passer pour un paquet. + se faire passer pour un paquet. Le STATUT n'est écrit que s'il est donné. """ - return { + l = { "url": url, "upstream": upstream, "bytes": octets, "outcome": issue, "class": classe, } + if statut is not None: + l["status"] = statut + return l PAQUET_A = ( @@ -155,6 +160,31 @@ class TestVerdict(unittest.TestCase): de mesure, donc pas de succès.""" self.assertFalse(QC.verdict([ligne(PAQUET_A, True)], [], None)) + def test_un_miroir_qui_refuse_ne_fait_pas_echouer(self): + """Un miroir rangé sous un autre chemin répond 404 à chaque paquet ; le + client prend le fichier ailleurs, et le cache le sert du disque. Le + 404 repart à l'amont aux deux VM sans que rien n'ait manqué.""" + mauvais = PAQUET_A.replace("miroir.example", "autre.example/x") + premier = [ + ligne(mauvais, True, octets=153, issue="fetched", statut=404), + ligne(PAQUET_A, True, statut=200), + ] + second = [ + ligne(mauvais, True, octets=153, issue="fetched", statut=404), + ligne(PAQUET_A, False, issue="hit", statut=200), + ] + self.assertTrue( + QC.verdict(premier, second, None), + "le 404 d'un miroir a été compté comme un paquet ressorti", + ) + + def test_un_paquet_livre_deux_fois_reste_une_faute(self): + """Le refus est écarté, pas la réponse livrée : un 200 déjà vu qui + repart à l'amont fait toujours échouer.""" + premier = [ligne(PAQUET_A, True, statut=200)] + second = [ligne(PAQUET_A, True, statut=200)] + self.assertFalse(QC.verdict(premier, second, None)) + def test_le_melange(self): premier = [ligne(PAQUET_A, True), ligne(PAQUET_B, True)] second = [