SCL aus parametrisierten Templates: Golden-Files, Render-Kontext, Pilot 1Sep1Swi
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
97a0db6707
commit
4895ebf5ed
@@ -0,0 +1,130 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user