#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_omniflo_strecke.py - Validiert den Omniflo-Streckenzug. Testfall (tests/testdata/omniflo_strecke_tests.json), aktuelles Datenmodell: 7 Elemente: 2x gerade_unten (Fahrtrichtung 0 Grad), 2x gerade_oben (Fahrtrichtung 180 Grad), 2x bogen (echte Omniflo-Boegen), 1x weiche. Prueft (ohne BricsCAD): 1. Struktur: 7 Elemente in der richtigen Gruppen-/Typ-Verteilung, seq lueckenlos 2. Katalog: die Bogen-SivasNummern (gruppe "bogen") existieren in data/json/omniflo_boegen.json mit passendem Radius/Kurvenwinkel; die Weichen-SivasNummer in data/json/omniflo_weichen.json 3. Geometrie: die Geraden haben eine zur Laenge passende Distanz Anfang->Ende und behalten ihre Fahrtrichtung (drehung). Hinweis: Boegen/Weichen tragen im aktuellen Testdaten-Modell KEINE Endpunkte (x_ende/y_ende = null) und keine Mittelpunkte - die frueheren Halbkreis-/ Verkettungs-Tests (drehung_ende, x_mitte, kurve_180, 12 Elemente) bezogen sich auf ein anderes, nicht mehr vorhandenes Modell und wurden entfernt. Optional (nach BricsCAD-Lauf c:TEST_OMNIFLO_STRECKE): 4. Integrationstest gegen tests/output/omniflo_strecke_results.json """ import json import math import os import pytest _TOL = 0.05 # mm / Grad Toleranz (JSON ist auf 2 Nachkommastellen gerundet) # --------------------------------------------------------------------------- # Hilfsfunktionen # --------------------------------------------------------------------------- def _data_dir(): base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) return os.path.join(base, "data") def _testdata_dir(): return os.getenv("DXFM_TESTDATA", os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata")) def _output_dir(): return os.getenv("DXFM_TESTOUT", os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")) def _load_json(path): try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except UnicodeDecodeError: with open(path, "r", encoding="cp1252") as f: return json.load(f) def _load_strecke(): return _load_json(os.path.join(_testdata_dir(), "omniflo_strecke_tests.json")) def _katalog_lookup(dateiname): """SivasNr -> Katalog-Eintrag aus data/json/. Der Schluessel heisst je nach Katalog 'Sivasnr' oder 'SivasNummer'.""" eintraege = _load_json(os.path.join(_data_dir(), "json", dateiname)) lookup = {} for e in eintraege: key = e.get("Sivasnr") or e.get("SivasNummer") or e.get("sivasnr") if key is not None: lookup[str(key)] = e return lookup # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def strecke(): return _load_strecke() @pytest.fixture def boegen_lookup(): return _katalog_lookup("omniflo_boegen.json") @pytest.fixture def weichen_lookup(): return _katalog_lookup("omniflo_weichen.json") # --------------------------------------------------------------------------- # 1. Struktur des Streckenzugs # --------------------------------------------------------------------------- class TestStreckeStruktur: def test_gesamtzahl(self, strecke): """4 Geraden + 2 Boegen + 1 Weiche = 7 Elemente.""" assert len(strecke) == 7, f"Erwartet 7 Elemente, erhalten {len(strecke)}" def test_seq_lueckenlos(self, strecke): """seq muss lueckenlos bei 1 beginnend aufsteigen.""" for i, el in enumerate(strecke): assert el["seq"] == i + 1, f"Element {i}: seq={el['seq']}, erwartet {i + 1}" def test_gruppen_verteilung(self, strecke): """Erwartete Gruppen-Verteilung: 2x gerade_unten, 2x gerade_oben, 2x bogen, 1x weiche.""" from collections import Counter ist = Counter(el["gruppe"] for el in strecke) erwartet = {"gerade_unten": 2, "gerade_oben": 2, "bogen": 2, "weiche": 1} assert dict(ist) == erwartet, f"Gruppen-Verteilung abweichend:\n Ist: {dict(ist)}\n Soll: {erwartet}" def test_typen(self, strecke): """gerade_* -> type 'gerade', bogen/weiche -> type 'bogen'.""" for el in strecke: erwartet = "gerade" if el["gruppe"].startswith("gerade") else "bogen" assert el["type"] == erwartet, ( f"seq {el['seq']}: type={el['type']}, erwartet {erwartet}") def test_vier_geraden(self, strecke): """Vier Geraden gesamt (2 unten + 2 oben).""" assert sum(1 for el in strecke if el["type"] == "gerade") == 4 def test_geraden_richtungen(self, strecke): """gerade_unten faehrt 0 Grad, gerade_oben 180 Grad (Gegenrichtung).""" for el in strecke: if el["gruppe"] == "gerade_unten": assert el["drehung"] == pytest.approx(0.0, abs=_TOL), ( f"seq {el['seq']}: gerade_unten drehung={el['drehung']}, erwartet 0") elif el["gruppe"] == "gerade_oben": assert el["drehung"] == pytest.approx(180.0, abs=_TOL), ( f"seq {el['seq']}: gerade_oben drehung={el['drehung']}, erwartet 180") # --------------------------------------------------------------------------- # 2. Katalog-Abgleich der Boegen / Weichen # --------------------------------------------------------------------------- class TestStreckeKatalog: def test_bogen_sivasnr_im_katalog(self, strecke, boegen_lookup): """Echte Boegen (gruppe 'bogen') muessen im Boegen-Katalog stehen.""" boegen = [el for el in strecke if el["gruppe"] == "bogen"] assert boegen, "Keine Boegen in der Strecke" for el in boegen: assert str(el["sivasnr"]) in boegen_lookup, ( f"SivasNr {el['sivasnr']} nicht in omniflo_boegen.json") def test_bogen_radius_und_winkel(self, strecke, boegen_lookup): for el in strecke: if el["gruppe"] != "bogen": continue eintrag = boegen_lookup[str(el["sivasnr"])] assert float(eintrag["Radius"]) == pytest.approx(el["radius"], abs=_TOL), ( f"{el['sivasnr']}: Radius Katalog={eintrag['Radius']}, Test={el['radius']}") assert float(eintrag["KurvenWinkel"]) == pytest.approx(el["winkel"], abs=_TOL), ( f"{el['sivasnr']}: Winkel Katalog={eintrag['KurvenWinkel']}, Test={el['winkel']}") def test_weiche_sivasnr_im_katalog(self, strecke, weichen_lookup): """Die Weiche (gruppe 'weiche') muss im Weichen-Katalog stehen.""" weichen = [el for el in strecke if el["gruppe"] == "weiche"] for el in weichen: assert str(el["sivasnr"]) in weichen_lookup, ( f"Weichen-SivasNr {el['sivasnr']} nicht in omniflo_weichen.json") # --------------------------------------------------------------------------- # 3. Geometrie der Geraden # --------------------------------------------------------------------------- class TestStreckeGeometrie: def test_geraden_laenge(self, strecke): """Die euklidische Distanz Anfang->Ende entspricht der Laenge.""" for el in strecke: if el["type"] != "gerade": continue dist = math.hypot(el["x_ende"] - el["x"], el["y_ende"] - el["y"]) assert dist == pytest.approx(el["laenge"], abs=0.5), ( f"seq {el['seq']}: Distanz {dist:.2f} != Laenge {el['laenge']}") def test_geraden_haben_endpunkt(self, strecke): """Geraden tragen einen Endpunkt (x_ende/y_ende gesetzt).""" for el in strecke: if el["type"] != "gerade": continue assert el.get("x_ende") is not None and el.get("y_ende") is not None, ( f"seq {el['seq']}: Gerade ohne Endpunkt") # --------------------------------------------------------------------------- # 4. Integrationstest gegen BricsCAD-Ergebnis (optional) # --------------------------------------------------------------------------- class TestStreckeResults: """Validiert omniflo_strecke_results.json aus c:TEST_OMNIFLO_STRECKE.""" @pytest.fixture def results(self): path = os.path.join(_output_dir(), "omniflo_strecke_results.json") if not os.path.exists(path): pytest.skip("omniflo_strecke_results.json nicht vorhanden - " "TEST_OMNIFLO_STRECKE in BricsCAD ausfuehren") data = _load_json(path) if not data: pytest.skip("omniflo_strecke_results.json ist leer") return data def test_alle_elemente_ausgefuehrt(self, strecke, results): assert len(results) == len(strecke), ( f"Erwartet {len(strecke)} Ergebnisse, erhalten {len(results)}") def test_alle_ok(self, results): for r in results: assert r["status"] == "OK", ( f'{r.get("test_id")} (seq {r.get("seq")}): Status={r["status"]}')