Claude commited on
Commit
fca122a
·
unverified ·
1 Parent(s): a4a8458

test(ci): rendre test_lifespan_starts_purge_task tolérant aux runners lents

Browse files

``test_lifespan_starts_purge_task`` utilisait ``time.sleep(0.05)``
fixe après ``TestClient(app).__enter__()`` pour attendre que la
tâche asyncio soit schedulée. Cette marge de 50 ms peut être trop
courte sur les runners GitHub Actions lents — particulièrement
Windows et macOS où l'event loop asyncio met parfois > 100 ms à
exécuter la première itération d'une tâche freshly-created.

Remplacé par un ``threading.Event`` :

- ``_fake_purge_task`` ``set()`` l'event dès qu'elle commence.
- Le test polle l'event avec slots de 10 ms jusqu'à 2 s.
- Assertion sur ``started_event.is_set()`` au lieu d'un dict mutable.

Marge x 40 sans coût en cas de succès (l'event est set quasi-
instantanément, le polling s'arrête immédiatement).

https://claude.ai/code/session_01ArfZ8kcgv7Cyda7VbJVmpn

tests/security/test_phase1_post_rewrite_wiring.py CHANGED
@@ -946,16 +946,24 @@ class TestUploadPurgeTaskWired:
946
  def test_lifespan_starts_purge_task(self, monkeypatch) -> None:
947
  """Au démarrage de l'app FastAPI, un ``asyncio.create_task`` doit
948
  emballer ``upload_purge_task``. On patch la fonction pour
949
- l'observer puis on enclenche le lifespan."""
 
 
 
 
 
 
 
 
950
  from fastapi.testclient import TestClient
951
 
952
- observed: dict = {"started": False, "uploads_root": None}
 
953
 
954
  async def _fake_purge_task(uploads_root):
955
- observed["started"] = True
956
  observed["uploads_root"] = uploads_root
 
957
  # Boucle infinie minimale — annulée au shutdown.
958
- import asyncio
959
  try:
960
  while True:
961
  await asyncio.sleep(3600)
@@ -972,13 +980,15 @@ class TestUploadPurgeTaskWired:
972
  from picarones.interfaces.web.app import app
973
 
974
  with TestClient(app):
975
- # Le lifespan a démarré ; la tâche tourne en arrière-plan.
976
- # On laisse à asyncio le temps de la lancer.
977
- import time
978
- time.sleep(0.05)
979
-
980
- assert observed["started"] is True, (
981
- "upload_purge_task aurait dû être démarrée par le lifespan"
 
 
982
  )
983
 
984
  def test_purge_protects_active_corpus(self, tmp_path: Path) -> None:
 
946
  def test_lifespan_starts_purge_task(self, monkeypatch) -> None:
947
  """Au démarrage de l'app FastAPI, un ``asyncio.create_task`` doit
948
  emballer ``upload_purge_task``. On patch la fonction pour
949
+ l'observer puis on enclenche le lifespan.
950
+
951
+ Polling actif au lieu de ``time.sleep`` fixe : robuste aux
952
+ runners CI lents (Windows en particulier peut prendre > 100 ms
953
+ pour scheduler la première tâche asyncio)."""
954
+ import asyncio
955
+ import threading
956
+ import time
957
+
958
  from fastapi.testclient import TestClient
959
 
960
+ started_event = threading.Event()
961
+ observed: dict = {"uploads_root": None}
962
 
963
  async def _fake_purge_task(uploads_root):
 
964
  observed["uploads_root"] = uploads_root
965
+ started_event.set()
966
  # Boucle infinie minimale — annulée au shutdown.
 
967
  try:
968
  while True:
969
  await asyncio.sleep(3600)
 
980
  from picarones.interfaces.web.app import app
981
 
982
  with TestClient(app):
983
+ # Polling 2 s avec slot 10 ms assez de marge pour
984
+ # les runners GitHub Actions lents (macOS / Windows).
985
+ deadline = time.monotonic() + 2.0
986
+ while not started_event.is_set() and time.monotonic() < deadline:
987
+ time.sleep(0.01)
988
+
989
+ assert started_event.is_set(), (
990
+ "upload_purge_task aurait dû être démarrée par le lifespan "
991
+ "dans les 2 s suivant TestClient(app).__enter__()"
992
  )
993
 
994
  def test_purge_protects_active_corpus(self, tmp_path: Path) -> None: