Claude commited on
Commit
315a6b9
·
unverified ·
1 Parent(s): d109222

test: corriger 4 défauts de classification du chantier B

Browse files

L'audit a identifié que la classification automatique du commit B2
avait mal placé 23 fichiers dans des sous-dossiers qui ne reflètent
pas le sujet réel du test.

**1. 13 tests ``tests/core/`` → ``tests/measurements/``**

L'algorithme ``Counter.most_common()`` privilégiait l'ordre
alphabétique en cas d'égalité ; comme les tests métriques importaient
fréquemment 1-2 modules ``picarones.core.*`` (``metric_registry``,
``modules``) en plus du module ``picarones.measurements.X`` testé,
ils étaient classés en ``core/``.

Tests concernés : ``test_sprint{38,52,53,55,56,57,58,59,60,64,65,84,85}_*``
— tous testent un module sous ``picarones.measurements/``.

**2. 6 tests ``_html`` ``tests/measurements/`` → ``tests/report/``**

Les tests dont le nom comporte le suffixe ``_html`` testent
explicitement le **rendu HTML** d'une métrique (renderer dans
``picarones.report.*_render``). Ils croisent ``measurements`` et
``report`` mais leur sujet principal est le rendu :
``test_sprint{46_stratification,67_pipeline,68_pipeline_comparison,86_aii5,87_readability,88_robustness_projection}_html``.

**3. 4 tests ambigus reclassés**

- ``test_sprint69_user_doc.py`` (était en ``core/``) → ``integration/``
(test du contenu de ``docs/user/`` — pas une abstraction cercle 1).
- ``test_sprint13_parallelisation_stats.py`` (``measurements/`` →
``integration/``) — teste le runner parallèle via 5 adapters OCR
simultanément, c'est un test E2E.
- ``test_sprint7_advanced_report.py`` (``measurements/`` → ``report/``)
— teste ``ReportGenerator``, pas une métrique.
- ``test_sprint28_ux_save_compare.py`` (``report/`` → ``web/``) —
teste les endpoints ``/api/config/save``, ``/api/config/load``,
``/api/benchmark/{job_id}/synthesis_preview``, etc. C'est un test
HTTP, pas un test de rendu.

**4. ``conftest.py`` racine scindé**

Avant : ``tests/conftest.py`` exposait une fixture
``autouse=True _isolate_web_app_state`` qui s'appliquait à **tous**
les tests, même ceux des cercles 1 et 2 qui n'utilisaient jamais
``picarones.web.*``. Conséquences :

