Spaces:
Sleeping
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
|
@@ -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 |
-
|
|
|
|
| 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 |
-
#
|
| 976 |
-
#
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
|
|
|
|
|
|
| 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:
|