#!/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 _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 _expected_counts(testdata): return { "kreisel": sum(1 for t in testdata if _is_kreisel(t)), "vario": sum(1 for t in testdata if _is_vario(t)), "gefaellestrecke": _gefaelle_count(testdata), "separator": sum(1 for t in testdata if _is_separator(t)), } # ============================================================ # 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-Split = 500/4230 (Meter).""" 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 -> 500mm/4230mm = "0.500,4.230" if "L_GF1" in t and "L_GF2" 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, h = float(teile[0]) * 1000.0, float(teile[1]) * 1000.0 assert abs(v - t["L_GF1"]) < 1.0, \ f'{t["test_id"]}: L_GF1 vorn ist={v}, erwartet={t["L_GF1"]}' assert abs(h - t["L_GF2"]) < 1.0, \ f'{t["test_id"]}: L_GF2 hinten ist={h}, erwartet={t["L_GF2"]}' # ============================================================ # 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'