Picarones / tests /architecture /test_render_helpers.py
Claude
docs: remplacer les chemins legacy par les chemins canoniques v2.0
6b429be unverified
"""Garde-fou contre la prolifération des helpers de rendu.
Les renderers HTML dans ``picarones/report/`` ont accumulé des helpers
locaux dupliqués (couleur, heatmap SVG, etc.) qui devraient vivre dans
un unique ``picarones/report/render_helpers.py``.
État après le sprint de consolidation : tous les ``_color_for_*`` et
``_build_heatmap_svg`` locaux ont été déplacés dans
``picarones/report/render_helpers.py`` qui expose
:func:`color_traffic_light`, :func:`color_single_gradient`,
:func:`color_diverging`, :func:`text_color_for_bg` et
:func:`build_grid_svg`.
Snapshot v1.0.0 (2026-05-02, post-consolidation) : **0 helper local
dupliqué**.
Test ratchet : ce nombre ne peut que descendre. Si un nouveau helper
``_color_for_*`` ou ``_build_heatmap_svg`` apparaît dans un renderer,
le test échoue. La résolution est de paramétrer un des helpers de
:mod:`picarones.reports._helpers.render_helpers` plutôt que de réintroduire
une fonction locale.
"""
from __future__ import annotations
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
REPORT_DIR = REPO_ROOT / "picarones" / "report"
#: Snapshot v1.0.0 post-consolidation. Doit rester à 0.
HELPER_BASELINE = 0
#: Le module mutualisé est exempté (c'est *là* qu'on veut les voir).
HELPERS_MODULE_NAME = "render_helpers.py"
#: Fichiers à ignorer (pas des renderers).
IGNORED_FILES: frozenset[str] = frozenset({"__init__.py", HELPERS_MODULE_NAME})
#: Patterns capturant les helpers à mutualiser.
#:
#: On vise spécifiquement la duplication observée : coloration et
#: builders SVG génériques. Les helpers vraiment locaux (extraction
#: depuis une structure de données spécifique au domaine, formatage
#: dépendant de la métrique) ne sont *pas* visés.
HELPER_PATTERNS: tuple[re.Pattern[str], ...] = (
re.compile(r"^def\s+_color_for\w*\s*\("),
re.compile(r"^def\s+_color\s*\("),
re.compile(r"^def\s+_build_heatmap\w*\s*\("),
)
def _scan_helpers() -> list[tuple[str, int, str]]:
"""Retourne la liste des (chemin_relatif, ligne, signature)."""
found: list[tuple[str, int, str]] = []
for path in sorted(REPORT_DIR.rglob("*.py")):
if path.name in IGNORED_FILES:
continue
try:
text = path.read_text(encoding="utf-8")
except OSError:
continue
for line_num, line in enumerate(text.splitlines(), 1):
for pattern in HELPER_PATTERNS:
if pattern.match(line):
rel = path.relative_to(REPO_ROOT).as_posix()
found.append((rel, line_num, line.strip()))
break
return found
def test_render_helpers_below_baseline() -> None:
"""Le nombre de helpers locaux ne peut que descendre.
Quand on consolide un helper vers ``render_helpers.py``, abaisser
aussi :data:`HELPER_BASELINE` dans le même commit pour verrouiller
le gain.
"""
helpers = _scan_helpers()
count = len(helpers)
locations = "\n".join(
f" {rel}:{line}{sig}" for rel, line, sig in helpers
)
assert count <= HELPER_BASELINE, (
f"\n{count} helpers locaux trouvés (baseline {HELPER_BASELINE}).\n"
f"Régression : un nouveau helper a été ajouté.\n\n"
f"Localisations :\n{locations}\n\n"
"Soit déplace ce helper dans picarones/report/render_helpers.py "
"et importe-le, soit relève HELPER_BASELINE consciemment dans "
"tests/architecture/test_render_helpers.py."
)
def test_baseline_must_be_tightened_when_progress_made() -> None:
"""Si le compte est sous le baseline, abaisse :data:`HELPER_BASELINE`.
Force à verrouiller chaque consolidation : sans cette étape, le
progrès n'est pas figé et une régression future passerait inaperçue
sous le seuil obsolète.
"""
count = len(_scan_helpers())
assert count >= HELPER_BASELINE, (
f"\nExcellent : {count} helpers vs baseline {HELPER_BASELINE}.\n\n"
f"Mets à jour HELPER_BASELINE = {count} dans "
"tests/architecture/test_render_helpers.py pour verrouiller le gain."
)