File size: 3,954 Bytes
563a0f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
98
99
100
101
102
103
104
105
106
107
108
109
"""Tests Sprint A5 — structure et idempotence du corpus de référence.

Item M-14. Le corpus est généré au runtime via ``_generate.py``. Ce
fichier valide que la génération produit la structure attendue et est
idempotente (deux générations successives produisent les mêmes octets).

L'exécution effective du benchmark Tesseract sur le corpus se fait
dans le workflow CI ``perf_regression.yml`` (cron hebdo) — pas ici,
car ça exigerait que Tesseract soit installé sur la machine de test
(disponible en CI, pas garanti en dev).
"""

from __future__ import annotations

import hashlib
import shutil
from pathlib import Path



REFERENCE_DIR = Path(__file__).parent / "reference_corpus"


def _file_sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def test_reference_corpus_directory_exists() -> None:
    """Le dossier doit exister et contenir le script + le README."""
    assert REFERENCE_DIR.exists() and REFERENCE_DIR.is_dir()
    assert (REFERENCE_DIR / "_generate.py").exists()
    assert (REFERENCE_DIR / "README.md").exists()


def test_each_doc_has_image_and_gt() -> None:
    """Chaque ``doc_<id>.png`` a son ``doc_<id>.gt.txt`` jumeau."""
    pngs = sorted(REFERENCE_DIR.glob("doc_*.png"))
    gts = sorted(REFERENCE_DIR.glob("doc_*.gt.txt"))
    assert len(pngs) >= 5, "Au moins 5 documents de référence attendus"
    assert len(pngs) == len(gts), (
        f"{len(pngs)} PNG mais {len(gts)} GT — alignement cassé."
    )
    for png in pngs:
        gt = png.with_suffix(".gt.txt")
        assert gt.exists(), f"GT manquante pour {png.name}"
        assert gt.stat().st_size > 0, f"GT vide pour {png.name}"


def test_corpus_generation_is_idempotent(tmp_path: Path) -> None:
    """Deux générations successives doivent produire des PNG bit-à-bit
    identiques. Garantit la reproductibilité du baseline CER."""
    # Copie le script dans un tmp_path
    script_target = tmp_path / "_generate.py"
    shutil.copy(REFERENCE_DIR / "_generate.py", script_target)

    import importlib.util
    spec = importlib.util.spec_from_file_location("gen", script_target)
    mod = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(mod)

    out1 = tmp_path / "run1"
    out2 = tmp_path / "run2"
    mod.generate(out1)
    mod.generate(out2)

    pngs1 = sorted(out1.glob("doc_*.png"))
    pngs2 = sorted(out2.glob("doc_*.png"))
    assert [p.name for p in pngs1] == [p.name for p in pngs2]

    for p1, p2 in zip(pngs1, pngs2, strict=True):
        h1 = _file_sha256(p1)
        h2 = _file_sha256(p2)
        assert h1 == h2, (
            f"Génération non-idempotente pour {p1.name} : {h1} vs {h2}. "
            "Vérifier que la police par défaut Pillow est stable."
        )


def test_gt_files_are_utf8(tmp_path: Path) -> None:
    """Les fichiers GT doivent être en UTF-8 valide (pas de BOM, pas de
    caractères de contrôle inutiles)."""
    for gt in REFERENCE_DIR.glob("doc_*.gt.txt"):
        text = gt.read_text(encoding="utf-8")
        assert text.strip(), f"{gt.name} est vide après strip"
        assert "\x00" not in text, f"{gt.name} contient un NUL byte"


def test_no_unexpected_files_in_corpus_dir() -> None:
    """Garde-fou : le dossier ne doit pas accumuler de fichiers parasites
    (ex : `.partial.json` du runner, `.DS_Store` macOS)."""
    allowed = {
        "_generate.py",
        "README.md",
        "test_reference_corpus_structure.py",  # parfois listé via os.scandir si test à proximité
    }
    unexpected = []
    for f in REFERENCE_DIR.iterdir():
        if f.name in allowed:
            continue
        if f.suffix in (".png", ".txt"):
            continue  # documents générés
        if f.name.startswith("__"):
            continue  # __pycache__
        unexpected.append(f.name)
    assert not unexpected, (
        f"Fichiers parasites dans reference_corpus/ : {unexpected}"
    )