[FEAT] Weitere Objekte der Anlage in hundm05.json bauen (Kreisel)

tests/testdata/hundm05.json enthaelt jetzt neben den 5 Spec-Ketten auch
andere Objekte der Anlage - erkennbar am Feld "function", im Format der
jeweiligen Einzel-Testdaten (ein Kreisel also wie in kreisel_tests.json).
Damit beschreibt EINE Datei die ganze Anlage.

- tests/test_hundm05.lsp: hundm05:bau-zusatzobjekte sammelt alle Objekte mit
  "function" und baut sie; hundm05:bau-kreisel ruft kreisel-insert-script
  (wie tests/test_kreisel.lsp) und liefert einen Ergebnis-Record in derselben
  Form wie die Ketten. Eigener ssg-start-Rahmen mit ATTREQ/ATTDIA 0, weil
  vsp-bau-datei seinen schon geschlossen hat und (command "_.INSERT" ...)
  sonst nach Attributwerten fragt. Je Objekt gefangen, damit ein Fehler die
  restlichen nicht mitnimmt.
- Lisp/vf_spec.lsp: "kind" ist jetzt ein Record-Feld (Default "linienzug")
  statt eines Literals in vsp-result-json - so schreibt vsp-results-schreiben
  beide Objektarten in EINE Ergebnisdatei.
- lib/vf_spec_export.py: Objekte der Ziel-Datei, die keine Kette beschreiben,
  werden beim Neuschreiben unveraendert ans Dateiende uebernommen. Ohne das
  waere jeder von Hand ergaenzte Eintrag beim naechsten Generatorlauf weg -
  die Datei ist erzeugt UND handgepflegt. spec_aus_flachen_objekten
  ueberspringt sie jetzt statt zu werfen, genau wie vsp-gruppieren in LISP.
- tests/test_hundm05.py: die Ketten-Tests filtern auf kind "linienzug"
  (Records ohne das Feld stammen aus aelteren Laeufen und waren nur Ketten);
  neue Klasse TestZusatzobjekte prueft Status, expect_block_prefix,
  expect_hoehe (Attribut HOEHE), expect_kreiselart (KREISELART) und den
  Einfuegepunkt - dieselben Erwartungsfelder wie tests/test_kreisel.py.
- tests/test_vf_spec.py: neuer Test, dass die Zusatzobjekte die
  Ketten-Zerlegung nicht beeinflussen - auch nicht, wenn sie mitten zwischen
  den Sektionen stehen (der eingetragene Kreisel stand zunaechst inmitten
  der Sub-Knoten von Kette 5; nach dem Regenerieren steht er am Dateiende).

