File size: 3,787 Bytes
1d1017e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""Phase 5.3 audit code-quality — ``picarones.register_default_metrics``
est exposée comme API publique testable, en remplacement du
side-effect import opaque qui était en tête de ``picarones/__init__.py``.

Avant la Phase 5.3, le ``__init__`` contenait :

.. code-block:: python

    import picarones.evaluation.metrics as _trigger_metric_registration

Côté caller, l'enregistrement des ~37 métriques + ~25 agrégateurs
corpus-level était implicite — pas de point d'entrée nommé, pas de
test direct.  Désormais :

- ``register_default_metrics()`` est exposée dans ``picarones.__all__``.
- L'auto-déclenchement en fin de ``__init__`` reste pour la
  rétrocompat (``from picarones import register_metric`` doit
  marcher hors-test sans setup explicite).
- L'opération est idempotente (appel multiple sans effet de bord
  grâce au cache ``sys.modules``).
"""

from __future__ import annotations


def test_register_default_metrics_is_public_api() -> None:
    """La fonction est exportée dans ``picarones.__all__``."""
    import picarones

    assert "register_default_metrics" in picarones.__all__
    assert callable(picarones.register_default_metrics)


def test_register_default_metrics_is_idempotent() -> None:
    """Plusieurs appels successifs n'ont pas d'effet de bord —
    le module ``picarones.evaluation.metrics`` est cache par
    ``sys.modules`` après le premier import.
    """
    import picarones
    from picarones.evaluation.metric_registry import all_metrics

    snapshot_before = len(all_metrics())
    picarones.register_default_metrics()
    picarones.register_default_metrics()
    picarones.register_default_metrics()
    snapshot_after = len(all_metrics())

    assert snapshot_before == snapshot_after, (
        f"register_default_metrics non idempotent : {snapshot_before} → "
        f"{snapshot_after} métriques.  Le cache sys.modules devrait "
        f"empêcher tout doublon."
    )


def test_auto_trigger_loads_evaluation_metrics_submodules() -> None:
    """Le simple ``import picarones`` doit charger les sous-modules
    listés dans ``picarones/evaluation/metrics/__init__.py`` (ce qui
    déclenche leurs ``@register_metric`` éventuels).

    Limitation connue (drift pré-existant identifié par l'audit
    code-quality, hors scope Phase 5.3) : tous les modules
    ``@register_metric`` ne sont pas dans le ``__init__``.
    L'enregistrement complet des ~37 métriques passe par
    ``builtin_hooks`` et ``philological_hooks`` qui sont eux-mêmes
    chargés à la demande par ``BenchmarkService``.  Ce test vérifie
    uniquement l'invariant minimal : les modules **explicitement**
    listés sont bien chargés.
    """
    import sys

    import picarones  # noqa: F401 — déclenche l'auto-import
    # ``picarones.evaluation.metrics`` doit être chargé.
    assert "picarones.evaluation.metrics" in sys.modules, (
        "Le module ``picarones.evaluation.metrics`` n'est pas dans "
        "``sys.modules`` après ``import picarones`` — "
        "``register_default_metrics`` ne déclenche plus l'import."
    )
    # Et au moins un sous-module concret listé dans son __init__.
    assert "picarones.evaluation.metrics.confusion" in sys.modules


def test_register_default_metrics_signature_takes_no_args() -> None:
    """Contrat d'API : appelable sans argument."""
    import inspect

    import picarones

    sig = inspect.signature(picarones.register_default_metrics)
    assert len(sig.parameters) == 0, (
        f"register_default_metrics doit être sans argument, signature : "
        f"{sig}"
    )
    assert sig.return_annotation in (None, type(None), "None"), (
        f"register_default_metrics doit retourner ``None``, "
        f"annotation actuelle : {sig.return_annotation}"
    )