Files
sps_skel/tests/test_scl_skeleton_golden.py
T
Samer AyadiandClaude Opus 5.5 4895ebf5ed 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>
2026-10-09 18:06:18 +02:00

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