1d5d7c141e
mubea:build-separator-one nutzte (and blockname (ssg-ils-block-laden blockname)) fuer eff - AutoLISPs AND liefert bei mehreren wahren Ausdruecken T zurueck statt des letzten Werts (anders als in Common Lisp/Scheme). Sobald ein Blockname (z.B. "Scanner") direkt gefunden wurde, statt ueber den Separator_SP-Fallback zu laufen, wurde eff=T an "_.INSERT" uebergeben. Fix: (if blockname (ssg-ils-block-laden blockname)). Ausserdem: Scanner-Testeintrag in mubea.json ergaenzt und _vario_count/_separator_count in test_mubea.py, damit erwartete Ergebniszahlen auch bei "anzahl">1 (Template->mehrere Kopien) stimmen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
164 lines
7.0 KiB
Python
164 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
test_mubea.py - Validiert das Mubea-Gesamtmodell.
|
|
|
|
Prueft die von TEST_MUBEA (test_mubea.lsp) erzeugten Ergebnisse:
|
|
1. Alle Eintraege aus mubea.json wurden gebaut (results.json)
|
|
2. Statuswerte (executed / placeholder fuer Separatoren)
|
|
3. Kreisel-Attribute (HOEHE, KREISELART, Block-Praefix)
|
|
4. Vario: VF_-Block mit VF_WINKEL=21 und L_GF-Split 500/4230
|
|
5. DXF-Geometrie: Block-Anzahlen je Typ (ezdxf)
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
# ============================================================
|
|
# Klassifikation der mubea.json-Eintraege (analog test_mubea.lsp)
|
|
# ============================================================
|
|
|
|
def _is_separator(item):
|
|
return "block" in item
|
|
|
|
def _is_kreisel(item):
|
|
return "id" in item and "block" not in item
|
|
|
|
def _is_vario(item):
|
|
tid = item.get("test_id", "")
|
|
return tid.startswith("VF_")
|
|
|
|
def _is_gefaelle(item):
|
|
tid = item.get("test_id", "")
|
|
return tid.startswith("GF_")
|
|
|
|
|
|
def _vario_count(testdata):
|
|
"""Wie _gefaelle_count: ein VF-Eintrag ist ein Template, das ueber 'anzahl'
|
|
expandiert wird (Default 1, falls Feld fehlt)."""
|
|
return sum(int(t.get("anzahl", 1)) for t in testdata if _is_vario(t))
|
|
|
|
|
|
def _gefaelle_count(testdata):
|
|
"""Anzahl erzeugter Gefaellestrecken: ein GF-Eintrag ist ein Template, das
|
|
ueber 'anzahl' expandiert wird (Default 1, falls Feld fehlt)."""
|
|
return sum(int(t.get("anzahl", 1)) for t in testdata if _is_gefaelle(t))
|
|
|
|
|
|
def _separator_count(testdata):
|
|
"""Wie _gefaelle_count: ein Separator-Eintrag kann per 'anzahl' mehrere
|
|
identische, versetzte Kopien erzeugen (Default 1, falls Feld fehlt)."""
|
|
return sum(int(t.get("anzahl", 1)) for t in testdata if _is_separator(t))
|
|
|
|
|
|
def _expected_counts(testdata):
|
|
return {
|
|
"kreisel": sum(1 for t in testdata if _is_kreisel(t)),
|
|
"vario": _vario_count(testdata),
|
|
"gefaellestrecke": _gefaelle_count(testdata),
|
|
"separator": _separator_count(testdata),
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# Ergebnis-Pruefung (results.json vs. testdata)
|
|
# ============================================================
|
|
|
|
class TestMubeaResults:
|
|
|
|
def test_alle_gebaut(self, mubea_testdata, mubea_results):
|
|
"""Je Kind muss die erwartete Anzahl an Ergebnissen vorliegen."""
|
|
expected = _expected_counts(mubea_testdata)
|
|
got = {}
|
|
for r in mubea_results:
|
|
got[r["kind"]] = got.get(r["kind"], 0) + 1
|
|
for kind, n in expected.items():
|
|
assert got.get(kind, 0) == n, \
|
|
f'{kind}: erwartet {n}, gebaut {got.get(kind, 0)}'
|
|
|
|
def test_status_ok(self, mubea_results):
|
|
"""Alle Ergebnisse muessen 'executed' sein (Separatoren duerfen 'placeholder')."""
|
|
for r in mubea_results:
|
|
if r["kind"] == "separator":
|
|
assert r["status"] in ("executed", "placeholder"), \
|
|
f'{r["test_id"]}: Status={r["status"]}'
|
|
else:
|
|
assert r["status"] == "executed", \
|
|
f'{r["test_id"]}: Status={r["status"]}'
|
|
|
|
def test_kreisel_attribute(self, mubea_testdata, mubea_results):
|
|
"""Kreisel: HOEHE / KREISELART / Block-Praefix wie in mubea.json erwartet."""
|
|
by_id = {r["test_id"]: r for r in mubea_results}
|
|
for t in mubea_testdata:
|
|
if not _is_kreisel(t):
|
|
continue
|
|
r = by_id.get(t["id"])
|
|
assert r, f'Kreisel {t["id"]} fehlt im Ergebnis'
|
|
if "expect_hoehe" in t:
|
|
assert r["actual_attributes"].get("HOEHE") == t["expect_hoehe"], \
|
|
f'{t["id"]}: HOEHE ist={r["actual_attributes"].get("HOEHE")}, erwartet={t["expect_hoehe"]}'
|
|
if "expect_kreiselart" in t:
|
|
assert r["actual_attributes"].get("KREISELART") == t["expect_kreiselart"], \
|
|
f'{t["id"]}: KREISELART ist={r["actual_attributes"].get("KREISELART")}'
|
|
prefix = t.get("expect_block_prefix", "KREISEL_")
|
|
assert r["block_name"].startswith(prefix), \
|
|
f'{t["id"]}: Block "{r["block_name"]}" ohne Praefix "{prefix}"'
|
|
|
|
def test_vario_winkel_und_split(self, mubea_testdata, mubea_results):
|
|
"""Vario: VF_WINKEL entspricht Vorgabe, L_GF-Vorlauf entspricht L_GF1-Ziel,
|
|
Vorlauf+Nachlauf (L_GF_m) ergeben zusammen die aus deltaL geloeste Gesamtlaenge."""
|
|
by_id = {r["test_id"]: r for r in mubea_results}
|
|
for t in mubea_testdata:
|
|
if not _is_vario(t):
|
|
continue
|
|
r = by_id.get(t["test_id"])
|
|
assert r, f'Vario {t["test_id"]} fehlt im Ergebnis'
|
|
attrs = r["actual_attributes"]
|
|
assert r["block_name"].startswith("VF_"), \
|
|
f'{t["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block'
|
|
if "winkel" in t:
|
|
assert attrs.get("VF_WINKEL") == str(t["winkel"]), \
|
|
f'{t["test_id"]}: VF_WINKEL ist={attrs.get("VF_WINKEL")}, erwartet={t["winkel"]}'
|
|
# L_GF_m = "vorne,hinten" in Metern; L_GF1 (Vorlauf-Ziel) muss exakt
|
|
# getroffen werden, L_GF2 (Nachlauf) ist der aus deltaL geloeste Rest.
|
|
if "L_GF1" in t and "L_GF_m" in attrs:
|
|
teile = attrs["L_GF_m"].split(",")
|
|
assert len(teile) == 2, f'{t["test_id"]}: L_GF_m Format "{attrs["L_GF_m"]}"'
|
|
v = float(teile[0]) * 1000.0
|
|
assert abs(v - t["L_GF1"]) < 1.0, \
|
|
f'{t["test_id"]}: L_GF1 vorn ist={v}, erwartet={t["L_GF1"]}'
|
|
|
|
|
|
# ============================================================
|
|
# DXF Geometrie-Pruefung (ezdxf)
|
|
# ============================================================
|
|
|
|
class TestMubeaGeometry:
|
|
|
|
def _counts(self, dxf):
|
|
c = {}
|
|
for e in dxf.modelspace():
|
|
if e.dxftype() == "INSERT":
|
|
c[e.dxf.name] = c.get(e.dxf.name, 0) + 1
|
|
return c
|
|
|
|
def test_block_anzahlen(self, mubea_dxf, mubea_testdata):
|
|
"""Anzahl der KREISEL_/VF_/GF_-Bloecke >= Vorgabe aus mubea.json."""
|
|
c = self._counts(mubea_dxf)
|
|
n_kr = sum(v for n, v in c.items() if n.startswith("KREISEL_"))
|
|
n_vf = sum(v for n, v in c.items() if n.startswith("VF_"))
|
|
n_gf = sum(v for n, v in c.items() if n.startswith("GF_"))
|
|
exp = _expected_counts(mubea_testdata)
|
|
assert n_kr >= exp["kreisel"], f"KREISEL_: {n_kr} < {exp['kreisel']}"
|
|
assert n_vf >= exp["vario"], f"VF_: {n_vf} < {exp['vario']}"
|
|
assert n_gf >= exp["gefaellestrecke"], f"GF_: {n_gf} < {exp['gefaellestrecke']}"
|
|
|
|
def test_handles_im_modelspace(self, mubea_dxf, mubea_results):
|
|
"""Jedes gebaute Block-Handle (executed) liegt im Modelspace."""
|
|
handles = {e.dxf.handle for e in mubea_dxf.modelspace()
|
|
if e.dxftype() == "INSERT"}
|
|
for r in mubea_results:
|
|
if r["status"] == "executed" and r["block_handle"]:
|
|
assert r["block_handle"] in handles, \
|
|
f'{r["test_id"]}: Handle {r["block_handle"]} nicht im Modelspace'
|