#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_vf_journal_grammatik.py - Prueft den Journal-Dekoder in lib/vf_journal_export.py gegen handgebaute Journale. Warum: der Dekoder ist die ZWEITE Implementierung der Linienzug-Grammatik (erste ist der Bau-Ablauf in Lisp/vf_linienzug.lsp selbst). Nur wenn beide dieselbe Frage-Reihenfolge kennen, laesst sich ein Journal aus einer echten Zeichnung verlustfrei in das Testdatenschema zuruecklesen (tests/testdata/hm_recformat.json). Die 5 echten HundM-Ketten deckten zwei Zweige NICHT ab, weil ihre Daten sie nicht enthalten: - Glied "Linie-VF" ("Neue Linie: Ab/Auf VF") - eine VF-Einheit mit GEWINKELTEM Erstkoerper - dort stellt vfl-vf-einheit KEINE Separator-/Endpunkt-Frage vorab (das tut nur vfl-baue-horizontal-koerper beim horizontalen Erstkoerper, winkel1=0) Genau diese beiden Faelle sichern die Tests hier ab. Laeuft ohne CAD und ohne die grosse DXF - die Journale stehen als Token-Listen im Test. """ import os import sys import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) import vf_journal_export as vje def _journal(*tokens): """Token-Liste in die Rohform bringen, in der das Journal in der XDATA steht: "ART:wert", verbunden mit dem Trenner (die zwei Zeichen "^J" - ein echter Zeilenumbruch wuerde die zeilenweise DXF-Lesung brechen).""" return vje.TRENNER.join(tokens) PRAEAMBEL_OHNE_AS = ("PT:0.0,0.0,5000.0", "REAL:5000.0", "STR:2") ES_GLIED = ("STEP:ES", "STR:90", "STR:1") def _dekodiere(*tokens): j = vje.Journal(_journal(*tokens)) out = j.dekodiere() # wirft, wenn Tokens uebrig bleiben return out def _typen(eintraege): return [e["typ"] for e in eintraege] def _labels(eintraege): return [e["wert"] for e in eintraege if e["typ"] == "step"] class TestHorizontalVF: """Kontrollfall: VF-Einheit mit horizontalem Erstkoerper. Dieser Zweig kommt in den echten HundM-Ketten vor und muss unveraendert bleiben.""" TOKENS = PRAEAMBEL_OHNE_AS + ( "STEP:Horizontal-VF", "STR:3", # erste Sektion, 4 Optionen: 3 = Horizontal-VF "DL:8000.0", "REAL:0.0", # gesnappte Fahrtrichtung (nur erstes Segment) "STR:2", # GF-Verteilung: alles am Einlauf "STR:2", # Separator VOR dem horizontalen Stueck? Nein "STR:2", # Separator NACH dem horizontalen Stueck? Nein "STR:1", # Endpunkt des Foerderers? Ja, nur Motorstation "STR:1", # Kettenende? Ja, Separator + ES ) + ES_GLIED def test_dekodiert_vollstaendig(self): eintraege = _dekodiere(*self.TOKENS) assert _labels(eintraege) == ["Horizontal-VF", "ES"] assert _typen(eintraege)[:3] == ["point_abs", "real", "string"] def test_separatorfragen_vorhanden(self): """Der horizontale Erstkoerper fragt Separator vor/nach - das ist der Unterschied zum gewinkelten Erstkoerper.""" kommentare = " | ".join(e["kommentar"] for e in _dekodiere(*self.TOKENS)) assert "Separator VOR" in kommentare assert "Separator NACH" in kommentare class TestLinieVF: """Der bis dahin fehlende Zweig: Glied "Linie-VF" mit gewinkeltem Erstkoerper der VF-Einheit.""" TOKENS = PRAEAMBEL_OHNE_AS + ( "STEP:Linie-VF", "STR:2", # erste Sektion, 4 Optionen: 2 = Neue Linie Ab/Auf VF "DL:12000.0", "REAL:0.0", # gesnappte Fahrtrichtung "REAL:3000.0", # Zielhoehe des Linienendpunkts # keine Winkelwahl: nur ein Kandidat war gueltig "STR:2", # GF-Verteilung: alles am Einlauf # KEINE Separator-/Endpunktfrage vorab "STR:1", # Endpunkt des Foerderers? Ja, nur Motorstation "STR:1", # Kettenende? Ja, Separator + ES ) + ES_GLIED def test_dekodiert_vollstaendig(self): eintraege = _dekodiere(*self.TOKENS) assert _labels(eintraege) == ["Linie-VF", "ES"] def test_keine_separatorfragen(self): """Ein gewinkelter Erstkoerper wird ohne Frage gebaut. Wuerde der Dekoder hier - wie vor dem Fix - horizontal_koerper() aufrufen, verschluckte er die Endpunkt-Antwort als Separator-Antwort und liefe aus den Tokens heraus.""" kommentare = " | ".join(e["kommentar"] for e in _dekodiere(*self.TOKENS)) assert "Separator VOR" not in kommentare assert "Zielhoehe" in kommentare def test_winkelwahl_optional(self): """vfl-waehle-winkel fragt nur bei mehreren gueltigen Kandidaten - dann steht ein INT im Journal, sonst nicht.""" mit_wahl = list(self.TOKENS) mit_wahl.insert(mit_wahl.index("REAL:3000.0") + 1, "INT:2") eintraege = _dekodiere(*mit_wahl) assert [e["typ"] for e in eintraege].count("int") == 1 def test_vario_kurve_in_der_einheit(self): tokens = PRAEAMBEL_OHNE_AS + ( "STEP:Linie-VF", "STR:2", "DL:12000.0", "REAL:0.0", "REAL:3000.0", "STR:2", # GF-Verteilung "STR:2", # Endpunkt? Nein, weiterbauen "STR:2", # Naechstes in der VF-Einheit: Vario-Kurve "STEP:Vario-Kurve", "INT:90", "STR:1", # Seite links "STR:1", # Variante aussen "STR:1", # Endpunkt? Ja, nur Motorstation "STR:2", # Kettenende? Nein, weiterbauen "STR:2", # Separator an dieser Stelle? Nein "STEP:Linie", "STR:5", # Folge-Sektion, 5 Optionen: 5 = Linie bis Ende "DL:4000.0", "REAL:3000.0", # Zielhoehe am Kettenende ) + ES_GLIED eintraege = _dekodiere(*tokens) assert _labels(eintraege) == ["Linie-VF", "Vario-Kurve", "Linie", "ES"] class TestFehlerfaelle: """Der Dekoder darf nicht raten: fehlende oder ueberzaehlige Tokens sind ein Fehler, kein stiller Teilerfolg.""" def test_unbekanntes_glied(self): with pytest.raises(ValueError): _dekodiere(*(PRAEAMBEL_OHNE_AS + ("STEP:Gibtsnicht", "STR:1"))) def test_tokens_uebrig(self): with pytest.raises(ValueError): _dekodiere(*(TestLinieVF.TOKENS + ("STR:1",))) def test_journal_zu_kurz(self): with pytest.raises(ValueError): _dekodiere(*TestLinieVF.TOKENS[:-1])