- Chaque test ``tests/core/test_corpus.py`` chargeait inutilement
``picarones.web.app`` à chaque exécution (plusieurs ms d'I/O
d'import × 3354 tests = pollution mesurable).
- L'isolation web mutait l'état global même quand non pertinent.

Après :

- ``tests/conftest.py`` réduit à ~30 lignes — ne fait plus que
positionner les ``os.environ.setdefault`` AVANT le premier import
de ``picarones.web.*``. C'est une opération à exécuter une fois,
pas par-test.
- ``tests/web/conftest.py`` (nouveau) — contient la fixture
``autouse=True``, automatiquement scopée aux tests sous
``tests/web/``. Le ``try/except ImportError`` est supprimé
puisqu'on est garanti que les imports ``picarones.web.*``
réussissent dans ce contexte.

Répartition finale
------------------

tests/core/ 10 fichiers (-13)
tests/measurements/ 49 fichiers (-3 ; -6 _html ; +13 from core)
tests/engines/ 9 fichiers (inchangé)
tests/report/ 21 fichiers (+1 sprint7 ; +6 _html ; -1 sprint28)
tests/web/ 5 fichiers (+1 sprint28)
tests/extras/ 1 fichier (inchangé)
tests/cli/ 1 fichier (inchangé)
tests/integration/ 12 fichiers (+2 ambigus)

Pytest : 3354 passed, 2 skipped, 0 failed (inchangé).
Ruff : All checks passed.

https://claude.ai/code/session_01Hsd7kL8yeCbXn1mA7GQK9L

Files changed (25) hide show
  1. tests/conftest.py +19 -77
  2. tests/{measurements → integration}/test_sprint13_parallelisation_stats.py +0 -0
  3. tests/{core → integration}/test_sprint69_user_doc.py +0 -0
  4. tests/{core → measurements}/test_sprint38_ner_metrics.py +0 -0
  5. tests/{core → measurements}/test_sprint52_readability.py +0 -0
  6. tests/{core → measurements}/test_sprint53_reading_order.py +0 -0
  7. tests/{core → measurements}/test_sprint55_unicode_blocks.py +0 -0
  8. tests/{core → measurements}/test_sprint56_abbreviations.py +0 -0
  9. tests/{core → measurements}/test_sprint57_mufi.py +0 -0
  10. tests/{core → measurements}/test_sprint58_early_modern.py +0 -0
  11. tests/{core → measurements}/test_sprint59_modern_archives.py +0 -0
  12. tests/{core → measurements}/test_sprint60_roman_numerals.py +0 -0
  13. tests/{core → measurements}/test_sprint64_pipeline_benchmark.py +0 -0
  14. tests/{core → measurements}/test_sprint65_pipeline_comparison.py +0 -0
  15. tests/{core → measurements}/test_sprint84_searchability.py +0 -0
  16. tests/{core → measurements}/test_sprint85_numerical_sequences.py +0 -0
  17. tests/{measurements → report}/test_sprint46_stratification_html.py +0 -0
  18. tests/{measurements → report}/test_sprint67_pipeline_html.py +0 -0
  19. tests/{measurements → report}/test_sprint68_pipeline_comparison_html.py +0 -0
  20. tests/{measurements → report}/test_sprint7_advanced_report.py +0 -0
  21. tests/{measurements → report}/test_sprint86_aii5_html.py +0 -0
  22. tests/{measurements → report}/test_sprint87_readability_html.py +0 -0
  23. tests/{measurements → report}/test_sprint88_robustness_projection_html.py +0 -0
  24. tests/web/conftest.py +66 -0
  25. tests/{report → web}/test_sprint28_ux_save_compare.py +0 -0
tests/conftest.py CHANGED
@@ -1,88 +1,30 @@
1
  """Configuration pytest globale.
2
 
3
- Picarones expose dans ``picarones.web.state`` trois états globaux
4
- partagés :
5
- - un sémaphore borné (``JOBS_SEMAPHORE``) pour les benchmarks
6
- concurrents,
7
- - un rate limiter par IP (``RATE_LIMITER``),
8
- - une liste cachée de browse roots dérivée à l'import dans
9
- ``picarones.web.routers.corpus._BROWSE_ROOTS``.
10
-
11
- Ces états peuvent polluer des tests indépendants. Ce conftest :
12
-
13
- 1. Force des défauts test-friendly via env vars **avant** l'import
14
- du module ``picarones.web.app`` sinon le sémaphore est déjà
15
- créé avec la valeur prod (2) au moment où le premier test
16
- l'utilise.
17
- 2. Restaure l'état entre chaque test via une fixture autouse qui
18
- purge le rate limiter, ré-injecte un sémaphore frais, et recalcule
19
- les browse roots selon l'environnement courant.
20
  """
21
 
22
  from __future__ import annotations
23
 
24
  import os
25
- import threading
26
-
27
- import pytest
28
-
29
 
30
- # ---------------------------------------------------------------------------
31
- # Defaults test-friendly à appliquer AVANT tout import de l'app FastAPI.
32
- # ---------------------------------------------------------------------------
33
-
34
- # Plafond très large pour ne jamais bloquer une suite de tests qui démarre
35
- # rapidement plusieurs benchmarks daemon (Sprint 6 + Sprint 24).
36
  os.environ.setdefault("PICARONES_MAX_CONCURRENT_JOBS", "32")
37
- # S'assurer qu'on est en mode dev pour les tests existants ; les tests
38
- # Sprint 24 qui valident le mode public le forcent eux-mêmes via monkeypatch.
39
- os.environ.pop("PICARONES_PUBLIC_MODE", None)
40
- # Rate limit désactivé en dev (déjà le cas, mais explicite).
41
- os.environ.setdefault("PICARONES_RATE_LIMIT_PER_HOUR", "0")
42
-
43
 
44
- # ---------------------------------------------------------------------------
45
- # Fixture d'isolation : purge l'état global avant chaque test.
46
- # ---------------------------------------------------------------------------
47
-
48
- @pytest.fixture(autouse=True)
49
- def _isolate_web_app_state():
50
- """Réinitialise sémaphore, rate limiter et browse roots entre tests.
51
-
52
- Sans cette fixture :
53
- - une suite séquentielle de 32+ tests qui font ``POST /api/benchmark/*``
54
- peut épuiser le sémaphore (les threads daemon ne libèrent qu'à la
55
- fin du benchmark Tesseract, qui prend une fraction de seconde mais
56
- plusieurs tests consécutifs se bousculent) ;
57
- - un test Sprint 24 qui mute ``_BROWSE_ROOTS`` localement laisse une
58
- liste pointant vers un ``tmp_path`` purgé ;
59
- - les tests qui appellent le rate limiter directement laissent des
60
- timestamps dans son bucket.
61
- """
62
- try:
63
- from picarones.web import app as web_app
64
- from picarones.web import security as web_sec
65
- from picarones.web import state as web_state
66
- from picarones.web.routers import corpus as web_corpus_router
67
- except ImportError:
68
- # Tests non-web : aucun état à restaurer.
69
- yield
70
- return
71
-
72
- # Sauvegarde
73
- original_browse_roots = list(web_corpus_router._BROWSE_ROOTS)
74
-
75
- # Sémaphore frais à chaque test (capacité large, voir conftest top-level).
76
- # Le module ``state`` détient le singleton ; ``app`` en a juste une
77
- # référence (importée comme ``_JOBS_SEMAPHORE``) — on synchronise les deux.
78
- new_sem = threading.Semaphore(web_sec.get_max_concurrent_jobs())
79
- web_state.JOBS_SEMAPHORE = new_sem
80
- web_app._JOBS_SEMAPHORE = new_sem
81
- web_state.RATE_LIMITER.reset()
82
- web_state.RATE_LIMITER.max_per_hour = web_sec.get_rate_limit_per_hour()
83
-
84
- yield
85
 
86
- # Restauration
87
- web_corpus_router._BROWSE_ROOTS[:] = original_browse_roots
88
- web_state.RATE_LIMITER.reset()
 
1
  """Configuration pytest globale.
2
 
3
+ Ce conftest racine ne fait **qu'une seule chose** : positionner les
4
+ variables d'environnement test-friendly **avant** tout import de
5
+ ``picarones.web.*``. Sans ça, les singletons web (``JOBS_SEMAPHORE``,
6
+ ``RATE_LIMITER``) seraient instanciés avec les valeurs de production
7
+ (2 jobs concurrents max, rate limit selon mode public) au moment du
8
+ premier import, et chaque test web verrait le bocal saturé.
9
+
10
+ L'isolation par-test des états globaux web (sémaphore, rate limiter,
11
+ browse roots) vit dans ``tests/web/conftest.py`` fixture
12
+ ``autouse=True`` qui ne s'applique qu'aux tests sous ``tests/web/``,
13
+ pour éviter qu'un test cercle 1 (``tests/core/``) ne paie le coût
14
+ de l'import de ``picarones.web.*`` à chaque exécution.
 
 
 
 
 
15
  """
16
 
17
  from __future__ import annotations
18
 
19
  import os
 
 
 
 
20
 
21
+ # Plafond très large pour ne jamais bloquer une suite de tests qui
22
+ # démarre rapidement plusieurs benchmarks daemon en parallèle.
 
 
 
 
23
  os.environ.setdefault("PICARONES_MAX_CONCURRENT_JOBS", "32")
 
 
 
 
 
 
24
 
25
+ # Mode dev par défaut. Les tests qui valident le mode public le
26
+ # forcent eux-mêmes via ``monkeypatch.setenv("PICARONES_PUBLIC_MODE", "1")``.
27
+ os.environ.pop("PICARONES_PUBLIC_MODE", None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # Rate limit désactivé en dev (déjà le défaut, explicité ici).
30
+ os.environ.setdefault("PICARONES_RATE_LIMIT_PER_HOUR", "0")
 
tests/{measurements → integration}/test_sprint13_parallelisation_stats.py RENAMED
File without changes
tests/{core → integration}/test_sprint69_user_doc.py RENAMED
File without changes
tests/{core → measurements}/test_sprint38_ner_metrics.py RENAMED
File without changes
tests/{core → measurements}/test_sprint52_readability.py RENAMED
File without changes
tests/{core → measurements}/test_sprint53_reading_order.py RENAMED
File without changes
tests/{core → measurements}/test_sprint55_unicode_blocks.py RENAMED
File without changes
tests/{core → measurements}/test_sprint56_abbreviations.py RENAMED
File without changes
tests/{core → measurements}/test_sprint57_mufi.py RENAMED
File without changes
tests/{core → measurements}/test_sprint58_early_modern.py RENAMED
File without changes
tests/{core → measurements}/test_sprint59_modern_archives.py RENAMED
File without changes
tests/{core → measurements}/test_sprint60_roman_numerals.py RENAMED
File without changes
tests/{core → measurements}/test_sprint64_pipeline_benchmark.py RENAMED
File without changes
tests/{core → measurements}/test_sprint65_pipeline_comparison.py RENAMED
File without changes
tests/{core → measurements}/test_sprint84_searchability.py RENAMED
File without changes
tests/{core → measurements}/test_sprint85_numerical_sequences.py RENAMED
File without changes
tests/{measurements → report}/test_sprint46_stratification_html.py RENAMED
File without changes
tests/{measurements → report}/test_sprint67_pipeline_html.py RENAMED
File without changes
tests/{measurements → report}/test_sprint68_pipeline_comparison_html.py RENAMED
File without changes
tests/{measurements → report}/test_sprint7_advanced_report.py RENAMED
File without changes
tests/{measurements → report}/test_sprint86_aii5_html.py RENAMED
File without changes
tests/{measurements → report}/test_sprint87_readability_html.py RENAMED
File without changes
tests/{measurements → report}/test_sprint88_robustness_projection_html.py RENAMED
File without changes
tests/web/conftest.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fixtures locales aux tests de l'interface web FastAPI.
2
+
3
+ Le serveur Picarones expose dans :mod:`picarones.web.state` trois
4
+ états globaux partagés entre routes :
5
+
6
+ - ``JOBS_SEMAPHORE`` — sémaphore borné pour les benchmarks concurrents,
7
+ - ``RATE_LIMITER`` — rate limiter par IP,
8
+ - ``picarones.web.routers.corpus._BROWSE_ROOTS`` — répertoires
9
+ autorisés à la navigation, calculés au chargement.
10
+
11
+ Ces états peuvent polluer des tests indépendants. Cette fixture
12
+ réinitialise les trois entre chaque test web — purge le rate
13
+ limiter, ré-injecte un sémaphore frais, recalcule les browse roots
14
+ selon l'environnement courant.
15
+
16
+ Discipline : la fixture est ``autouse=True`` mais n'est définie
17
+ que dans ``tests/web/conftest.py`` — les tests des cercles 1 et 2
18
+ (``tests/core/``, ``tests/measurements/``, etc.) ne paient pas le
19
+ coût de l'import de ``picarones.web.*`` à chaque test.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import threading
25
+
26
+ import pytest
27
+
28
+
29
+ @pytest.fixture(autouse=True)
30
+ def _isolate_web_app_state():
31
+ """Réinitialise sémaphore, rate limiter et browse roots entre tests.
32
+
33
+ Sans cette fixture :
34
+
35
+ - une suite séquentielle de 32+ tests qui font ``POST /api/benchmark/*``
36
+ peut épuiser le sémaphore (les threads daemon ne libèrent qu'à la
37
+ fin du benchmark Tesseract, qui prend une fraction de seconde mais
38
+ plusieurs tests consécutifs se bousculent) ;
39
+ - un test qui mute ``_BROWSE_ROOTS`` localement laisse une liste
40
+ pointant vers un ``tmp_path`` purgé ;
41
+ - les tests qui appellent le rate limiter directement laissent des
42
+ timestamps dans son bucket.
43
+ """
44
+ from picarones.web import app as web_app
45
+ from picarones.web import security as web_sec
46
+ from picarones.web import state as web_state
47
+ from picarones.web.routers import corpus as web_corpus_router
48
+
49
+ # Sauvegarde
50
+ original_browse_roots = list(web_corpus_router._BROWSE_ROOTS)
51
+
52
+ # Sémaphore frais à chaque test (capacité large, voir les env vars
53
+ # par défaut dans ``tests/conftest.py``). Le module ``state``
54
+ # détient le singleton ; ``app`` en a une référence importée comme
55
+ # ``_JOBS_SEMAPHORE`` — on synchronise les deux.
56
+ new_sem = threading.Semaphore(web_sec.get_max_concurrent_jobs())
57
+ web_state.JOBS_SEMAPHORE = new_sem
58
+ web_app._JOBS_SEMAPHORE = new_sem
59
+ web_state.RATE_LIMITER.reset()
60
+ web_state.RATE_LIMITER.max_per_hour = web_sec.get_rate_limit_per_hour()
61
+
62
+ yield
63
+
64
+ # Restauration
65
+ web_corpus_router._BROWSE_ROOTS[:] = original_browse_roots
66
+ web_state.RATE_LIMITER.reset()
tests/{report → web}/test_sprint28_ux_save_compare.py RENAMED
File without changes