diff --git a/tests/reference/omniflo_export_reference.csv b/tests/reference/omniflo_export_reference.csv index 2c2c2d7..f2da339 100644 --- a/tests/reference/omniflo_export_reference.csv +++ b/tests/reference/omniflo_export_reference.csv @@ -106,3 +106,4 @@ Elementnummer;TeileArt;TeileId;Bezeichnung;Anzahl;Merkmale 105;"Omniflo Weiche";"0000";"OFWeiche :6";1;{"Weichentyp": "Dreifachweiche", "Richtung": "3", "Weichenwinkel": 90.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": true, "SivasNummer": "834372404+0_BG071090"} 106;"Omniflo Weiche";"0000";"OFWeiche :1";1;{"Weichentyp": "Deltaweiche", "Richtung": "7", "Weichenwinkel": 90.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": false, "SivasNummer": "834372401"} 107;"Omniflo Weiche";"0000";"OFWeiche :1";1;{"Weichentyp": "Sternweiche", "Richtung": "3", "Weichenwinkel": 0.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": false, "SivasNummer": "834372420"} +108;"Omniflo Sum";"autogenerated_of_json";"Omniflo sum";1;{"Anzahl Bögen": 27, "Anzahl Weichengrundkörper": 0, "Anzahl Einwegweichen": 30, "Anzahl Deltaweichen": 7, "Anzahl Doppelweichen und Dreiwegeweichen": 42, "Anzahl Sternweichen": 1, "Gesamtlänge AP110": 0.0, "Gesamtlänge AP60": 0.0} diff --git a/tests/test_omniflo.lsp b/tests/test_omniflo.lsp index f144cdc..2f31a0f 100644 --- a/tests/test_omniflo.lsp +++ b/tests/test_omniflo.lsp @@ -1,10 +1,11 @@ ;; ============================================================ ;; test_omniflo.lsp - Automatischer Integrationstest fuer Omniflo ;; -;; Laedt Testfaelle aus tests/testdata/omniflo_tests.json, +;; Laedt Testfaelle aus tests/testdata/omniflo_tests.json und ;; fuegt je einen Bogen und Weiche jeden Typs in verschiedenen -;; Hoehen ein (Versatz in x-Richtung je 200mm) und exportiert -;; die CSV-Datei nach tests/output/omniflo_export.csv. +;; Hoehen ein (Versatz in x-Richtung je 200mm). Ergebnisse werden +;; ueber omniflo:export-results nach tests/output/omniflo_results.json +;; geschrieben (Aufruf durch SSG_RUN_ALL_TESTS oder manuell). ;; ;; Voraussetzungen: ;; - SSG_LIB geladen (OmniModulInsert.lsp, export.lsp) @@ -104,14 +105,13 @@ ;; ============================================================ ;; C:TEST_OMNIFLO ;; -;; Liest omniflo_tests.json, fuegt alle Testbloecke ein, -;; exportiert CSV nach tests/output/. +;; Liest omniflo_tests.json und fuegt alle Testbloecke ein. ;; ============================================================ (defun c:TEST_OMNIFLO (/ tests-json-pfad testfaelle x-val y-val idx eintrag sivasnr hoehe-str drehung-str description insert-pt anzahl-ok anzahl-fehler - results-list tests-out-dir old-results) + results-list) (ssg-start "TEST_OMNIFLO" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) @@ -217,24 +217,10 @@ ;; Ergebnisse in globaler Variable speichern (setq *omniflo-test-results* (reverse results-list)) - ;; CSV-Export nach tests/output/ - (setq tests-out-dir - (strcat (getenv "DXFMAKRO") "/tests/output")) - (vl-mkdir tests-out-dir) - - (setq old-results (getenv "DXFM_RESULTS")) - (setenv "DXFM_RESULTS" tests-out-dir) - - (princ "\n[TEST_OMNIFLO] Starte OMNI_EXPORT -> tests/output/omniflo_export.csv") - (c:OMNI_EXPORT) - - (setenv "DXFM_RESULTS" (if old-results old-results "")) - (ssg-end) (princ "\n================================================================") (princ "\n TEST_OMNIFLO abgeschlossen.") - (princ (strcat "\n CSV: " tests-out-dir "/omniflo_export.csv")) (princ "\n================================================================") (princ) ) diff --git a/tests/test_omniflo.py b/tests/test_omniflo.py index 49db2b8..233f84b 100644 --- a/tests/test_omniflo.py +++ b/tests/test_omniflo.py @@ -6,11 +6,11 @@ test_omniflo.py - Validiert Omniflo Bogen/Weiche Export. Prueft: 1. CSV-Inhalt gegen Testdefinitionen (Unit-Test, ohne BricsCAD) 2. Merkmale-Werte gegen JSON-Katalog - 3. Korrekte Anzahl und Reihenfolge der Elemente - 4. Integrationstest: liest CSV aus BricsCAD-Lauf (falls vorhanden) + 3. Korrekte Anzahl und Reihenfolge der Elemente inkl. Omniflo-Sum-Zeile + 4. Integrationstest: liest omniflo_results.json aus BricsCAD-Lauf (falls vorhanden) + 5. Referenz-Vergleich: CSV-Export aus export_csv.py gegen abgenommene Referenz-CSV """ -import csv import json import os import sys @@ -99,6 +99,9 @@ class TestOmnifloExportUnit: drehung=float(tc["drehung"]), )) self.items = process_blocks(self.blocks, self.lookup) + # Item-Zeilen ohne die abschliessende "Omniflo Sum"-Zeile + self.element_items = [it for it in self.items if it["teileart"] != "Omniflo Sum"] + self.sum_item = next((it for it in self.items if it["teileart"] == "Omniflo Sum"), None) # CSV in output speichern out_dir = _output_dir() @@ -112,27 +115,40 @@ class TestOmnifloExportUnit: def test_item_count_matches_testcases(self): """Anzahl exportierter Elemente muss Anzahl Testfaelle entsprechen.""" - assert len(self.items) == len(self.tests), ( - f"Erwartet {len(self.tests)} Elemente, erhalten {len(self.items)}" + assert len(self.element_items) == len(self.tests), ( + f"Erwartet {len(self.tests)} Elemente, erhalten {len(self.element_items)}" ) def test_numbering_starts_at_one(self): """Element-Nummerierung muss bei 1 beginnen.""" - assert self.items[0]["nr"] == 1, ( - f"Erstes Element: nr={self.items[0]['nr']}, erwartet 1" + assert self.element_items[0]["nr"] == 1, ( + f"Erstes Element: nr={self.element_items[0]['nr']}, erwartet 1" ) def test_numbering_is_consecutive(self): - """Element-Nummern muessen lueckenlos aufsteigen.""" + """Element-Nummern muessen lueckenlos aufsteigen (inkl. abschliessender Sum-Zeile).""" for i, item in enumerate(self.items): assert item["nr"] == i + 1, ( f"Element {i}: nr={item['nr']}, erwartet {i + 1}" ) + def test_sum_zeile_angehaengt(self): + """Nach allen Omniflo-Elementen muss eine 'Omniflo Sum'-Zeile folgen.""" + assert self.sum_item is not None, "Keine 'Omniflo Sum'-Zeile erzeugt" + assert self.sum_item["nr"] == len(self.items) + assert self.sum_item["teileid"] == "autogenerated_of_json" + m = self.sum_item["merkmale"] + bogen_tests = [tc for tc in self.tests if tc["type"] == "bogen"] + weiche_tests = [tc for tc in self.tests if tc["type"] == "weiche"] + assert m["Anzahl Bögen"] == len(bogen_tests) + assert (m["Anzahl Weichengrundkörper"] + m["Anzahl Einwegweichen"] + + m["Anzahl Deltaweichen"] + m["Anzahl Doppelweichen und Dreiwegeweichen"] + + m["Anzahl Sternweichen"]) == len(weiche_tests) + def test_bogen_teileart(self): """Boegen muessen TeileArt 'Omniflo Kurve' haben.""" bogen_tests = [tc for tc in self.tests if tc["type"] == "bogen"] - bogen_items = [it for it in self.items if it["teileart"] == "Omniflo Kurve"] + bogen_items = [it for it in self.element_items if it["teileart"] == "Omniflo Kurve"] assert len(bogen_items) == len(bogen_tests), ( f"Erwartet {len(bogen_tests)} Boegen, erhalten {len(bogen_items)}" ) @@ -140,7 +156,7 @@ class TestOmnifloExportUnit: def test_weiche_teileart(self): """Weichen muessen TeileArt 'Omniflo Weiche' haben.""" weiche_tests = [tc for tc in self.tests if tc["type"] == "weiche"] - weiche_items = [it for it in self.items if it["teileart"] == "Omniflo Weiche"] + weiche_items = [it for it in self.element_items if it["teileart"] == "Omniflo Weiche"] assert len(weiche_items) == len(weiche_tests), ( f"Erwartet {len(weiche_tests)} Weichen, erhalten {len(weiche_items)}" ) @@ -148,7 +164,7 @@ class TestOmnifloExportUnit: def test_bogen_merkmale_felder(self): """Boegen-Merkmale muessen alle Pflichtfelder enthalten.""" required = {"Kurvenwinkel", "Radius", "Höhe", "Drehung", "SivasNummer"} - for item in self.items: + for item in self.element_items: if item["teileart"] != "Omniflo Kurve": continue merkmale = item["merkmale"] @@ -161,7 +177,7 @@ class TestOmnifloExportUnit: """Weichen-Merkmale muessen alle Pflichtfelder enthalten.""" required = {"Weichentyp", "Richtung", "Weichenwinkel", "Höhe", "Drehung", "Antrieb Kurve", "SivasNummer"} - for item in self.items: + for item in self.element_items: if item["teileart"] != "Omniflo Weiche": continue merkmale = item["merkmale"] @@ -174,7 +190,7 @@ class TestOmnifloExportUnit: """Bogen-Merkmale muessen mit JSON-Katalog uebereinstimmen.""" for tc, item in zip( [t for t in self.tests if t["type"] == "bogen"], - [it for it in self.items if it["teileart"] == "Omniflo Kurve"] + [it for it in self.element_items if it["teileart"] == "Omniflo Kurve"] ): sivasnr = tc["sivasnr"] assert sivasnr in self.lookup, f"SivasNr {sivasnr} nicht im Katalog" @@ -200,7 +216,7 @@ class TestOmnifloExportUnit: """Weichen-Merkmale muessen mit JSON-Katalog uebereinstimmen.""" for tc, item in zip( [t for t in self.tests if t["type"] == "weiche"], - [it for it in self.items if it["teileart"] == "Omniflo Weiche"] + [it for it in self.element_items if it["teileart"] == "Omniflo Weiche"] ): sivasnr = tc["sivasnr"] assert sivasnr in self.lookup, f"SivasNr {sivasnr} nicht im Katalog" @@ -229,7 +245,7 @@ class TestOmnifloExportUnit: def test_bogen_hoehe_als_string(self): """Bogen-Hoehe muss als String vorliegen (z.B. '2000', nicht 2000).""" - for item in self.items: + for item in self.element_items: if item["teileart"] != "Omniflo Kurve": continue assert isinstance(item["merkmale"]["Höhe"], str), ( @@ -239,7 +255,7 @@ class TestOmnifloExportUnit: def test_weiche_hoehe_als_string(self): """Weichen-Hoehe muss als String vorliegen.""" - for item in self.items: + for item in self.element_items: if item["teileart"] != "Omniflo Weiche": continue assert isinstance(item["merkmale"]["Höhe"], str), ( @@ -248,7 +264,7 @@ class TestOmnifloExportUnit: def test_kurvenwinkel_als_float(self): """Kurvenwinkel muss als Float vorliegen (90.0, nicht 90).""" - for item in self.items: + for item in self.element_items: if item["teileart"] == "Omniflo Kurve": assert isinstance(item["merkmale"]["Kurvenwinkel"], float), ( f"{item['bezeichnung']}: Kurvenwinkel muss float sein" @@ -273,90 +289,37 @@ class TestOmnifloExportUnit: # --------------------------------------------------------------------------- -# Integrations-Tests: CSV aus BricsCAD-Lauf validieren +# Integrations-Tests: Omniflo-Ergebnisse aus BricsCAD-Lauf validieren +# (tests/output/omniflo_results.json, geschrieben von omniflo:export-results) # --------------------------------------------------------------------------- -class TestOmnifloExportCSV: - """Validiert CSV-Datei aus dem BricsCAD-Lauf (c:TEST_OMNIFLO_EXPORT).""" +class TestOmnifloResults: + """Validiert omniflo_results.json aus dem BricsCAD-Lauf (c:TEST_OMNIFLO).""" - @pytest.fixture(autouse=True) - def setup(self): - csv_path = os.path.join(_output_dir(), "omniflo_export.csv") - if not os.path.exists(csv_path): - pytest.skip( - "omniflo_export.csv nicht vorhanden - " - "TEST_OMNIFLO_EXPORT in BricsCAD ausfuehren" - ) - self.csv_path = csv_path - self.rows = [] - with open(csv_path, "r", encoding="utf-8") as f: - reader = csv.reader(f, delimiter=";") - self.header = next(reader) - for row in reader: - self.rows.append(row) + def test_all_tests_executed(self, omniflo_testdata, omniflo_results): + """Alle definierten Testfaelle muessen ausgefuehrt worden sein.""" + expected_sivas = {t["sivasnr"] for t in omniflo_testdata} + result_sivas = {r["sivasnr"] for r in omniflo_results} + missing = expected_sivas - result_sivas + assert not missing, f"Nicht ausgefuehrte Tests: {missing}" - def test_header_felder(self): - """CSV-Header muss korrekte Spaltenbezeichnungen haben.""" - assert self.header[0] == "Elementnummer" - assert self.header[1] == "TeileArt" - assert self.header[2] == "TeileId" - assert self.header[3] == "Bezeichnung" - assert self.header[4] == "Anzahl" - assert self.header[5] == "Merkmale" - - def test_mindest_anzahl_zeilen(self): - """CSV muss mindestens so viele Zeilen haben wie Testfaelle.""" - tests = _load_omniflo_tests() - assert len(self.rows) >= len(tests), ( - f"CSV hat {len(self.rows)} Zeilen, " - f"erwartet mind. {len(tests)}" - ) - - def test_anzahl_spalte_ist_eins(self): - """Anzahl-Spalte muss fuer alle Zeilen '1' sein.""" - for i, row in enumerate(self.rows): - if len(row) < 5: - continue - assert row[4] == "1", ( - f"Zeile {i + 2}: Anzahl={row[4]}, erwartet 1" + def test_all_executed_successfully(self, omniflo_results): + """Alle Ergebnisse muessen Status 'OK' haben.""" + for result in omniflo_results: + assert result["status"] == "OK", ( + f'{result["test_id"]} ({result["sivasnr"]}): Status={result["status"]}' ) - def test_merkmale_sind_valides_json(self): - """Merkmale-Spalte muss valides JSON enthalten.""" - for i, row in enumerate(self.rows): - if len(row) < 6: + def test_hoehe_matches(self, omniflo_testdata, omniflo_results): + """Eingefuegte Hoehe muss dem Testfall entsprechen.""" + results_by_sivas = {r["sivasnr"]: r for r in omniflo_results} + for test in omniflo_testdata: + result = results_by_sivas.get(test["sivasnr"]) + if not result: continue - try: - json.loads(row[5]) - except json.JSONDecodeError as e: - pytest.fail( - f"Zeile {i + 2}: Merkmale ist kein valides JSON: {e}\n" - f" Inhalt: {row[5][:100]}" - ) - - def test_bogen_merkmale_felder_in_csv(self): - """Bogen-Zeilen muessen Pflichtfelder in Merkmale haben.""" - required = {"Kurvenwinkel", "Radius", "Höhe", "Drehung", "SivasNummer"} - for i, row in enumerate(self.rows): - if len(row) < 6 or row[1].strip('"') != "Omniflo Kurve": - continue - merkmale = json.loads(row[5]) - missing = required - set(merkmale.keys()) - assert not missing, ( - f"Zeile {i + 2}: Fehlende Bogen-Felder: {missing}" - ) - - def test_weiche_merkmale_felder_in_csv(self): - """Weichen-Zeilen muessen Pflichtfelder in Merkmale haben.""" - required = {"Weichentyp", "Richtung", "Weichenwinkel", - "Höhe", "Drehung", "Antrieb Kurve", "SivasNummer"} - for i, row in enumerate(self.rows): - if len(row) < 6 or row[1].strip('"') != "Omniflo Weiche": - continue - merkmale = json.loads(row[5]) - missing = required - set(merkmale.keys()) - assert not missing, ( - f"Zeile {i + 2}: Fehlende Weichen-Felder: {missing}" + assert result["hoehe"] == str(int(test["hoehe"])), ( + f'{test["sivasnr"]}: Hoehe erwartet={test["hoehe"]}, ' + f'ist={result["hoehe"]}' ) @@ -377,7 +340,7 @@ def _normalize_row(line): class TestOmnifloReferenceCSV: - """Vergleicht den erzeugten Unit-Test-CSV-Export mit einer Referenzdatei.""" + """Vergleicht den von export_csv.py erzeugten CSV-Export mit einer Referenzdatei.""" @pytest.fixture(autouse=True) def setup(self):