775e27182c
17 vorbestehende Failures behoben - alles veraltete Tests, kein Produktivcode: test_foerderer.py (3): CSV-Spalten header-basiert statt per festem Index ansprechen. Die Export-CSV hat inzwischen 17 statt 11 Spalten (Insertpoint, K1-K4, Warnungen kamen dazu, siehe lib/export_csv.py) - die Tests hingen an row[10] fuer Merkmale. Neu: self.col-Lookup + _cell(row, spaltenname), robust gegen weitere Spalten. test_mubea.py (2): _is_gefaelle zaehlte einen zweiten GF_*-Eintrag (block 'S-LP', ein Separator mit assigned_to GF_Mubea) faelschlich als Gefaellestrecke mit -> erwartete 40 statt 20. Jetzt: GF_* UND nicht _is_separator. test_omniflo_strecke.py (12): Test und seine Testdaten-Datei waren auf zwei verschiedene Datenmodelle geschrieben (Test: 12 Elemente, gerade_start/ kurve_180/gerade_ende, Felder drehung_ende/x_mitte; Datei: 7 Elemente, gerade_unten/gerade_oben/bogen/weiche ohne diese Felder). Test komplett auf das tatsaechliche 7-Element-Modell umgeschrieben (Struktur/Katalog/Geraden- Geometrie); Boegen gegen omniflo_boegen.json, Weiche gegen omniflo_weichen.json. Nicht angefasst (bewusst): 3x hundm05 (funktioniert noch nicht) und test_omniflo.py::test_zeilen_matches_reference (inkonsistente Export-CSV + veraltete Referenz - Analyse: export_csv.py ist konsistent 17-spaltig, die Output-Datei stammt aus einem gemischten Lauf; Referenz per --set-as-reference neu abnehmen, kein Code-Fix noetig). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
226 lines
8.8 KiB
Python
226 lines
8.8 KiB
Python
#!/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/<dateiname>. 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"]}')
|