Verifiziert: 118 pytest-Tests gruen, Spec-Regenerierung uebernimmt das
Zusatzobjekt (58 Objekte), beide .lsp lint-sauber. Die Kreisel-Tests warten
auf den naechsten TEST_HUNDM05-Lauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 14:49:46 +02:00
parent e91f1d00d0
commit 2dd5382060
8 changed files with 296 additions and 12 deletions
+107 -1
View File
@@ -38,7 +38,7 @@ def spec_objekte():
@pytest.fixture(scope="module")
def spec_ergebnisse():
def alle_ergebnisse():
for pfad in ERGEBNIS_ORTE:
if os.path.isfile(pfad):
with open(pfad, encoding="utf-8") as fh:
@@ -47,6 +47,37 @@ def spec_ergebnisse():
"ausfuehren")
@pytest.fixture(scope="module")
def spec_ergebnisse(alle_ergebnisse):
"""Nur die Ketten. Die Ergebnisdatei enthaelt seit dem Nachtragen
weiterer Anlagenteile mehrere Objektarten, unterschieden ueber "kind"
(Records ohne das Feld stammen aus einem aelteren Lauf und waren
ausschliesslich Ketten)."""
return [r for r in alle_ergebnisse
if r.get("kind", "linienzug") == "linienzug"]
@pytest.fixture(scope="module")
def kreisel_ergebnisse(alle_ergebnisse):
raus = [r for r in alle_ergebnisse if r.get("kind") == "kreisel"]
if not raus:
pytest.skip("keine Kreisel-Ergebnisse (Lauf vor dem Nachtragen "
"weiterer Anlagenteile?)")
return raus
@pytest.fixture(scope="module")
def kreisel_objekte(spec_objekte):
"""Die nachgetragenen Anlagenteile aus den Testdaten - alles mit
"function", das keine Kette beschreibt."""
raus = [o for o in spec_objekte
if o.get("function") and not ("spec_id" in o or "glied" in o
or "sub" in o)]
if not raus:
pytest.skip("keine Zusatzobjekte in tests/testdata/hundm05.json")
return raus
class TestSpecBau:
def test_je_spec_ein_ergebnis(self, spec_objekte, spec_ergebnisse):
@@ -142,3 +173,78 @@ class TestSpecBau:
f'{spec_r["spec_id"]}: Attribut {tag} ist ' \
f'{spec_r["actual_attributes"].get(tag)!r}, ' \
f'Journal-Pfad hatte {wert!r}'
# ============================================================
# Weitere Objekte der Anlage (Kreisel)
# ============================================================
# tests/testdata/hundm05.json enthaelt neben den Ketten auch andere Objekte
# der Anlage - Format wie tests/testdata/kreisel_tests.json (Schema in
# tests/testdata/object_data.md Abschnitt 1). Gebaut werden sie von
# hundm05:bau-zusatzobjekte in tests/test_hundm05.lsp.
class TestZusatzobjekte:
def test_je_objekt_ein_ergebnis(self, kreisel_objekte,
kreisel_ergebnisse):
soll = [o["id"] for o in kreisel_objekte]
ist = [r["spec_id"] for r in kreisel_ergebnisse]
assert ist == soll, f"Ergebnisse {ist}, erwartet {soll}"
def test_status_executed(self, kreisel_ergebnisse):
for r in kreisel_ergebnisse:
assert r["status"] == "executed", \
f'{r["spec_id"]}: Status={r["status"]}, {r.get("fehler_text")}'
def test_block_prefix_wie_erwartet(self, kreisel_objekte,
kreisel_ergebnisse):
"""expect_block_prefix aus den Testdaten - bei Kreiseln KREISEL_,
weil die Haelften zu einem Wrapper-Block zusammengefasst werden."""
erwartet = dict((o["id"], o.get("expect_block_prefix"))
for o in kreisel_objekte)
for r in kreisel_ergebnisse:
pref = erwartet.get(r["spec_id"])
if not pref:
continue
assert r["block_name"].startswith(pref), \
f'{r["spec_id"]}: Block {r["block_name"]!r} beginnt nicht ' \
f'mit {pref!r}'
def test_hoehe_wie_erwartet(self, kreisel_objekte, kreisel_ergebnisse):
erwartet = dict((o["id"], o.get("expect_hoehe"))
for o in kreisel_objekte)
for r in kreisel_ergebnisse:
soll = erwartet.get(r["spec_id"])
if soll is None:
continue
ist = r["actual_attributes"].get("HOEHE")
assert ist == str(soll), \
f'{r["spec_id"]}: HOEHE ist {ist!r}, erwartet {soll!r}'
def test_kreiselart_wie_erwartet(self, kreisel_objekte,
kreisel_ergebnisse):
erwartet = dict((o["id"], o.get("expect_kreiselart"))
for o in kreisel_objekte)
for r in kreisel_ergebnisse:
soll = erwartet.get(r["spec_id"])
if soll is None:
continue
ist = r["actual_attributes"].get("KREISELART")
assert ist == str(soll), \
f'{r["spec_id"]}: KREISELART ist {ist!r}, erwartet {soll!r}'
def test_einfuegepunkt_getroffen(self, kreisel_objekte,
kreisel_ergebnisse):
"""Der Einfuegepunkt des Wrapper-Blocks ist der in den Testdaten
angegebene Punkt."""
soll = dict((o["id"], [o.get("x"), o.get("y"), o.get("z")])
for o in kreisel_objekte)
for r in kreisel_ergebnisse:
erwartet = soll.get(r["spec_id"])
if not erwartet or erwartet[0] is None:
continue
for achse, (e, i) in enumerate(zip(erwartet, r["insert_point"])):
if e is None:
continue
assert abs(float(e) - float(i)) < 1.0, \
f'{r["spec_id"]}: Achse {achse} ist {i}, erwartet {e}'