# -*- coding: utf-8 -*- """Golden-File-Tests fuer scl_skeleton.py - die komplette FB_Main-Ausgabe. Schritt 1 aus doc/SCL_Templates.md (§8.3, §11): die heutige Ausgabe wird festgeschrieben, bevor der Generator auf Templates umgebaut wird. Solange kein Template eingeschaltet ist, muss jede SCL byte-gleich zur Referenz bleiben. Eingaben: tests/fixtures/scl/_tro.json - feste Kopien der TRO-JSON aus tro_flow --json, damit nur scl_skeleton geprueft wird und nicht tro_flow. Erwartung: tests/fixtures/scl/_FB_Main.scl, Zeitstempel maskiert. Eine gewollte Aenderung der Ausgabe (z. B. ein Template wird eingeschaltet): Diff pruefen, dann die Referenzen neu schreiben mit set SKEL_UPDATE_GOLDEN=1 & python -m pytest tests/test_scl_skeleton_golden.py """ import os import re from pathlib import Path import pytest import scl_skeleton from scl_skeleton import TEMPLATE_DIR, SclTemplates, load_document, render FIXTURES = Path(__file__).resolve().parent / "fixtures" / "scl" # mubea: alle fuenf dort vorkommenden Typen, auch 2Sep2Swi (ohne Template); # HundM05: die Referenzanlage der TRO-Templates. PROJEKTE = ["mubea", "HundM05"] # Die einzige Zeile, die sich von Lauf zu Lauf aendert. _ZEITSTEMPEL = re.compile(r"^// Erzeugt: .*$", re.MULTILINE) _MASKE = "// Erzeugt: " def _maskiert(text: str) -> str: return _ZEITSTEMPEL.sub(_MASKE, text) def _aktualisieren() -> bool: return os.environ.get("SKEL_UPDATE_GOLDEN") == "1" def _vergleichen(ist: str, referenz: Path) -> None: if _aktualisieren(): referenz.write_text(ist, encoding="utf-8", newline="\n") pytest.skip(f"Referenz neu geschrieben: {referenz.name}") assert referenz.is_file(), ( f"{referenz.name} fehlt - mit SKEL_UPDATE_GOLDEN=1 erzeugen") soll = referenz.read_text(encoding="utf-8") if ist != soll: # Erste abweichende Zeile nennen; der volle Diff ist bei ~900 Zeilen # in der pytest-Ausgabe nicht lesbar. ist_z, soll_z = ist.splitlines(), soll.splitlines() for nr, (a, b) in enumerate(zip(ist_z, soll_z), start=1): if a != b: pytest.fail(f"{referenz.name} Zeile {nr}:\n soll: {b}\n ist : {a}") pytest.fail(f"{referenz.name}: {len(ist_z)} statt {len(soll_z)} Zeilen") @pytest.mark.parametrize("name", PROJEKTE) def test_render_wie_referenz(name: str) -> None: """render() liefert byte-gleich die festgeschriebene SCL.""" quelle = f"{name}_tro.json" document = load_document(FIXTURES / quelle) _vergleichen(_maskiert(render(document, quelle)), FIXTURES / f"{name}_FB_Main.scl") @pytest.mark.parametrize("name", PROJEKTE) def test_render_mit_templates_wie_referenz(name: str) -> None: """--templates: TROs mit Template aus dem Template, der Rest generisch. Eigene Referenz _FB_Main_templates.scl. Eine Aenderung an einem Template zeigt sich hier als Diff - pruefen, dann neu schreiben. """ quelle = f"{name}_tro.json" document = load_document(FIXTURES / quelle) text = render(document, quelle, templates=SclTemplates(TEMPLATE_DIR)) _vergleichen(_maskiert(text), FIXTURES / f"{name}_FB_Main_templates.scl") @pytest.mark.parametrize("name", PROJEKTE) def test_cli_schreibt_referenz(name: str, tmp_path: Path, monkeypatch) -> None: """Der CLI-Weg: Exit 0, Dateiname _FB_Main.scl, Inhalt wie render(). Das ist der Vertrag, auf den sich ein Workflow verlaesst (doc/SCL_Templates.md §12). """ if _aktualisieren(): pytest.skip("Referenzen werden nur ueber test_render_wie_referenz geschrieben") monkeypatch.setenv("SKEL_RESULTS", str(tmp_path)) exit_code = scl_skeleton.main(["--json", str(FIXTURES / f"{name}_tro.json")]) assert exit_code == 0 ausgabe = tmp_path / f"{name}_FB_Main.scl" assert ausgabe.is_file() soll = (FIXTURES / f"{name}_FB_Main.scl").read_text(encoding="utf-8") assert _maskiert(ausgabe.read_text(encoding="utf-8")) == soll def test_cli_templates_und_strict(tmp_path: Path, monkeypatch) -> None: """--templates schreibt die Template-Referenz; --strict bricht ab, solange ein FB-Typ der Anlage (mubea: u. a. 2Sep2Swi) kein Template hat.""" if _aktualisieren(): pytest.skip("Referenzen werden nur ueber die render-Tests geschrieben") monkeypatch.setenv("SKEL_RESULTS", str(tmp_path)) json_file = str(FIXTURES / "mubea_tro.json") assert scl_skeleton.main(["--json", json_file, "--templates"]) == 0 soll = (FIXTURES / "mubea_FB_Main_templates.scl").read_text(encoding="utf-8") ist = (tmp_path / "mubea_FB_Main.scl").read_text(encoding="utf-8") assert _maskiert(ist) == soll (tmp_path / "mubea_FB_Main.scl").unlink() assert scl_skeleton.main(["--json", json_file, "--templates", "--strict"]) == 1 assert not (tmp_path / "mubea_FB_Main.scl").exists() assert scl_skeleton.main(["--json", json_file, "--templates", str(tmp_path / "gibt_es_nicht")]) == 1 def test_todo_marker_bleiben_zaehlbar() -> None: """Die Zahl der offenen Stellen ist eine Qualitaetsgroesse im Workflow. Sie darf sich nur mit Absicht aendern (dann Referenz neu schreiben). """ for name in PROJEKTE: text = (FIXTURES / f"{name}_FB_Main.scl").read_text(encoding="utf-8") assert text.count(scl_skeleton.TODO) > 0, name