Schritte 1-3 aus doc/SCL_Templates.md (neu: Konzept, Wertklassen je Zeile, Namensregeln, Tests, Migrationsplan, Vertrag fuer den CSV->SCL-Workflow). - Golden-Files (tests/test_scl_skeleton_golden.py, tests/fixtures/scl/): komplette FB_Main-Ausgabe von mubea und HundM05 byte-genau festgeschrieben, Zeitstempel maskiert; gewollte Aenderung per SKEL_UPDATE_GOLDEN=1 - lib/scl_context.py (neu): alles, was scl_skeleton aus der TRO-JSON ableitet (Reihenfolge, Separator-/Weichen-/Scanner-Bloecke, Staubereiche, Ziellisten, Namen) als pydantic-Kontext SclFile/SclTro; Namen als SclName (Wert + todo-Merker) als Andockstelle fuer die Namensregeln (Schritt 4). scl_skeleton schreibt nur noch Text; Ausgabe byte-identisch (Golden-Files, zusaetzlich dematic/showroom mit und ohne --start alt/neu verglichen) - lib/scl_templates/ (neu, Jinja2): _macros.scl.j2 + Pilot FB_ILS_MTRO_1Sep1Swi.scl.j2 nach der realen Aufrufstelle TRO107 (PriorityManagerDir2, stInSenJam<n>, xInCarouselRun1, xInRelease TRUE, Separatorzeiten 100/50 ms). Ortsgebundene Zeilen als Vorschlag umgesetzt, jede offene Stelle mit TODO(E-Planung) - Bestaetigung durch SPS steht aus - scl_skeleton: --templates [DIR] (opt-in, ohne Schalter Ausgabe unveraendert), --strict (Exit 1 ohne SCL, wenn ein FB-Typ kein Template hat); Kopf und Zusammenfassung nennen Template-/generische Typen; Templatefehler (StrictUndefined) -> Exit 1 ohne SCL - tests/test_scl_templates.py: Rundlauf Template mit Referenzwerten == Referenz-REGION (normalisiert); jede Platzhalterzeile traegt den TODO-Marker - requirements.txt: jinja2 >= 3.1 (bestehende .venv: pip install -r requirements.txt) - Doku: SCL_Templates.md, Python_Scripts.md §5b, Commandlines.md §2.5, CLAUDE.md Befunde: xInCarouselRun1 laesst sich nicht aus der TRO-JSON ableiten (Kanten- art "umlauf" kommt praktisch nicht vor) -> immer als TODO; nTo1Destinations ist in der Referenz 3 bei 2 Abgaengen (zaehlt evtl. Ziele) -> "pruefen". Mit --templates nur die 1Sep1Swi-REGIONs (+ Kopf) anders als generisch; 32 Tests gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
5.2 KiB
Python
131 lines
5.2 KiB
Python
# -*- 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/<name>_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/<name>_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: <maskiert>"
|
|
|
|
|
|
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 <name>_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 <name>_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
|