[CHANGE] Nur noch EIN Test baut die HundM-Geometrie: Spec wird TEST_HUNDM05
Nach dem gruenen VF_SPEC_BAU-Lauf bauten zwei Tests dieselben 5 Ketten. Der Spec-Pfad ist jetzt der routinemaessige Bau-Test, der Aufzeichnungs-Pfad der abgeschaltete Regressionstest fuer das Journal-Format. Umbenennungen: - tests/testdata/vf_spec_hundm05.json -> tests/testdata/hundm05.json Die Spec ist das Eingabeformat und die Datei, in der weitere Ketten der Anlage nachgetragen werden. spec_ids jetzt VF_hundm05_LZ_* (wie im Aufzeichnungs-Protokoll), damit sich die Ergebnisse beider Laeufe paaren lassen. - tests/testdata/hundm05.json -> tests/testdata/hm_recformat.json tests/test_hundm05.lsp -> tests/test_hm_recformat.lsp (TEST_HM_RECFORMAT, Praefix hmrec:, Export hm_recformat:export-results) tests/test_hundm05.py -> tests/test_hm_recformat.py In alltests.json auf "disabled": true. Neu: tests/test_hundm05.lsp (TEST_HUNDM05) baut die Spec - OHNE eigene Bau-Logik, es ruft vsp-bau-datei aus Lisp/vf_spec.lsp. Damit gibt es genau einen Code-Pfad, der aus einer Spec Geometrie erzeugt; c:VF_SPEC_BAU nutzt denselben. tests/test_hundm05.py prueft das Ergebnis (die 9 Tests, die vorher in test_vf_spec.py standen); test_vf_spec.py ist jetzt reiner Uebersetzer-Test ohne CAD. Warum das Aufzeichnungsformat bleibt (Details in doc/TODO-plan-vf-interactive.md Abschnitt 4.7): - hm_recformat.json ist die EINZIGE eingecheckte Kopie der Originaldaten (data/polylines.dxf ist mit 124 MB per .gitignore ausgeschlossen). Die Spec ist daraus abgeleitet; ohne die Aufzeichnung faellt die rechte Seite des Rundlauf-Beweises weg. - Es prueft eine ANDERE Invariante: das Journal kommt roh aus der XDATA einer Kundenzeichnung. TEST_HM_RECFORMAT ist damit der einzige Test dafuer, dass ein BESTEHENDER VF_n-Block weiter abspielbar ist - also dass Doppelklick-Edit und 2D/3D-Konvertierung an Altbestand funktionieren. Ein spec-gebautes Journal kann das nicht zeigen, es kommt aus dem Uebersetzer. - Es dokumentiert die Frage-Reihenfolge (kommentar je Eintrag); die Spec verbirgt den Dialog, das ist ihr Zweck. Weitere Anpassungen: - Ergebnisdatei heisst nach der Spec-Datei (<basisname>_results.json), damit Befehl und Testrunner in dieselbe Datei schreiben. Verzeichnis: tests/output (Override DXFM_VF_SPEC_OUT); NICHT DXFM_RESULTS - das sind die Sivas-/ CSV-Exporte, dort landete die Datei ausserhalb des Testbaums. - Neu *vsp-dim-override* fuer TEST_HUNDM05_2D/_3D: wird je Kette angewandt, weil der Abbruch-Handler in vf-linienzug-modus *ssg-ils-dim* zurueck setzt. - Das Anlagenkuerzel der test_id in lib/vf_journal_export.py kam aus dem Namen der Ziel-JSON. Nach der Umbenennung haette eine Regenerierung die ids stillschweigend auf VF_hm_recformat_LZ_* geaendert und die Paarung der beiden Laeufe zerlegt - jetzt feste Konstante TEST_ID_ANLAGE. - conftest: hundm05_* Fixtures -> hmrec_* (die Spec-Fixtures stehen in tests/test_hundm05.py). Verifiziert: 98 pytest-Tests gruen (die 9 Ergebnis-Tests warten auf einen neuen TEST_HUNDM05-Lauf), alle .lsp lint-sauber, Spec-Regenerierung idempotent, TEST_VF_SPEC in BricsCAD 28 PASS / 0 FAIL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+59
-27
@@ -77,6 +77,11 @@
|
||||
(if (not (boundp '*vsp-erster-dl*)) (setq *vsp-erster-dl* T))
|
||||
(if (not (boundp '*vsp-wo*)) (setq *vsp-wo* "Spec"))
|
||||
(if (not (boundp '*vsp-prompts*)) (setq *vsp-prompts* 0))
|
||||
;; Erzwungene Bau-Dimension ("2D"/"3D") fuer einen ganzen Lauf. nil = die
|
||||
;; Spec bzw. die Sitzung entscheidet. Muss JE KETTE neu gesetzt werden,
|
||||
;; darum in vsp-bau-aus-spec und nicht einmal aussen: der Abbruch-Handler in
|
||||
;; vf-linienzug-modus setzt *ssg-ils-dim* zurueck.
|
||||
(if (not (boundp '*vsp-dim-override*)) (setq *vsp-dim-override* nil))
|
||||
|
||||
;; Fehler sammeln (aeltester zuerst) statt beim ersten abzubrechen: eine
|
||||
;; handgeschriebene Spec hat selten genau einen Tippfehler.
|
||||
@@ -684,7 +689,7 @@
|
||||
|
||||
;; Eine Kette aus einer Spec bauen. Rueckgabe: Ergebnis-Record (Alist).
|
||||
;; Die Klammer um vf-linienzug-modus ist dieselbe wie in
|
||||
;; tests/test_hundm05.lsp: *error* sichern, fangen, *error* zurueck. Weil
|
||||
;; tests/test_hm_recformat.lsp: *error* sichern, fangen, *error* zurueck. Weil
|
||||
;; gefangen wird, laeuft der Abbruch-Handler der Modus-Funktion NICHT - eine
|
||||
;; abgebrochene Kette bleibt also als lose Geometrie liegen und ist per
|
||||
;; c:VF_SEKTION_RESTORE rettbar (vfl-segment-xdata-sichern hat pro Iteration
|
||||
@@ -716,7 +721,11 @@
|
||||
;; Dimension je Kette neu setzen: der Abbruch-Handler in
|
||||
;; vf-linienzug-modus setzt *ssg-ils-dim* zurueck, eine abgebrochene
|
||||
;; Kette wuerde die folgenden sonst in der falschen Dimension bauen.
|
||||
(setq dim (ssg-val kopf "dim"))
|
||||
;; Vorrang: expliziter Lauf-Override (Testbefehle _2D/_3D) vor dem
|
||||
;; Feld in der Spec. Ist keines gesetzt, gilt die Sitzung.
|
||||
(setq dim (if (and (boundp '*vsp-dim-override*) *vsp-dim-override*)
|
||||
*vsp-dim-override*
|
||||
(ssg-val kopf "dim")))
|
||||
(if dim (setq *ssg-ils-dim* dim))
|
||||
(setq rec (vsp-set rec "dimension" (vsp-dim-text)))
|
||||
(princ (strcat " -> " (itoa (length journal)) " Journal-Eintraege, "
|
||||
@@ -811,7 +820,7 @@
|
||||
;; TEIL 8: BEFEHL
|
||||
;; ============================================================
|
||||
;; Fragt nichts. Spec-Datei aus DXFM_VF_SPEC, sonst
|
||||
;; tests/testdata/vf_spec_hundm05.json; Ergebnisse nach DXFM_RESULTS bzw.
|
||||
;; tests/testdata/hundm05.json (die Ketten der Anlage HundM);
|
||||
;; tests/output.
|
||||
|
||||
;; ssg-ensure gibt es nur, wenn das Menue geladen wurde (SSG_LIB.mnl). Im
|
||||
@@ -831,30 +840,23 @@
|
||||
"\n Ergebnis: " (itoa ok) " OK, " (itoa rest) " Fehler"
|
||||
"\n================================================")))
|
||||
|
||||
(defun c:VF_SPEC_BAU ( / datei ziel specs records alt-dim)
|
||||
;; Eine Spec-Datei bauen. Rueckgabe: Liste der Ergebnis-Records (nil, wenn
|
||||
;; die Datei nicht lesbar war). Schreibt NICHTS - das entscheidet der
|
||||
;; Aufrufer (der Befehl legt die Datei selbst ab, der Testrunner ueber
|
||||
;; <name>:export-results). Genutzt von c:VF_SPEC_BAU UND von
|
||||
;; tests/test_hundm05.lsp, damit es nur EINEN Weg gibt, der Geometrie aus
|
||||
;; einer Spec erzeugt.
|
||||
(defun vsp-bau-datei (datei / specs records alt-dim)
|
||||
(vsp-modul-sichern "Gefaellestrecke")
|
||||
(setq datei (getenv "DXFM_VF_SPEC"))
|
||||
(if (or (null datei) (= datei ""))
|
||||
(setq datei (strcat (getenv "DXFMAKRO")
|
||||
"/tests/testdata/vf_spec_hundm05.json")))
|
||||
;; Ergebnis-JSON gehoert nach tests/output - dort liegen alle
|
||||
;; Testergebnisse (Konvention test_run_all.lsp, von den
|
||||
;; pytest-Fixtures gelesen, per .gitignore ausgeschlossen).
|
||||
;; NICHT DXFM_RESULTS: das ist das Verzeichnis der Sivas-/CSV-
|
||||
;; Exporte, dort wuerde die Datei ausserhalb des Testbaums landen.
|
||||
(setq ziel (getenv "DXFM_VF_SPEC_OUT"))
|
||||
(if (or (null ziel) (= ziel ""))
|
||||
(setq ziel (strcat (getenv "DXFMAKRO") "/tests/output")))
|
||||
(setq ziel (strcat ziel "/vf_spec_results.json"))
|
||||
|
||||
(princ (strcat "\n================================================"
|
||||
"\n VF_SPEC_BAU - " datei
|
||||
"\n================================================"))
|
||||
(setq specs (vsp-json-laden datei))
|
||||
(if (null specs)
|
||||
(princ "\n[VF_SPEC] Keine Specs gelesen - Abbruch.")
|
||||
(progn
|
||||
(cond
|
||||
((null specs)
|
||||
(princ "\n[VF_SPEC] Keine Specs gelesen - Abbruch.")
|
||||
nil)
|
||||
(T
|
||||
(ssg-start "VF_SPEC_BAU" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
|
||||
;; Ohne ATTREQ/ATTDIA 0 fragt jedes (command "_.INSERT" ...) nach
|
||||
;; Attributwerten - der wahrscheinlichste Haenger im Batch.
|
||||
(setvar "OSMODE" 0)
|
||||
(setvar "ATTREQ" 0)
|
||||
(setvar "ATTDIA" 0)
|
||||
@@ -873,10 +875,40 @@
|
||||
(princ (strcat "\n[VF_SPEC] FEHLER in der Spec-Schleife: "
|
||||
(vl-catch-all-error-message records)))
|
||||
(setq records '())))
|
||||
(vl-mkdir (vl-filename-directory ziel))
|
||||
(vsp-results-schreiben records ziel)
|
||||
(vsp-bilanz records)
|
||||
(ssg-end)))
|
||||
(ssg-end)
|
||||
records)))
|
||||
|
||||
;; Standard-Spec-Datei: DXFM_VF_SPEC, sonst die HundM-Ketten.
|
||||
(defun vsp-standard-datei ( / datei)
|
||||
(setq datei (getenv "DXFM_VF_SPEC"))
|
||||
(if (or (null datei) (= datei ""))
|
||||
(setq datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json")))
|
||||
datei)
|
||||
|
||||
;; Ergebnis-Datei zur Spec-Datei: gleicher Basisname, Endung _results.json,
|
||||
;; im Testausgabe-Verzeichnis. Damit schreibt ein Lauf mit hundm05.json nach
|
||||
;; hundm05_results.json - dieselbe Datei, die der Testrunner erwartet, also
|
||||
;; keine zweite Kopie derselben Ergebnisse.
|
||||
;; NICHT DXFM_RESULTS als Verzeichnis: das sind die Sivas-/CSV-Exporte, dort
|
||||
;; wuerde die Datei ausserhalb des Testbaums landen.
|
||||
(defun vsp-ziel-datei (spec-datei / verz)
|
||||
(setq verz (getenv "DXFM_VF_SPEC_OUT"))
|
||||
(if (or (null verz) (= verz ""))
|
||||
(setq verz (strcat (getenv "DXFMAKRO") "/tests/output")))
|
||||
(strcat verz "/" (vl-filename-base spec-datei) "_results.json"))
|
||||
|
||||
(defun c:VF_SPEC_BAU ( / datei ziel records)
|
||||
(setq datei (vsp-standard-datei))
|
||||
(setq ziel (vsp-ziel-datei datei))
|
||||
(princ (strcat "\n================================================"
|
||||
"\n VF_SPEC_BAU - " datei
|
||||
"\n================================================"))
|
||||
(setq records (vsp-bau-datei datei))
|
||||
(if records
|
||||
(progn
|
||||
(vl-mkdir (vl-filename-directory ziel))
|
||||
(vsp-results-schreiben records ziel)))
|
||||
(princ))
|
||||
|
||||
(princ "\n[vf_spec] geladen (VF_SPEC_BAU)")
|
||||
|
||||
@@ -434,6 +434,53 @@ Zwei Nacharbeiten aus dem Lauf:
|
||||
`(load (strcat (getenv "DXFMAKRO") "/tests/test_vf_spec.lsp"))` - oder
|
||||
ueber `SSG_RUN_ALL_TESTS`, wo `vf_spec` inzwischen in `alltests.json` steht.
|
||||
|
||||
### 4.7 Aufteilung der Testfaelle (2026-09-03)
|
||||
|
||||
Nach dem gruenen `VF_SPEC_BAU`-Lauf gab es zwei Tests, die dieselbe Geometrie
|
||||
bauen. Aufgeloest:
|
||||
|
||||
| | Aufzeichnungsformat | Spec |
|
||||
|---|---|---|
|
||||
| Daten | `tests/testdata/hm_recformat.json` (281 Objekte) | `tests/testdata/hundm05.json` (52 Objekte) |
|
||||
| Treiber | `tests/test_hm_recformat.lsp`, `TEST_HM_RECFORMAT` | `tests/test_hundm05.lsp`, `TEST_HUNDM05` |
|
||||
| pytest | `tests/test_hm_recformat.py` | `tests/test_hundm05.py` |
|
||||
| In `alltests.json` | **abgeschaltet** | aktiv |
|
||||
|
||||
Der Spec-Test ist damit der routinemaessige Bau-Test; die Spec ist auch die
|
||||
Datei, in der weitere Ketten der Anlage nachgetragen werden.
|
||||
|
||||
**Warum die Aufzeichnung bleibt** (und nicht durch die Spec ersetzt wird):
|
||||
|
||||
1. Sie ist die **einzige eingecheckte Kopie der Originaldaten** -
|
||||
`data/polylines.dxf` ist mit 124 MB per `.gitignore` ausgeschlossen. Die
|
||||
Spec ist daraus abgeleitet; ohne die Aufzeichnung faellt die rechte Seite
|
||||
des Rundlauf-Beweises weg und die Spec belegt nur noch, dass der Emitter
|
||||
mit sich selbst uebereinstimmt.
|
||||
2. Sie prueft eine **andere Invariante**: ihr Journal kommt roh aus der XDATA
|
||||
einer Kundenzeichnung. Damit ist `TEST_HM_RECFORMAT` der einzige Test
|
||||
dafuer, dass ein BESTEHENDER `VF_n`-Block weiter abspielbar ist - also
|
||||
dass Doppelklick-Edit und 2D/3D-Konvertierung an Altbestand funktionieren
|
||||
(I1). Ein spec-gebautes Journal kann das nicht zeigen, es kommt aus dem
|
||||
Uebersetzer. Bei Aenderungen am Journal-Format, an den
|
||||
`vfl-in-*`-Wrappern oder am XDATA-Layout also bewusst einschalten.
|
||||
3. Sie **dokumentiert die Frage-Reihenfolge**: jeder Eintrag traegt einen
|
||||
`kommentar`, der die zugehoerige Frage nennt. Die Spec verbirgt den Dialog -
|
||||
das ist ihr Zweck - und kann diese Rolle nicht uebernehmen.
|
||||
|
||||
Damit es nur EINEN Code-Pfad gibt, der aus einer Spec Geometrie erzeugt, hat
|
||||
`tests/test_hundm05.lsp` keine eigene Bau-Logik: es ruft `vsp-bau-datei` aus
|
||||
`Lisp/vf_spec.lsp` - dieselbe Funktion, die auch `c:VF_SPEC_BAU` nutzt. Die
|
||||
Ergebnisdatei heisst jetzt nach der Spec-Datei
|
||||
(`<basisname>_results.json`), damit Befehl und Testrunner in dieselbe Datei
|
||||
schreiben und keine zweite Kopie derselben Ergebnisse entsteht.
|
||||
|
||||
Nebenbei behoben: das Anlagenkuerzel der `test_id` in
|
||||
`lib/vf_journal_export.py` kam aus dem Namen der Ziel-JSON. Nach der
|
||||
Umbenennung haette eine Regenerierung die ids stillschweigend von
|
||||
`VF_hundm05_LZ_*` auf `VF_hm_recformat_LZ_*` geaendert - und damit die
|
||||
Paarung zwischen den beiden Testlaeufen zerlegt. Jetzt feste Konstante
|
||||
`TEST_ID_ANLAGE`.
|
||||
|
||||
**Noch offen in Stufe 1**: die Uebersetzer fuer Modus 2 (`pfad-handles` +
|
||||
Segmentliste, Abschnitt 4.4) und Modus 3 sind NICHT gebaut - Modus 2 braucht
|
||||
laut Reihenfolge (Punkt 11) erst 2-3 echte `"linienzug2"`-Journale als
|
||||
|
||||
@@ -550,11 +550,11 @@ echten HundM-Ketten - er beweist, dass die Spec das Journal verlustfrei
|
||||
abbildet. Modus 2 und 3 haben noch keinen Uebersetzer (Modus 2 braucht erst
|
||||
echte `"linienzug2"`-Journale als Referenz).
|
||||
|
||||
Beispiel: `tests/test_hundm05.lsp` (5 echte Ketten aus `data/polylines.dxf`)
|
||||
Beispiel: `tests/test_hm_recformat.lsp` (5 echte Ketten aus `data/polylines.dxf`)
|
||||
setzt alle drei Schalter mit Save/Restore, ersetzt `getpoint`/`getstring`/
|
||||
`getint`/`getreal`/`alert` zusaetzlich durch **zaehlende** Stubs (Netz fuer
|
||||
Prompts ausserhalb der Wrapper) und schreibt `prompts`, `headless_fehler` und
|
||||
`meldungen` in `tests/output/hundm05_results.json`. Noch nicht umgestellt:
|
||||
`meldungen` in `tests/output/hm_recformat_results.json`. Noch nicht umgestellt:
|
||||
`vfl-konvertiere-ent` (Batch-2D/3D-Konvertierung) laeuft weiter ohne
|
||||
`*vfl-headless*` - dort ist eine Live-Rueckfrage bisher gewolltes Verhalten.
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ Aufruf:
|
||||
python vf_journal_export.py <dxf> <output.json> [--csv <sivas_export.csv>]
|
||||
[--kein-startpunkt-fix]
|
||||
|
||||
Beispiel (erzeugt tests/testdata/hundm05.json):
|
||||
Beispiel (erzeugt tests/testdata/hm_recformat.json):
|
||||
python lib/vf_journal_export.py data/polylines.dxf \
|
||||
tests/testdata/hundm05.json --csv results/HundM_export.csv
|
||||
tests/testdata/hm_recformat.json --csv results/HundM_export.csv
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -57,6 +57,9 @@ TRENNER = "^J"
|
||||
|
||||
RUND = 6
|
||||
|
||||
# Anlagenkuerzel in den test_ids (VF_<kuerzel>_LZ_01 ...).
|
||||
TEST_ID_ANLAGE = "hundm05"
|
||||
|
||||
# Antwort-Klartexte der Menues aus vf_linienzug.lsp (nur zur Annotation).
|
||||
JA_NEIN = {"1": "Ja", "2": "Nein"}
|
||||
SEITE = {"1": "links", "2": "rechts"}
|
||||
@@ -531,12 +534,17 @@ def baue_testfaelle(inserts, strecken, startpunkt_fix, quelle_datei,
|
||||
ssg-parse-json-array (Lisp/ssg_core.lsp) liest die Datei ZEILENWEISE und
|
||||
behandelt jedes "{" als Objektanfang. Ein verschachteltes Array wuerde die
|
||||
Kopf-Objekte verschlucken. Der Treiber gruppiert wieder ueber "test_id"
|
||||
(siehe hundm05:gruppiere in tests/test_hundm05.lsp).
|
||||
(siehe hmrec:gruppiere in tests/test_hm_recformat.lsp).
|
||||
"""
|
||||
# Anlagenkuerzel der test_id kommt aus dem Namen der Ziel-JSON (die
|
||||
# Testfall-Sammlung), nicht aus dem Zeichnungsnamen: tests/testdata/
|
||||
# hundm05.json -> VF_hundm05_LZ_01 .. _nn.
|
||||
praefix = os.path.splitext(os.path.basename(ziel_datei))[0]
|
||||
# Anlagenkuerzel der test_id: FEST, nicht aus dem Dateinamen
|
||||
# abgeleitet. Die id benennt die ANLAGE, nicht die Datei - und sie
|
||||
# muss stabil bleiben, weil dieselbe Kette in beiden Testlaeufen
|
||||
# (Aufzeichnungsformat und Spec) denselben Namen tragen soll:
|
||||
# tests/testdata/hundm05.json (die Spec) wird aus dieser Datei
|
||||
# erzeugt und uebernimmt die ids. Frueher kam das Kuerzel aus dem
|
||||
# Namen der Ziel-JSON; nach der Umbenennung in hm_recformat.json
|
||||
# haette eine Regenerierung die ids stillschweigend geaendert.
|
||||
praefix = TEST_ID_ANLAGE
|
||||
faelle = []
|
||||
for nr, e in enumerate(sorted(inserts, key=lambda i: i["x"]), start=1):
|
||||
eintraege = Journal(e["journal_roh"]).dekodiere()
|
||||
|
||||
+13
-10
@@ -29,8 +29,8 @@ lib/vf_journal_export.py (Journal.dekodiere) und ist dort kommentiert.
|
||||
|
||||
Aufruf (erzeugt die Spec-Testdaten aus dem Protokoll der echten Ketten):
|
||||
|
||||
python lib/vf_spec_export.py tests/testdata/hundm05.json \
|
||||
tests/testdata/vf_spec_hundm05.json
|
||||
python lib/vf_spec_export.py tests/testdata/hm_recformat.json \
|
||||
tests/testdata/hundm05.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -111,7 +111,7 @@ def _ja_code(wert):
|
||||
# STR Menue-/Wertantwort STEP Glied-Marker (Label = Segmenttyp)
|
||||
|
||||
def tokens_aus_protokoll(eintraege):
|
||||
"""Das Frage-Antwort-Protokoll (Schema tests/testdata/hundm05.json)
|
||||
"""Das Frage-Antwort-Protokoll (Schema tests/testdata/hm_recformat.json)
|
||||
zurueck in Journal-Tokens uebersetzen - die Umkehrung von
|
||||
vf_journal_export.Journal.add."""
|
||||
tok = []
|
||||
@@ -577,8 +577,8 @@ def journal_aus_spec(spec):
|
||||
# Flaches JSON (vom LISP-Parser lesbar)
|
||||
# ============================================================
|
||||
# ssg-load-json/ssg-parse-json-array liest ZEILENWEISE und FLACH: jedes "{"
|
||||
# beginnt ein Objekt, Verschachtelung ist unmoeglich. Also dieselbe Form wie
|
||||
# tests/testdata/hundm05.json - Kopf-Objekt ("spec_id"), dann Sektionen
|
||||
# beginnt ein Objekt, Verschachtelung ist unmoeglich. Also dieselbe flache
|
||||
# Form wie die Aufzeichnung: ein Kopf-Objekt ("spec_id"), dann Sektionen
|
||||
# ("glied") und deren Sub-Knoten ("sub") in Reihenfolge.
|
||||
|
||||
def flache_objekte(spec_id, spec, kopf_extra=None):
|
||||
@@ -626,7 +626,7 @@ def flache_objekte(spec_id, spec, kopf_extra=None):
|
||||
|
||||
def spec_aus_flachen_objekten(objekte):
|
||||
"""Umkehrung von flache_objekte - dieselbe Gruppierregel wie
|
||||
hundm05:gruppiere in LISP (Kopf, dann glied/sub in Reihenfolge)."""
|
||||
hmrec:gruppiere in LISP (Kopf, dann glied/sub in Reihenfolge)."""
|
||||
specs = []
|
||||
for obj in objekte:
|
||||
if "spec_id" in obj:
|
||||
@@ -680,9 +680,9 @@ def spec_aus_flachen_objekten(objekte):
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(
|
||||
description="Frage-Antwort-Protokoll (hundm05.json) in Specs "
|
||||
description="Frage-Antwort-Protokoll (hm_recformat.json) in Specs "
|
||||
"uebersetzen und den Rundlauf pruefen.")
|
||||
p.add_argument("protokoll", help="Eingabe (Schema hundm05.json)")
|
||||
p.add_argument("protokoll", help="Eingabe (Schema hm_recformat.json)")
|
||||
p.add_argument("ziel", help="Ausgabe (flaches Spec-JSON)")
|
||||
args = p.parse_args()
|
||||
|
||||
@@ -720,8 +720,11 @@ def main():
|
||||
"planquadrat", "block", "dxf_handle", "quelle"):
|
||||
if feld in kopf:
|
||||
extra[feld] = kopf[feld]
|
||||
alle.extend(flache_objekte(tid.replace("hundm05_LZ", "spec_LZ"),
|
||||
spec, extra))
|
||||
# Die spec_id bleibt die Kettenkennung aus dem Protokoll: dieselbe
|
||||
# Kette soll in beiden Testlaeufen (Spec-Pfad und Aufzeichnungs-
|
||||
# Pfad) denselben Namen tragen, sonst laesst sich das Ergebnis
|
||||
# nicht paaren.
|
||||
alle.extend(flache_objekte(tid, spec, extra))
|
||||
print("%s %-20s %2d Sektionen, %3d Tokens - Rundlauf ok"
|
||||
% (PREFIX, tid, len(spec["sektionen"]), len(tok)))
|
||||
|
||||
|
||||
+14
-3
@@ -102,7 +102,7 @@ Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module.
|
||||
{ "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" },
|
||||
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" },
|
||||
{ "name": "mubea", "save": "dxf", "module": "KreiselInsert" },
|
||||
{ "name": "hundm05", "save": "dxf", "module": "VarioFoerderer", "disabled": true }
|
||||
{ "name": "hundm05", "save": "dxf", "module": "VarioFoerderer" }
|
||||
]
|
||||
```
|
||||
|
||||
@@ -160,7 +160,16 @@ tests/
|
||||
test_foerderer.py # pytest-Validierung Foerderer (Status/Mathematik/CSV/Sivas)
|
||||
test_gefaellestrecke.py # pytest-Validierung Gefaellestrecke
|
||||
test_mubea.py # pytest-Validierung Mubea-Gesamtmodell
|
||||
test_hundm05.py # pytest-Validierung HundM05 (5 Linienzuege)
|
||||
test_hundm05.lsp # LISP: baut die 5 HundM-Ketten aus ihrer
|
||||
# Spec (testdata/hundm05.json) - ruft
|
||||
# vsp-bau-datei, keine eigene Bau-Logik
|
||||
test_hundm05.py # pytest: die so gebaute Anlage pruefen
|
||||
test_hm_recformat.lsp # LISP: dieselben Ketten aus dem ROHEN
|
||||
# Journal der Kundenzeichnung - der
|
||||
# Regressionstest fuers Aufzeichnungs-
|
||||
# format (in alltests.json abgeschaltet,
|
||||
# baut sonst dieselbe Geometrie zweimal)
|
||||
test_hm_recformat.py # pytest-Validierung dazu
|
||||
test_vf_spec.lsp # LISP-Unittest: Spec-Uebersetzer (vsp-journal),
|
||||
# je Glied-Typ + die 5 echten HundM-Ketten
|
||||
# gegen ihr aufgezeichnetes Journal
|
||||
@@ -179,7 +188,9 @@ tests/
|
||||
kreisel_tests.json # Testfall-Definitionen fuer Kreisel
|
||||
omniflo_tests.json # Testfall-Definitionen fuer Omniflo
|
||||
omniflo_strecke_tests.json # Streckenzug: 4 Geraden + 2 Boegen + 1 Weiche (7 Elemente)
|
||||
foerderer_tests.json / mubea.json / hundm05.json / ...
|
||||
hm_recformat.json # Aufzeichnung: rohes Journal der 5 Ketten
|
||||
hundm05.json # daraus abgeleitete Spec (wird erweitert)
|
||||
foerderer_tests.json / mubea.json / ...
|
||||
output/ # Ergebnisse aus BricsCAD (nicht in Git)
|
||||
<name>_results.json # JSON-Ergebnisse pro Testmodul (immer)
|
||||
<name>_tests.dxf/.dwg # Zeichnung pro Testmodul (gemaess save in alltests.json)
|
||||
|
||||
+2
-1
@@ -8,5 +8,6 @@
|
||||
{ "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" },
|
||||
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" },
|
||||
{ "name": "mubea", "save": "dxf", "module": "KreiselInsert" },
|
||||
{ "name": "hundm05", "save": "dxf", "module": "VarioFoerderer", "disabled": true }
|
||||
{ "name": "hundm05", "save": "dxf", "module": "VarioFoerderer" },
|
||||
{ "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true }
|
||||
]
|
||||
|
||||
+19
-17
@@ -189,35 +189,37 @@ def mubea_dxf():
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
|
||||
# --- HundM05 Fixtures ---
|
||||
# --- Aufzeichnungsformat-Fixtures (TEST_HM_RECFORMAT) ---
|
||||
# hm_recformat.json ist das ROHE Eingabe-Journal der 5 HundM-Ketten,
|
||||
# ausgelesen aus der XDATA der Kundenzeichnung (lib/vf_journal_export.py).
|
||||
# Die daraus abgeleitete Spec liegt als testdata/hundm05.json - die Fixtures
|
||||
# dafuer stehen in tests/test_hundm05.py.
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_testdata():
|
||||
"""Laedt die HundM05-Testdefinitionen aus testdata/hundm05.json.
|
||||
|
||||
Erzeugt von lib/dxf_scan_components.py aus results/ST500592_05.dxf
|
||||
(Fenster 216000 144000 228000 156000, Kreisel-Abschnitt).
|
||||
"""
|
||||
path = os.path.join(_testdata_dir(), "hundm05.json")
|
||||
def hmrec_testdata():
|
||||
"""Laedt das Frage-Antwort-Protokoll aus testdata/hm_recformat.json."""
|
||||
path = os.path.join(_testdata_dir(), "hm_recformat.json")
|
||||
return _load_json(path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_results():
|
||||
"""Laedt die HundM05-Ergebnisse aus output/hundm05_results.json."""
|
||||
path = os.path.join(_output_dir(), "hundm05_results.json")
|
||||
def hmrec_results():
|
||||
"""Laedt die Ergebnisse aus output/hm_recformat_results.json."""
|
||||
path = os.path.join(_output_dir(), "hm_recformat_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("hundm05_results.json nicht vorhanden - TEST_HUNDM05 in BricsCAD ausfuehren")
|
||||
pytest.skip("hm_recformat_results.json nicht vorhanden - "
|
||||
"TEST_HM_RECFORMAT in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("hundm05_results.json ist leer")
|
||||
pytest.skip("hm_recformat_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_dxf():
|
||||
"""Laedt die erzeugte HundM05-DXF aus output/hundm05_tests.dxf."""
|
||||
path = os.path.join(_output_dir(), "hundm05_tests.dxf")
|
||||
def hmrec_dxf():
|
||||
"""Laedt die erzeugte DXF aus output/hm_recformat_tests.dxf."""
|
||||
path = os.path.join(_output_dir(), "hm_recformat_tests.dxf")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("hundm05_tests.dxf nicht vorhanden - TESTRUN (save:dxf) in BricsCAD ausfuehren")
|
||||
pytest.skip("hm_recformat_tests.dxf nicht vorhanden - TESTRUN "
|
||||
"(save:dxf) in BricsCAD ausfuehren")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
;; ============================================================
|
||||
;; test_hm_recformat.lsp - Regressionstest fuer das AUFZEICHNUNGSFORMAT
|
||||
;; (rec = recorded): baut die 5 VarioFoerderer-Linienzuege der Anlage HundM
|
||||
;; aus den Journalen nach, die in der Kundenzeichnung stehen.
|
||||
;;
|
||||
;; ABGRENZUNG zu TEST_HUNDM05 (tests/test_hundm05.lsp): dort werden dieselben
|
||||
;; 5 Ketten aus einer SPEC gebaut (tests/testdata/hundm05.json, das neue
|
||||
;; Eingabeformat) - das ist der Test fuer den Bau-Pfad und der, der
|
||||
;; routinemaessig laeuft. HIER geht es um etwas anderes: das Journal kommt
|
||||
;; roh aus der XDATA einer echten Zeichnung. Damit ist dies der einzige Test,
|
||||
;; der belegt, dass ein BESTEHENDER VF_n-Block weiter abspielbar bleibt -
|
||||
;; also dass Doppelklick-Editieren und 2D/3D-Konvertierung an Altbestand
|
||||
;; funktionieren (Invariante I1 in doc/TODO-plan-vf-interactive.md). Ein
|
||||
;; spec-gebautes Journal kann das nicht zeigen, es kommt aus dem Uebersetzer.
|
||||
;;
|
||||
;; Darum in tests/alltests.json auf "disabled": true - er baut dieselbe
|
||||
;; Geometrie wie TEST_HUNDM05 und kostet doppelte CAD-Zeit. Bei Aenderungen
|
||||
;; am Journal-Format, an den vfl-in-*-Wrappern oder am XDATA-Layout aber
|
||||
;; bewusst einschalten: dann ist er der Test, der zaehlt.
|
||||
;;
|
||||
;; Datenquelle: tests/testdata/hm_recformat.json, erzeugt von
|
||||
;; python lib/vf_journal_export.py data/polylines.dxf \
|
||||
;; tests/testdata/hm_recformat.json --csv results/HundM_export.csv
|
||||
;; Das sind die EINGABE-JOURNALE der echten Ketten - ausgelesen aus der XDATA
|
||||
;; (App SSG_VF_EDIT, Marker "linienzug") der VF_n-Bloecke in data/polylines.dxf,
|
||||
;; also genau die Werte, mit denen die Ketten in BricsCAD gebaut wurden.
|
||||
;;
|
||||
;; JSON-Aufbau (FLACH, weil ssg-load-json zeilenweise liest - Details in
|
||||
;; tests/testdata/object_data.md Abschnitt 8):
|
||||
;; Kopf-Objekt (traegt "test_id"): test_id, anlagetyp, modus,
|
||||
;; anzahl_eingaben, block, dxf_handle, startpunkt_mm,
|
||||
;; rotation_grad, beschreibung, sivas_* , erwartung_hinweis
|
||||
;; Eingabe-Objekt (traegt "typ"): point_abs | point_rel | real | int |
|
||||
;; string | step - in exakter Frage-Reihenfolge
|
||||
;; Ein Kopf-Objekt beginnt eine Kette, die folgenden Eingabe-Objekte gehoeren
|
||||
;; dazu (hmrec:gruppiere).
|
||||
;;
|
||||
;; WARUM REPLAY UND KEINE EINGABE-MOCKS (Unterschied zu test_linienzug.lsp):
|
||||
;; test_linienzug.lsp ersetzt getpoint/getstring/getint/getreal durch Mocks und
|
||||
;; rechnet Punkte aus "dL"+"hz" selbst aus. Das geht nur, weil
|
||||
;; linienzug_tests.json zu JEDEM Segment ein "hz" mitfuehrt. Ein echtes Journal
|
||||
;; tut das nicht: vfl-in-abstand journalisiert die Fahrtrichtung NUR beim
|
||||
;; allerersten Segment der Kette, jedes weitere erbt sie vom Vorgaenger. Ein
|
||||
;; Mock kann diese geerbte Richtung nicht kennen - er wuerde einen Punkt in der
|
||||
;; falschen Richtung liefern und die Projektion in vfl-neue-linie-messen ergaebe
|
||||
;; eine falsche (bis auf 0 zusammenfallende) Laenge. Darum wird hier derselbe
|
||||
;; Weg genommen, den die Produktion beim Editieren/Konvertieren nutzt:
|
||||
;; vfl-journal-replay-start + vf-linienzug-modus (siehe vfl-konvertiere-ent in
|
||||
;; Lisp/vf_linienzug.lsp). Die vfl-in-*-Wrapper ziehen ihre Werte dann direkt
|
||||
;; aus der Replay-Queue - inklusive der Sonderregel fuer "hz".
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (VarioFoerderer inkl. vf_linienzug, Gefaellestrecke,
|
||||
;; ssg_core, ssg_dbg)
|
||||
;; - Umgebungsvariable DXFMAKRO gesetzt
|
||||
;;
|
||||
;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"):
|
||||
;; tests/output/hm_recformat_tests.dxf
|
||||
;; tests/output/hm_recformat_results.json (via hm_recformat:export-results)
|
||||
;;
|
||||
;; Aufruf in BricsCAD:
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_hm_recformat.lsp"))
|
||||
;; TEST_HM_RECFORMAT ; in der aktuellen Dimension (ssg-ils-dim-aktuell)
|
||||
;; TEST_HM_RECFORMAT_2D ; erzwungen 2D
|
||||
;; TEST_HM_RECFORMAT_3D ; erzwungen 3D
|
||||
;;
|
||||
;; Debug-Datei (nur wenn eingeschaltet): (dbg-schalter-on "hm_recformat")
|
||||
;; schreibt hm_recformat.dbg ins DXFM_LOG-Verzeichnis (Journal je Kette als JSON).
|
||||
;; ============================================================
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Ergebnis-JSON
|
||||
;; ============================================================
|
||||
|
||||
;; --- JSON-Hilfen fuer Diagnosefelder (Strings/String-Listen) ---
|
||||
;; Die extra-Felder in hmrec:result-json werden ROH in das JSON gesetzt,
|
||||
;; ein Text muss also selbst seine Anfuehrungszeichen mitbringen und die
|
||||
;; JSON-Sonderzeichen maskieren.
|
||||
(defun hmrec:json-escape (s / out i c)
|
||||
(setq out "" i 1)
|
||||
(while (<= i (strlen s))
|
||||
(setq c (substr s i 1))
|
||||
(setq out (cond ((= c "\"") (strcat out "\\\""))
|
||||
((= c "\\") (strcat out "\\\\"))
|
||||
((= c "\n") (strcat out " "))
|
||||
(T (strcat out c))))
|
||||
(setq i (1+ i)))
|
||||
out)
|
||||
|
||||
(defun hmrec:json-str (s)
|
||||
(if s (strcat "\"" (hmrec:json-escape s) "\"") "null"))
|
||||
|
||||
(defun hmrec:json-liste (lst / s first)
|
||||
(setq s "[" first T)
|
||||
(foreach x lst
|
||||
(if (not first) (setq s (strcat s ", ")))
|
||||
(setq s (strcat s (hmrec:json-str x)) first nil))
|
||||
(strcat s "]"))
|
||||
|
||||
;; --- Headless-Diagnose der Produktion lesen ---
|
||||
;; vfl-headless-abbruch (Lisp/vf_linienzug.lsp) legt bei erschoepfter
|
||||
;; Replay-Queue Art der Eingabe, Glied-Nummer und Eingabe-Nummer ab. Das ist
|
||||
;; die eigentliche Desync-Auskunft - ohne sie weiss man nur, DASS Werte offen
|
||||
;; blieben, nicht WO der Ablauf abgewichen ist.
|
||||
(defun hmrec:headless-fehler-text ( / f)
|
||||
(if (and (boundp '*vfl-headless-fehler*) *vfl-headless-fehler*)
|
||||
(progn
|
||||
(setq f *vfl-headless-fehler*)
|
||||
(strcat (cdr (assoc "was" f)) " @ " (cdr (assoc "ort" f))))
|
||||
nil))
|
||||
|
||||
;; Zaehler der trotz allem erreichten Live-Eingaben (muss 0 bleiben).
|
||||
(defun hmrec:prompts ()
|
||||
(if (and (boundp '*hmrec-prompts*) *hmrec-prompts*) *hmrec-prompts* 0))
|
||||
|
||||
|
||||
;; "dimension" haelt fest, in welcher Dimension (2D/3D) gebaut wurde. Das ist
|
||||
;; KEINE Eigenschaft der Eingabedaten: hm_recformat.json beschreibt die Ketten
|
||||
;; dimensionsfrei. Womit unsere Makros bauen, entscheidet ssg-ils-dim-aktuell -
|
||||
;; unsere Bibliothek hat von jedem Block eine _2D- und eine _3D-Variante.
|
||||
;; extra: Alist zusaetzlicher Felder ((name . wert-string)), wird als
|
||||
;; JSON-Zahlen/Strings so uebernommen, wie sie uebergeben werden.
|
||||
(defun hmrec:result-json (test-id kind status block-name block-handle
|
||||
insert-point attribs extra /
|
||||
json tag val first)
|
||||
(setq json (strcat " {\n"
|
||||
" \"test_id\": \"" test-id "\",\n"
|
||||
" \"kind\": \"" kind "\",\n"
|
||||
" \"status\": \"" status "\",\n"
|
||||
" \"dimension\": \""
|
||||
(if (and (boundp '*hmrec-dimension*) *hmrec-dimension*)
|
||||
*hmrec-dimension* "?") "\",\n"
|
||||
" \"block_name\": \"" (if block-name block-name "") "\",\n"
|
||||
" \"block_handle\": \"" (if block-handle block-handle "") "\",\n"
|
||||
" \"insert_point\": ["
|
||||
(if insert-point
|
||||
(strcat (rtos (car insert-point) 2 1) ", "
|
||||
(rtos (cadr insert-point) 2 1) ", "
|
||||
(rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1))
|
||||
"0.0, 0.0, 0.0")
|
||||
"],\n"))
|
||||
(foreach f extra
|
||||
(setq json (strcat json " \"" (car f) "\": " (cdr f) ",\n")))
|
||||
(setq json (strcat json " \"actual_attributes\": {"))
|
||||
(setq first T)
|
||||
(foreach att attribs
|
||||
(setq tag (car att) val (cdr att))
|
||||
(if (not first) (setq json (strcat json ",")))
|
||||
(setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\""))
|
||||
(setq first nil))
|
||||
(setq json (strcat json "\n }\n }"))
|
||||
json
|
||||
)
|
||||
|
||||
|
||||
;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen ---
|
||||
;; status: "executed" bei sauberem Lauf; ein abweichender Status (z.B. bei
|
||||
;; einem Queue-Desync) wird durchgereicht, die Block-Angaben bleiben dabei
|
||||
;; erhalten - sonst waere der betroffene Block im Ergebnis nicht auffindbar.
|
||||
(defun hmrec:ent-json-status (test-id kind status ent extra / ed)
|
||||
(if ent
|
||||
(progn
|
||||
(setq ed (entget ent))
|
||||
(hmrec:result-json test-id kind status
|
||||
(cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed))
|
||||
(ssg-attrib-read ent) extra))
|
||||
(hmrec:result-json test-id kind "failed" nil nil nil nil extra))
|
||||
)
|
||||
|
||||
(defun hmrec:ent-json (test-id kind ent extra)
|
||||
(hmrec:ent-json-status test-id kind "executed" ent extra)
|
||||
)
|
||||
|
||||
|
||||
;; --- Letzten INSERT mit Blocknamen-Praefix suchen ---
|
||||
;; AutoLISP kennt KEIN entprev (nur entnext und entlast) - rueckwaerts durch
|
||||
;; die Zeichnung laufen geht also nicht. Gesucht wird darum VORWAERTS, wobei
|
||||
;; der letzte Treffer gewinnt. Ein blosses (entlast) genuegt nicht: nach dem
|
||||
;; Bau koennen weitere Objekte entstehen (z.B. Beschriftungstexte), entlast
|
||||
;; ist dann nicht der gesuchte Block - genau daran scheiterte der Testlauf.
|
||||
;; ab-ent = Stand der Zeichnung VOR dem Bau. Damit prueft die Suche nur die
|
||||
;; neu entstandenen Objekte: schnell, und sie kann nicht versehentlich den
|
||||
;; Block eines frueheren Baus liefern (siehe Kommentar bei -ab).
|
||||
(defun hmrec:insert-prefix-p (ent pref / ed bn)
|
||||
(setq ed (entget ent) bn (cdr (assoc 2 ed)))
|
||||
(and ed (= (cdr (assoc 0 ed)) "INSERT") bn
|
||||
(>= (strlen bn) (strlen pref))
|
||||
(= (substr bn 1 (strlen pref)) pref)))
|
||||
|
||||
;; Vorwaertslauf ab ab-ent (nil = ganze Zeichnung); der LETZTE Treffer
|
||||
;; gewinnt. Der entget-Test faengt ein inzwischen ungueltiges ab-ent ab
|
||||
;; (entget liefert dann nil, entnext wuerde einen Fehler werfen).
|
||||
(defun hmrec:suche-vorwaerts (pref ab-ent / ent found)
|
||||
(setq ent (if (and ab-ent (entget ab-ent)) (entnext ab-ent) (entnext)))
|
||||
(while ent
|
||||
(if (hmrec:insert-prefix-p ent pref) (setq found ent))
|
||||
(setq ent (entnext ent)))
|
||||
found)
|
||||
|
||||
(defun hmrec:last-insert-prefix-ab (pref ab-ent / ent)
|
||||
(if ab-ent
|
||||
;; MIT Marker nur die neu entstandenen Objekte pruefen - und BEWUSST
|
||||
;; ohne entlast-Kurzschluss: hat dieser Bau keinen Block erzeugt, waere
|
||||
;; entlast der Block eines FRUEHEREN Baus und das Ergebnis wuerde der
|
||||
;; falschen Kette zugeordnet. Kein Treffer = kein Block, das ist die
|
||||
;; richtige Antwort.
|
||||
(hmrec:suche-vorwaerts pref ab-ent)
|
||||
(progn
|
||||
;; Ohne Marker: erst entlast (der haeufige Fall, das gerade
|
||||
;; eingefuegte Objekt), sonst die ganze Zeichnung.
|
||||
(setq ent (entlast))
|
||||
(if (and ent (hmrec:insert-prefix-p ent pref))
|
||||
ent
|
||||
(hmrec:suche-vorwaerts pref nil)))))
|
||||
|
||||
(defun hmrec:last-insert-prefix (pref)
|
||||
(hmrec:last-insert-prefix-ab pref nil))
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; JSON-Eintraege -> Ketten
|
||||
;; ============================================================
|
||||
|
||||
;; Das flache JSON-Array in Ketten gruppieren: ein Objekt mit "test_id" ist
|
||||
;; ein Kopf und beginnt eine neue Kette, die folgenden Objekte mit "typ" sind
|
||||
;; ihre Eingaben (Reihenfolge = Frage-Reihenfolge, bleibt erhalten).
|
||||
;; Rueckgabe: Liste von (kopf eingaben-liste).
|
||||
(defun hmrec:gruppiere (daten / ketten kopf eingaben)
|
||||
(setq ketten '() kopf nil eingaben '())
|
||||
(foreach obj daten
|
||||
(cond
|
||||
((ssg-val obj "test_id")
|
||||
(if kopf
|
||||
(setq ketten (cons (list kopf (reverse eingaben)) ketten)))
|
||||
(setq kopf obj eingaben '()))
|
||||
((ssg-val obj "typ")
|
||||
(if kopf (setq eingaben (cons obj eingaben))))
|
||||
)
|
||||
)
|
||||
(if kopf (setq ketten (cons (list kopf (reverse eingaben)) ketten)))
|
||||
(reverse ketten)
|
||||
)
|
||||
|
||||
|
||||
;; --- Wert als String lesen (auch wenn er als Zahl im JSON steht) ---
|
||||
;; Die Menue-Antworten sind Strings ("1"/"2"/"90"/"30"). Steht in einer von
|
||||
;; Hand gepflegten Datei versehentlich 1 statt "1", wuerde ein Zahlenwert in
|
||||
;; die Replay-Queue wandern und der Vergleich (= antwort "1") schlagen fehl -
|
||||
;; darum hier immer nach String wandeln.
|
||||
(defun hmrec:als-string (v)
|
||||
(cond
|
||||
((null v) "")
|
||||
((= (type v) 'STR) v)
|
||||
((= (type v) 'INT) (itoa v))
|
||||
((= (type v) 'REAL) (rtos v 2 6))
|
||||
(T "")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; --- Einen Eingabe-Eintrag in Journal-Eintraege uebersetzen ---
|
||||
;; Journal-Format (vfl-entry->string in Lisp/vf_linienzug.lsp):
|
||||
;; ("PT" x y z) / ("REAL" . r) / ("DL" . r) / ("INT" . i) / ("STR" . s) /
|
||||
;; ("STEP" . label)
|
||||
;; Ein "point_rel" wird zu EINEM oder ZWEI Eintraegen: immer die Laenge (DL),
|
||||
;; zusaetzlich die Fahrtrichtung (REAL) nur dort, wo sie im Journal steht
|
||||
;; (allererstes Segment der Kette, siehe Kopfkommentar).
|
||||
;; Rueckgabe: Liste von Journal-Eintraegen (kann leer sein).
|
||||
(defun hmrec:eintrag->journal (e / typ w hz)
|
||||
(setq typ (ssg-val e "typ"))
|
||||
(cond
|
||||
((= typ "point_abs")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (and w (listp w) (>= (length w) 3))
|
||||
(list (cons "PT" (mapcar 'float w)))
|
||||
nil))
|
||||
((= typ "point_rel")
|
||||
(setq w (ssg-val e "dL") hz (ssg-val e "hz"))
|
||||
(cond
|
||||
((not (numberp w)) nil)
|
||||
((numberp hz) (list (cons "DL" (float w)) (cons "REAL" (float hz))))
|
||||
(T (list (cons "DL" (float w))))))
|
||||
((= typ "real")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (numberp w) (list (cons "REAL" (float w))) nil))
|
||||
((= typ "int")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (numberp w) (list (cons "INT" (fix w))) nil))
|
||||
((= typ "string")
|
||||
(list (cons "STR" (hmrec:als-string (ssg-val e "wert")))))
|
||||
((= typ "step")
|
||||
(list (cons "STEP" (hmrec:als-string (ssg-val e "wert")))))
|
||||
(T nil)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; --- Alle Eingaben einer Kette in ein Vorwaerts-Journal uebersetzen ---
|
||||
;; Vorwaerts = Bau-Reihenfolge, genau so erwartet es vfl-journal-replay-start.
|
||||
(defun hmrec:journal-bauen (eingaben / out)
|
||||
(setq out '())
|
||||
(foreach e eingaben
|
||||
(foreach j (hmrec:eintrag->journal e)
|
||||
(setq out (cons j out))))
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
|
||||
;; --- Nicht verbrauchte Werte in der Replay-Queue zaehlen ---
|
||||
;; STEP-Marker zaehlen nicht mit: vfl-replay-pop ueberspringt sie, sie sind
|
||||
;; also kein offener Wert. Bleibt hier etwas uebrig, ist der Bau-Ablauf vom
|
||||
;; aufgezeichneten abgewichen (Desync) - das MUSS auffallen.
|
||||
(defun hmrec:queue-rest ( / n)
|
||||
(setq n 0)
|
||||
(if (and (boundp '*vfl-replay-queue*) *vfl-replay-queue*)
|
||||
(foreach e *vfl-replay-queue*
|
||||
(if (/= (car e) "STEP") (setq n (1+ n)))))
|
||||
n
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Eine Kette bauen
|
||||
;; ============================================================
|
||||
|
||||
;; Spielt das Journal einer Kette ab. Ablauf wie vfl-konvertiere-ent
|
||||
;; (Lisp/vf_linienzug.lsp): Queue setzen, vf-linienzug-modus laufen lassen,
|
||||
;; danach den entstandenen VF_n-Block einsammeln.
|
||||
;;
|
||||
;; vl-catch-all-apply ist hier BEWUSST gesetzt (anders als im interaktiven
|
||||
;; Editier-Pfad vfl-edit-ent): bricht eine Kette ab, muss die Testschleife
|
||||
;; ueber die restlichen Ketten weiterlaufen. Der Preis ist derselbe wie beim
|
||||
;; Batch-Konverter - der *error*-Handler in vf-linienzug-modus (der die
|
||||
;; Teil-Geometrie zu einem Block wickelt) kommt dann nicht zum Zug und die
|
||||
;; Teilgeometrie bleibt lose in der Zeichnung liegen. Der Testfall wird darum
|
||||
;; klar als "failed" samt Fehlertext gemeldet.
|
||||
;; *error* wird um den Aufruf herum gesichert: nach einem abgefangenen Abbruch
|
||||
;; steht dort noch der Handler-Lambda von vf-linienzug-modus (es kommt nicht
|
||||
;; mehr zu dessen (setq *error* old-error)) - ohne Restore wuerde die naechste
|
||||
;; Kette auf einem Handler mit toten dynamischen Bindungen aufsetzen.
|
||||
(defun hmrec:build-linienzug (kopf eingaben / tid journal anz offen err ent
|
||||
alt-error extra hl-fehler meldungen vor-ent)
|
||||
(setq tid (ssg-val kopf "test_id"))
|
||||
(setq journal (hmrec:journal-bauen eingaben))
|
||||
(setq anz (length journal))
|
||||
(princ (strcat "\n [LINIENZUG] " tid " -> " (itoa (length eingaben))
|
||||
" Eingaben, " (itoa anz) " Journal-Eintraege"))
|
||||
(if (ssg-val kopf "beschreibung")
|
||||
(princ (strcat "\n " (ssg-val kopf "beschreibung"))))
|
||||
(if (and (boundp '*hmrec-dbg*) *hmrec-dbg*)
|
||||
(progn
|
||||
(dbgmsg (strcat "KETTE " tid " JOURNAL " (vfl-journal->json journal)))
|
||||
(dbgflush)))
|
||||
|
||||
(cond
|
||||
;; Ohne Journal (oder ohne Startpunkt an erster Stelle) nichts bauen: ein
|
||||
;; Replay ohne PT wuerde den Startpunkt interaktiv erfragen und die ganze
|
||||
;; Kette an der falschen Stelle aufbauen.
|
||||
((null journal)
|
||||
(princ (strcat "\n FEHLER: " tid " - keine Eingaben lesbar."
|
||||
" Stehen die Zahlen-Arrays in EINER Zeile?"
|
||||
" (ssg-cfg-parse-array kann keine umgebrochenen lesen)"))
|
||||
(hmrec:result-json tid "linienzug" "failed" nil nil nil nil
|
||||
(list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa anz)))))
|
||||
|
||||
((/= (car (car journal)) "PT")
|
||||
(princ (strcat "\n FEHLER: " tid " - erster Eintrag ist kein"
|
||||
" Startpunkt (point_abs), nichts gebaut."))
|
||||
(hmrec:result-json tid "linienzug" "failed" nil nil nil nil
|
||||
(list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa anz)))))
|
||||
|
||||
(T
|
||||
;; Stand der Zeichnung VOR dem Bau: der fertige VF_-Block wird danach
|
||||
;; ab hier gesucht (siehe hmrec:last-insert-prefix-ab).
|
||||
(setq vor-ent (entlast))
|
||||
(setq alt-error *error*)
|
||||
(vfl-journal-reset)
|
||||
(vfl-journal-replay-start journal)
|
||||
(setq err (vl-catch-all-apply 'vf-linienzug-modus '()))
|
||||
(setq offen (hmrec:queue-rest))
|
||||
;; Diagnose VOR dem Reset sichern: vfl-journal-reset loescht
|
||||
;; *vfl-headless-fehler* und *vfl-meldungen* mit (beide gehoeren zum
|
||||
;; einzelnen Lauf, nicht zur Sitzung).
|
||||
(setq hl-fehler (hmrec:headless-fehler-text))
|
||||
(setq meldungen (if (boundp '*vfl-meldungen*) *vfl-meldungen* nil))
|
||||
(setq *error* alt-error)
|
||||
(vfl-journal-reset)
|
||||
(setq extra (list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa offen))
|
||||
(cons "prompts" (itoa (hmrec:prompts)))
|
||||
(cons "headless_fehler" (hmrec:json-str hl-fehler))
|
||||
(cons "meldungen" (hmrec:json-liste meldungen))))
|
||||
(cond
|
||||
;; Der Headless-Riegel der Produktion hat gegriffen: die Journal-Daten
|
||||
;; passen nicht zum tatsaechlichen Bau-Ablauf. Praeziser als "failed",
|
||||
;; weil hl-fehler Glied UND Eingabe-Nummer nennt.
|
||||
(hl-fehler
|
||||
(princ (strcat "\n DESYNC: " tid " - " hl-fehler))
|
||||
(hmrec:ent-json-status tid "linienzug" "desync"
|
||||
(hmrec:last-insert-prefix-ab "VF_" vor-ent) extra))
|
||||
((vl-catch-all-error-p err)
|
||||
(princ (strcat "\n FEHLER: " tid " abgebrochen -> "
|
||||
(vl-catch-all-error-message err)))
|
||||
(hmrec:result-json tid "linienzug" "failed" nil nil nil nil extra))
|
||||
;; Kette gebaut, aber der Ablauf hat nicht alle aufgezeichneten Werte
|
||||
;; abgerufen: die Geometrie kann nicht der Vorlage entsprechen.
|
||||
((> offen 0)
|
||||
(princ (strcat "\n FEHLER: " tid " - " (itoa offen)
|
||||
" Journal-Werte NICHT verbraucht (Ablauf weicht"
|
||||
" von der Aufzeichnung ab)."))
|
||||
(hmrec:ent-json-status tid "linienzug" "desync"
|
||||
(hmrec:last-insert-prefix-ab "VF_" vor-ent) extra))
|
||||
(T
|
||||
(setq ent (hmrec:last-insert-prefix-ab "VF_" vor-ent))
|
||||
(if ent
|
||||
(princ (strcat " -> Block " (cdr (assoc 2 (entget ent)))))
|
||||
(princ "\n FEHLER: kein VF_-Block gefunden"))
|
||||
(hmrec:ent-json tid "linienzug" ent extra)))))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Headless-Absicherung: Dialoge aus, leere Queue bricht ab
|
||||
;; ============================================================
|
||||
;; Die Journale sind vollstaendig, es darf also keine Live-Eingabe geben.
|
||||
;; Falls doch (Desync), wuerde ein getpoint/getstring den Testlauf blockieren
|
||||
;; und ein alert-Fenster ihn haengen lassen. Darum werden die Eingabe-
|
||||
;; Funktionen fuer die Dauer des Tests durch Abbruch-Stubs ersetzt (liefern
|
||||
;; nil = Abbruch, was vf-linienzug-modus sauber beendet) und alert auf die
|
||||
;; Konsole umgeleitet. Alles mit Save/Restore, siehe hmrec:stubs-aus.
|
||||
;; Die Stubs haben feste 1- bzw. 2-Arity (AutoLISP kennt keine optionalen
|
||||
;; Parameter) - genau wie die Mocks in test_linienzug.lsp. Ruft der Bau-Ablauf
|
||||
;; eine der Funktionen mit anderer Argumentzahl auf, gibt es einen
|
||||
;; Argumentfehler statt einer Warnung; beides landet im vl-catch-all-apply um
|
||||
;; vf-linienzug-modus und meldet die Kette als "failed". Wichtig ist nur, dass
|
||||
;; kein Prompt und kein Dialog den Lauf blockiert.
|
||||
(defun hmrec:stub-warnung (was)
|
||||
(setq *hmrec-prompts* (1+ (hmrec:prompts)))
|
||||
(princ (strcat "\n WARNUNG: unerwartete Live-Eingabe (" was
|
||||
") - Replay-Queue leer, Kette wird abgebrochen."))
|
||||
nil
|
||||
)
|
||||
(defun hmrec:stub-getpoint (a b) (hmrec:stub-warnung "getpoint"))
|
||||
(defun hmrec:stub-getstring (a) (hmrec:stub-warnung "getstring"))
|
||||
(defun hmrec:stub-getint (a) (hmrec:stub-warnung "getint"))
|
||||
(defun hmrec:stub-getreal (a) (hmrec:stub-warnung "getreal"))
|
||||
(defun hmrec:stub-alert (msg)
|
||||
(princ (strcat "\n ALERT (unterdrueckt): " (if msg msg "")))
|
||||
(princ)
|
||||
)
|
||||
|
||||
(defun hmrec:stubs-an ( / )
|
||||
(setq *hmrec-alt-getpoint* vfl-getpoint
|
||||
*hmrec-alt-getstring* getstring
|
||||
*hmrec-alt-getint* getint
|
||||
*hmrec-alt-getreal* getreal
|
||||
*hmrec-alt-alert* alert)
|
||||
(setq vfl-getpoint hmrec:stub-getpoint
|
||||
getstring hmrec:stub-getstring
|
||||
getint hmrec:stub-getint
|
||||
getreal hmrec:stub-getreal
|
||||
alert hmrec:stub-alert)
|
||||
;; Wizard-Dialoge aus (waehrend eines Replays greifen sie ohnehin nicht,
|
||||
;; siehe vfl-wizard-aktiv - aber nach einem Desync wuerde ein Dialog
|
||||
;; aufgehen und warten).
|
||||
(setq *hmrec-alt-wizard* (if (boundp '*vfl-wizard-mode*) *vfl-wizard-mode*))
|
||||
(setq *vfl-wizard-mode* nil)
|
||||
(if (car (atoms-family 1 '("SSG-GUI-AUS"))) (ssg-gui-aus))
|
||||
;; Headless-Riegel der Produktion einschalten: eine erschoepfte Replay-Queue
|
||||
;; ist dann ein harter, lokalisierter Abbruch statt eines stillen Rueckfalls
|
||||
;; auf Live-Eingabe (siehe vfl-headless-abbruch). Die Stubs oben bleiben als
|
||||
;; zweites Netz - sie fangen die Prompts ausserhalb der vfl-in-*-Wrapper
|
||||
;; (Modus 3, Nebenpfade) und zaehlen sie.
|
||||
(setq *hmrec-alt-headless* (if (boundp '*vfl-headless*) *vfl-headless*))
|
||||
(setq *vfl-headless* T)
|
||||
(setq *hmrec-prompts* 0)
|
||||
(princ)
|
||||
)
|
||||
|
||||
(defun hmrec:stubs-aus ( / )
|
||||
(setq vfl-getpoint *hmrec-alt-getpoint*
|
||||
getstring *hmrec-alt-getstring*
|
||||
getint *hmrec-alt-getint*
|
||||
getreal *hmrec-alt-getreal*
|
||||
alert *hmrec-alt-alert*)
|
||||
(setq *vfl-wizard-mode* *hmrec-alt-wizard*)
|
||||
(setq *vfl-headless* *hmrec-alt-headless*)
|
||||
(if (car (atoms-family 1 '("SSG-GUI-AN"))) (ssg-gui-an))
|
||||
(princ)
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Alle Ketten bauen
|
||||
;; ============================================================
|
||||
;; Eigene Funktion, damit der Aufrufer sie in vl-catch-all-apply klammern und
|
||||
;; die Eingabe-Stubs GARANTIERT zuruecknehmen kann (ein hier entkommender
|
||||
;; Fehler wuerde sonst getstring/getpoint/alert der laufenden BricsCAD-Sitzung
|
||||
;; ersetzt zuruecklassen).
|
||||
;; Rueckgabe: Ergebnis-Liste in Bau-Reihenfolge.
|
||||
(defun hmrec:ketten-bauen (ketten / kette res out tid fehlertext)
|
||||
(setq out '())
|
||||
(foreach kette ketten
|
||||
;; Dimensions-Override je Kette neu setzen: der Abbruch-Handler in
|
||||
;; vf-linienzug-modus setzt *ssg-ils-dim* zurueck, ein Abbruch wuerde die
|
||||
;; folgenden Ketten sonst in der falschen Dimension bauen.
|
||||
(if (and (boundp '*hmrec-dim-override*) *hmrec-dim-override*)
|
||||
(setq *ssg-ils-dim* *hmrec-dim-override*))
|
||||
;; Je Kette gefangen: ein Fehler NACH dem Bau (z.B. beim Auswerten des
|
||||
;; Ergebnisses) darf nicht die restlichen Ketten mitnehmen - sonst steht
|
||||
;; am Ende "0 OK, 0 Fehler" und man sieht nicht einmal, welche Kette
|
||||
;; gebaut wurde. Der Bau selbst ist in hmrec:build-linienzug bereits
|
||||
;; separat gefangen; dieser Riegel hier deckt alles danach ab.
|
||||
(setq tid (ssg-val (car kette) "test_id"))
|
||||
(setq res (vl-catch-all-apply 'hmrec:build-linienzug
|
||||
(list (car kette) (cadr kette))))
|
||||
(if (vl-catch-all-error-p res)
|
||||
(progn
|
||||
(setq fehlertext (vl-catch-all-error-message res))
|
||||
(princ (strcat "\n FEHLER: " tid " - Ausnahme: " fehlertext))
|
||||
(setq res (hmrec:result-json tid "linienzug" "failed"
|
||||
nil nil nil nil
|
||||
(list (cons "fehler_text"
|
||||
(hmrec:json-str fehlertext)))))))
|
||||
(if res (setq out (cons res out))))
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; JSON-Export der Ergebnisse
|
||||
;; ============================================================
|
||||
(defun hm_recformat:export-results (tests-out-dir / out-json f first)
|
||||
(if (null *hmrec-test-results*)
|
||||
(princ "\n Keine Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq out-json (strcat tests-out-dir "/hm_recformat_results.json"))
|
||||
(setq f (open out-json "w"))
|
||||
(if f
|
||||
(progn
|
||||
(write-line "[" f)
|
||||
(setq first T)
|
||||
(foreach r *hmrec-test-results*
|
||||
(if (not first) (write-line "," f))
|
||||
(write-line r f)
|
||||
(setq first nil))
|
||||
(write-line "]" f)
|
||||
(close f)
|
||||
(princ (strcat "\n Ergebnisse: " out-json)))
|
||||
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!")))))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; C:TEST_HM_RECFORMAT - Hauptbefehl
|
||||
;; ============================================================
|
||||
(defun c:TEST_HM_RECFORMAT ( / json-datei daten ketten results-list res
|
||||
anz-ok anz-fehler)
|
||||
;; Debug-Datei nur bei eingeschaltetem Schalter: (dbg-schalter-on "hm_recformat")
|
||||
(setq *hmrec-dbg*
|
||||
(and (car (atoms-family 1 '("DBG-SCHALTER-OPEN")))
|
||||
(dbg-schalter-open "hm_recformat" "hm_recformat.dbg" "DXFM_LOG")))
|
||||
|
||||
;; Benoetigte Feature-Module laden (Linienzug braucht Vario + Gefaelle)
|
||||
(ssg-ensure "VarioFoerderer")
|
||||
(ssg-ensure "Gefaellestrecke")
|
||||
|
||||
(if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS"))))
|
||||
(progn
|
||||
(princ "\n[TEST_HM_RECFORMAT] FEHLER: vf-linienzug-modus nicht geladen"
|
||||
" (VarioFoerderer/Gefaellestrecke laden).")
|
||||
(if *hmrec-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(ssg-start "TEST_HM_RECFORMAT" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
|
||||
(setvar "OSMODE" 0)
|
||||
(setvar "ATTREQ" 0)
|
||||
(setvar "ATTDIA" 0)
|
||||
|
||||
;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren
|
||||
(if (or (not *lib-initialized*) (null bogen-auf))
|
||||
(init-bibliothek))
|
||||
|
||||
;; Bau-Dimension festhalten. Die Eingabedaten sind dimensionsfrei; welche
|
||||
;; Blockvariante (_2D/_3D) verwendet wird, loest ssg-ils-dim-aktuell auf
|
||||
;; (Override *ssg-ils-dim* -> Umgebungsvariable DXFM_DIM -> Default "3D").
|
||||
(setq *hmrec-dimension* (ssg-ils-dim-aktuell))
|
||||
(princ (strcat "\n Bau-Dimension: " *hmrec-dimension*
|
||||
" (umschalten: TEST_HM_RECFORMAT_2D / TEST_HM_RECFORMAT_3D)"))
|
||||
|
||||
;; Testdaten laden
|
||||
(setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hm_recformat.json"))
|
||||
(if (not (findfile json-datei))
|
||||
(progn
|
||||
(princ (strcat "\n[TEST_HM_RECFORMAT] FEHLER: " json-datei " nicht gefunden!"))
|
||||
(ssg-end)
|
||||
(if *hmrec-dbg* (dbgclose))
|
||||
(exit)))
|
||||
(setq daten (ssg-load-json json-datei))
|
||||
(if (null daten)
|
||||
(progn
|
||||
(princ "\n[TEST_HM_RECFORMAT] FEHLER: Testdaten konnten nicht geladen werden.")
|
||||
(ssg-end)
|
||||
(if *hmrec-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(setq ketten (hmrec:gruppiere daten))
|
||||
(if (null ketten)
|
||||
(progn
|
||||
(princ "\n[TEST_HM_RECFORMAT] FEHLER: keine Kette gefunden - erwartet werden"
|
||||
" Kopf-Objekte mit \"test_id\" und Eingaben mit \"typ\".")
|
||||
(ssg-end)
|
||||
(if *hmrec-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(princ "\n\n================================================================")
|
||||
(princ "\n TEST_HM_RECFORMAT - 5 Linienzuege der Anlage HundM (polylines.dxf)")
|
||||
(princ (strcat "\n " (itoa (length daten)) " JSON-Objekte, "
|
||||
(itoa (length ketten)) " Ketten"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq anz-ok 0 anz-fehler 0)
|
||||
(hmrec:stubs-an)
|
||||
;; Stubs MUESSEN auch nach einem unerwarteten Fehler zurueckgenommen werden.
|
||||
(setq results-list (vl-catch-all-apply 'hmrec:ketten-bauen (list ketten)))
|
||||
(hmrec:stubs-aus)
|
||||
(if (vl-catch-all-error-p results-list)
|
||||
(progn
|
||||
(princ (strcat "\n[TEST_HM_RECFORMAT] FEHLER in der Kettenschleife: "
|
||||
(vl-catch-all-error-message results-list)))
|
||||
(setq results-list '())))
|
||||
|
||||
(foreach res results-list
|
||||
(if (vl-string-search "\"status\": \"executed\"" res)
|
||||
(setq anz-ok (1+ anz-ok))
|
||||
(setq anz-fehler (1+ anz-fehler))))
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, "
|
||||
(itoa anz-fehler) " Fehler"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq *hmrec-test-results* results-list)
|
||||
|
||||
(ssg-end)
|
||||
(princ "\n TEST_HM_RECFORMAT abgeschlossen.")
|
||||
(if *hmrec-dbg* (dbgclose))
|
||||
(princ)
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Dieselbe Anlage in einer bestimmten Dimension bauen
|
||||
;; ============================================================
|
||||
;; Die Eingabedaten (hm_recformat.json) sind dimensionsfrei - sie beschreiben die
|
||||
;; Eingaben, mit denen die Ketten gebaut wurden. Ob daraus flache 2D-Symbole
|
||||
;; oder 3D-Modelle werden, entscheidet allein die Blockvariante (_2D/_3D), die
|
||||
;; ssg-ils-dim-aktuell aufloest. Unsere Bibliothek hat beide, also laesst sich
|
||||
;; dieselbe Anlage ohne Datenaenderung in 2D ODER 3D aufbauen.
|
||||
;; *hmrec-dim-override* wird in der Kettenschleife je Kette neu gesetzt
|
||||
;; (siehe dort).
|
||||
(defun hmrec:mit-dimension (dim / alt ergebnis)
|
||||
(setq alt (if (boundp '*ssg-ils-dim*) *ssg-ils-dim*))
|
||||
(setq *ssg-ils-dim* dim)
|
||||
(setq *hmrec-dim-override* dim)
|
||||
(princ (strcat "\n[TEST_HM_RECFORMAT] Dimension voruebergehend auf " dim
|
||||
" gesetzt."))
|
||||
(setq ergebnis (c:TEST_HM_RECFORMAT))
|
||||
;; Override wieder auf den vorherigen Stand - sonst baut ein folgender Test
|
||||
;; unbemerkt in der hier gewaehlten Dimension weiter.
|
||||
(setq *ssg-ils-dim* alt)
|
||||
(setq *hmrec-dim-override* nil)
|
||||
(princ (strcat "\n[TEST_HM_RECFORMAT] Dimension zurueckgesetzt auf "
|
||||
(if alt alt "Umgebung/Default") "."))
|
||||
ergebnis
|
||||
)
|
||||
|
||||
(defun c:TEST_HM_RECFORMAT_2D ( / ) (hmrec:mit-dimension "2D"))
|
||||
(defun c:TEST_HM_RECFORMAT_3D ( / ) (hmrec:mit-dimension "3D"))
|
||||
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_hm_recformat.py - Validiert die 5 VarioFoerderer-Linienzuege der Anlage HundM.
|
||||
|
||||
Zwei Ebenen:
|
||||
1. Die Testdaten selbst (tests/testdata/hm_recformat.json): Struktur der
|
||||
Eingabe-Journale und Lesbarkeit durch den LISP-Parser.
|
||||
2. Die von TEST_HM_RECFORMAT (test_hm_recformat.lsp) erzeugten Ergebnisse
|
||||
(output/hmrec_results.json + output/hm_recformat_tests.dxf): jede Kette
|
||||
gebaut, kein Journal-Wert offen, Attribute passen zu den Eingaben.
|
||||
|
||||
Die Testdaten stammen aus lib/vf_journal_export.py (Journale der VF_n-Bloecke
|
||||
aus data/polylines.dxf, XDATA-App SSG_VF_EDIT) und nicht aus Handarbeit -
|
||||
Schema siehe tests/testdata/object_data.md Abschnitt 8.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
EINGABE_TYPEN = ("point_abs", "point_rel", "real", "int", "string", "step")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Hilfsfunktionen: flaches JSON in Ketten gruppieren
|
||||
# ============================================================
|
||||
# Gleiche Regel wie hmrec:gruppiere in test_hm_recformat.lsp: ein Objekt mit
|
||||
# "test_id" ist ein Kopf und beginnt eine Kette, die folgenden Objekte mit
|
||||
# "typ" sind ihre Eingaben.
|
||||
|
||||
def _ketten(testdata):
|
||||
ketten = []
|
||||
for obj in testdata:
|
||||
if "test_id" in obj:
|
||||
ketten.append((obj, []))
|
||||
elif "typ" in obj and ketten:
|
||||
ketten[-1][1].append(obj)
|
||||
return ketten
|
||||
|
||||
|
||||
def _journal_laenge(eingaben):
|
||||
"""Anzahl der Journal-Eintraege, die der Treiber daraus baut: ein
|
||||
point_rel mit "hz" wird zu ZWEI Eintraegen (DL + REAL)."""
|
||||
return sum(2 if (e["typ"] == "point_rel" and "hz" in e) else 1
|
||||
for e in eingaben)
|
||||
|
||||
|
||||
def _pfad(*teile):
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), *teile)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Testdaten-Pruefung (hm_recformat.json selbst)
|
||||
# ============================================================
|
||||
|
||||
class TestAufzeichnungTestdaten:
|
||||
|
||||
def test_fuenf_ketten(self, hmrec_testdata):
|
||||
"""Die Anlage hat fuenf am Kreisel haengende Linienzuege."""
|
||||
ketten = _ketten(hmrec_testdata)
|
||||
assert len(ketten) == 5, \
|
||||
f"{len(ketten)} Ketten in hm_recformat.json, erwartet 5"
|
||||
|
||||
def test_jedes_objekt_zuordenbar(self, hmrec_testdata):
|
||||
"""Kein Objekt darf zwischen Kopf und Eingabe fallen - sonst
|
||||
verschluckt hmrec:gruppiere es stillschweigend."""
|
||||
for i, obj in enumerate(hmrec_testdata):
|
||||
assert ("test_id" in obj) or ("typ" in obj), \
|
||||
f"Objekt {i} ist weder Kopf noch Eingabe: {sorted(obj)}"
|
||||
|
||||
def test_kopf_felder(self, hmrec_testdata):
|
||||
for kopf, _ in _ketten(hmrec_testdata):
|
||||
assert kopf["test_id"].startswith("VF_"), kopf["test_id"]
|
||||
# "typ" wuerde den Kopf zu einer Eingabe machen (siehe
|
||||
# hmrec:gruppiere) - der Anlagetyp heisst darum "anlagetyp".
|
||||
assert "typ" not in kopf, \
|
||||
f'{kopf["test_id"]}: Kopf darf kein Feld "typ" tragen'
|
||||
assert kopf["anlagetyp"] == "linienzug", kopf["test_id"]
|
||||
assert kopf["modus"] == 1, kopf["test_id"]
|
||||
assert kopf["block"].startswith("VF_"), kopf["block"]
|
||||
assert len(kopf["startpunkt_mm"]) == 3, kopf["test_id"]
|
||||
|
||||
def test_anzahl_eingaben_stimmt(self, hmrec_testdata):
|
||||
"""anzahl_eingaben ist die Selbstauskunft des Kopfes - weicht sie ab,
|
||||
fehlen Eingaben (z.B. durch eine Handaenderung)."""
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
assert kopf["anzahl_eingaben"] == len(eingaben), \
|
||||
f'{kopf["test_id"]}: anzahl_eingaben=' \
|
||||
f'{kopf["anzahl_eingaben"]}, gezaehlt {len(eingaben)}'
|
||||
|
||||
def test_eingabe_typen_bekannt(self, hmrec_testdata):
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
for i, e in enumerate(eingaben):
|
||||
assert e["typ"] in EINGABE_TYPEN, \
|
||||
f'{kopf["test_id"]} Eingabe {i}: unbekannter Typ ' \
|
||||
f'{e["typ"]}'
|
||||
assert e.get("kommentar"), \
|
||||
f'{kopf["test_id"]} Eingabe {i}: kein Kommentar'
|
||||
|
||||
def test_kette_beginnt_mit_startpunkt(self, hmrec_testdata):
|
||||
"""Erste Eingabe = absoluter Startpunkt, zweite = Starthoehe. Fehlt
|
||||
der Punkt, wuerde das Replay ihn interaktiv erfragen und die Kette an
|
||||
der falschen Stelle aufbauen (siehe hmrec:build-linienzug)."""
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
assert eingaben[0]["typ"] == "point_abs", kopf["test_id"]
|
||||
assert eingaben[1]["typ"] == "real", kopf["test_id"]
|
||||
punkt = eingaben[0]["wert"]
|
||||
assert len(punkt) == 3, kopf["test_id"]
|
||||
assert punkt == kopf["startpunkt_mm"], \
|
||||
f'{kopf["test_id"]}: point_abs {punkt} != startpunkt_mm ' \
|
||||
f'{kopf["startpunkt_mm"]}'
|
||||
assert eingaben[1]["wert"] == punkt[2], \
|
||||
f'{kopf["test_id"]}: Starthoehe {eingaben[1]["wert"]} != ' \
|
||||
f'Z des Startpunkts {punkt[2]}'
|
||||
|
||||
def test_hz_nur_beim_ersten_segment(self, hmrec_testdata):
|
||||
"""vfl-in-abstand journalisiert die Fahrtrichtung NUR beim ersten
|
||||
Segment der Kette; jedes weitere erbt sie vom Vorgaenger. Ein "hz" an
|
||||
spaeterer Stelle waere ein Wert zu viel in der Replay-Queue und
|
||||
verschiebt alle folgenden Antworten (Desync)."""
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
rel = [e for e in eingaben if e["typ"] == "point_rel"]
|
||||
assert rel, f'{kopf["test_id"]}: kein Segment'
|
||||
assert "hz" in rel[0], \
|
||||
f'{kopf["test_id"]}: erstes Segment ohne hz'
|
||||
for i, e in enumerate(rel[1:], start=2):
|
||||
assert "hz" not in e, \
|
||||
f'{kopf["test_id"]}: Segment {i} traegt hz - nur das ' \
|
||||
f'erste Segment darf eines haben'
|
||||
|
||||
def test_segmentlaengen_positiv(self, hmrec_testdata):
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
for e in eingaben:
|
||||
if e["typ"] == "point_rel":
|
||||
assert e["dL"] > 0, f'{kopf["test_id"]}: dL={e["dL"]}'
|
||||
|
||||
def test_menue_antworten_sind_strings(self, hmrec_testdata):
|
||||
"""Menue-Antworten muessen Strings bleiben ("1"/"2"/"90"/"30"): der
|
||||
Produktionscode vergleicht mit (= antwort "1"), eine Zahl wuerde
|
||||
stumm in den Default-Zweig laufen."""
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
for i, e in enumerate(eingaben):
|
||||
if e["typ"] in ("string", "step"):
|
||||
assert isinstance(e["wert"], str), \
|
||||
f'{kopf["test_id"]} Eingabe {i}: {e["wert"]!r} ist ' \
|
||||
f'kein String'
|
||||
|
||||
def test_glieder_vorhanden(self, hmrec_testdata):
|
||||
"""Jede Kette hat Glied-Marker (STEP) - sie sind der Anker fuer den
|
||||
Sektions-/Glied-Edit und die Kurzbeschreibung."""
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
glieder = [e["wert"] for e in eingaben if e["typ"] == "step"]
|
||||
assert glieder, f'{kopf["test_id"]}: kein STEP-Marker'
|
||||
for g in glieder:
|
||||
assert g in ("Linie-GF", "Linie-VF", "Linie", "GF-Bogen",
|
||||
"Horizontal-VF", "Vario-Kurve", "ES"), \
|
||||
f'{kopf["test_id"]}: unbekanntes Glied {g}'
|
||||
|
||||
def test_arrays_einzeilig(self):
|
||||
"""Jedes Array muss komplett in EINER Zeile stehen.
|
||||
|
||||
ssg-parse-json-array (Lisp/ssg_core.lsp) liest die Datei zeilenweise,
|
||||
und ssg-cfg-parse-array braucht das ganze Array in einem String. Ein
|
||||
von json.dump(indent=2) umgebrochenes Array wird zu nil - der Treiber
|
||||
baut die Kette dann nicht an ihrer Position. Das ist genau der
|
||||
Fehler, der die Anlage einmal komplett am Ursprung aufgebaut hat, und
|
||||
er ist in der Zeichnung nicht als Datenfehler zu erkennen.
|
||||
"""
|
||||
with open(_pfad("testdata", "hm_recformat.json"), encoding="utf-8") as fh:
|
||||
for nr, zeile in enumerate(fh, start=1):
|
||||
offen = zeile.count("[") - zeile.count("]")
|
||||
# Die aeusserste Klammer des Dokuments darf allein stehen.
|
||||
if zeile.strip() in ("[", "]"):
|
||||
continue
|
||||
assert offen == 0, \
|
||||
f"Zeile {nr}: Array ueber mehrere Zeilen umgebrochen - " \
|
||||
f"ssg-cfg-parse-array kann das nicht lesen: {zeile.strip()!r}"
|
||||
|
||||
def test_flach_fuer_lisp_parser(self, hmrec_testdata):
|
||||
"""Die Datei muss FLACH bleiben.
|
||||
|
||||
ssg-load-json / ssg-parse-json-array behandelt jedes "{" als
|
||||
Objektanfang und jedes "}"/"}," als Objektende - es kennt keine
|
||||
Verschachtelung. Ein Objekt in einem Objekt (oder ein Array von
|
||||
Objekten wie "eingaben": [...]) wuerde das umgebende Objekt
|
||||
verschlucken. Dieser Test zaehlt die Objekte so, wie der LISP-Parser
|
||||
sie zaehlen wuerde, und vergleicht mit dem echten JSON.
|
||||
"""
|
||||
objekte = 0
|
||||
with open(_pfad("testdata", "hm_recformat.json"), encoding="utf-8") as fh:
|
||||
for zeile in fh:
|
||||
gestrippt = zeile.strip()
|
||||
if gestrippt == "{":
|
||||
objekte += 1
|
||||
elif gestrippt in ("}", "},"):
|
||||
continue
|
||||
elif gestrippt.startswith('"'):
|
||||
# Ein Wert, der ein Objekt oder eine Objektliste eroeffnet,
|
||||
# macht die Datei fuer den LISP-Parser unlesbar.
|
||||
assert not gestrippt.endswith(("{", "[")), \
|
||||
f"verschachtelter Wert: {gestrippt!r}"
|
||||
assert objekte == len(hmrec_testdata), \
|
||||
f"LISP-Parser saehe {objekte} Objekte, json.load sieht " \
|
||||
f"{len(hmrec_testdata)}"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Ergebnis-Pruefung (results.json vs. testdata)
|
||||
# ============================================================
|
||||
|
||||
class TestAufzeichnungResults:
|
||||
|
||||
def test_je_kette_ein_ergebnis(self, hmrec_testdata, hmrec_results):
|
||||
soll = [k["test_id"] for k, _ in _ketten(hmrec_testdata)]
|
||||
ist = [r["test_id"] for r in hmrec_results]
|
||||
assert ist == soll, f"Ergebnisse {ist}, erwartet {soll}"
|
||||
for r in hmrec_results:
|
||||
assert r["kind"] == "linienzug", f'{r["test_id"]}: {r["kind"]}'
|
||||
|
||||
def test_status_executed(self, hmrec_results):
|
||||
""""desync" (Journal-Werte nicht verbraucht) und "failed" (Abbruch)
|
||||
sind beide echte Fehler - siehe hmrec:build-linienzug."""
|
||||
for r in hmrec_results:
|
||||
assert r["status"] == "executed", \
|
||||
f'{r["test_id"]}: Status={r["status"]}'
|
||||
|
||||
def test_journal_vollstaendig_verbraucht(self, hmrec_testdata,
|
||||
hmrec_results):
|
||||
"""Alle aufgezeichneten Werte muessen im Bau abgerufen worden sein.
|
||||
Bleibt etwas offen, ist der Ablauf von der Aufzeichnung abgewichen und
|
||||
die Geometrie kann der Vorlage nicht entsprechen."""
|
||||
soll = {k["test_id"]: _journal_laenge(e)
|
||||
for k, e in _ketten(hmrec_testdata)}
|
||||
for r in hmrec_results:
|
||||
assert "eingaben_offen" in r, (
|
||||
f'{r["test_id"]}: kein eingaben_offen im Ergebnis - die Kette '
|
||||
f'ist vor der Auswertung abgebrochen (status='
|
||||
f'{r.get("status")!r}, {r.get("fehler_text")})')
|
||||
assert r["eingaben_offen"] == 0, \
|
||||
f'{r["test_id"]}: {r["eingaben_offen"]} Journal-Werte offen'
|
||||
assert r["eingaben_gesamt"] == soll[r["test_id"]], \
|
||||
f'{r["test_id"]}: {r["eingaben_gesamt"]} Journal-Eintraege ' \
|
||||
f'abgespielt, erwartet {soll[r["test_id"]]}'
|
||||
|
||||
def test_keine_live_eingabe(self, hmrec_results):
|
||||
"""Kein Prompt, kein Dialog: der Treiber schaltet *vfl-headless* ein
|
||||
(harter Abbruch statt stillem Rueckfall auf Live-Eingabe, siehe
|
||||
vfl-headless-abbruch in Lisp/vf_linienzug.lsp) UND ersetzt zusaetzlich
|
||||
getpoint/getstring/getint/getreal durch zaehlende Stubs. Beide Wege
|
||||
muessen leer bleiben - "prompts" ist der positive Beweis, dass keine
|
||||
Eingabe ausserhalb der vfl-in-*-Wrapper erreicht wurde."""
|
||||
for r in hmrec_results:
|
||||
if "prompts" in r:
|
||||
assert r["prompts"] == 0, \
|
||||
f'{r["test_id"]}: {r["prompts"]} Live-Eingabe(n) erreicht'
|
||||
assert not r.get("headless_fehler"), \
|
||||
f'{r["test_id"]}: Headless-Abbruch bei {r["headless_fehler"]}'
|
||||
|
||||
def test_keine_geometrie_meldungen(self, hmrec_results):
|
||||
"""*vfl-meldungen* sammelt die abgewiesenen Sektionen (frueher
|
||||
blockierende alert-Fenster, jetzt vfl-meldung). Eine Meldung heisst:
|
||||
ein Glied liess sich geometrisch nicht bauen - der Nachbau weicht dann
|
||||
von der Vorlage ab, auch wenn das Journal aufgeht."""
|
||||
for r in hmrec_results:
|
||||
assert not r.get("meldungen"), \
|
||||
f'{r["test_id"]}: Meldungen aus dem Bau: {r["meldungen"]}'
|
||||
|
||||
def test_vf_block_erzeugt(self, hmrec_results):
|
||||
for r in hmrec_results:
|
||||
assert r["block_name"].startswith("VF_"), \
|
||||
f'{r["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block'
|
||||
assert r["block_handle"], f'{r["test_id"]}: kein Handle'
|
||||
|
||||
def test_dimension_einheitlich(self, hmrec_results):
|
||||
"""Alle Ergebnisse eines Laufs stammen aus EINER Bau-Dimension.
|
||||
|
||||
Die Eingabedaten sind dimensionsfrei; ob _2D- oder _3D-Blockvarianten
|
||||
verwendet werden, loest ssg-ils-dim-aktuell auf. Ein Lauf, in dem beide
|
||||
vorkommen, waere ein halb umgeschalteter Zustand (z.B. weil ein
|
||||
vorheriger Test *ssg-ils-dim* nicht zurueckgesetzt hat).
|
||||
"""
|
||||
dims = {r.get("dimension") for r in hmrec_results}
|
||||
assert len(dims) == 1, f"gemischte Bau-Dimensionen im Lauf: {dims}"
|
||||
dim = dims.pop()
|
||||
assert dim in ("2D", "3D"), \
|
||||
f'Bau-Dimension "{dim}" ist weder 2D noch 3D'
|
||||
|
||||
def test_startpunkt_getroffen(self, hmrec_testdata, hmrec_results):
|
||||
"""Der VF_n-Block wird mit dem Kettenstartpunkt als Basispunkt
|
||||
gewickelt (ssg-block-wrap-welt in vfl-block-erstellen) - sein
|
||||
Einfuegepunkt muss also der Startpunkt aus den Eingaben sein."""
|
||||
soll = {k["test_id"]: k["startpunkt_mm"]
|
||||
for k, _ in _ketten(hmrec_testdata)}
|
||||
for r in hmrec_results:
|
||||
p = soll[r["test_id"]]
|
||||
ist = r["insert_point"]
|
||||
for achse, i in (("X", 0), ("Y", 1), ("Z", 2)):
|
||||
assert abs(ist[i] - p[i]) < 1.0, \
|
||||
f'{r["test_id"]}: Einfuegepunkt {achse} {ist[i]} != ' \
|
||||
f'{p[i]}'
|
||||
|
||||
def test_as_es_attribute_aus_eingaben(self, hmrec_testdata,
|
||||
hmrec_results):
|
||||
"""WINKEL_AS/SEITE_AS und WINKEL_ES/SEITE_ES muessen die Antworten aus
|
||||
dem Journal wiedergeben (AS steht in der Praeambel, ES im ES-Glied)."""
|
||||
seiten = {"1": "links", "2": "rechts"}
|
||||
nach_id = {r["test_id"]: r for r in hmrec_results}
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
r = nach_id[kopf["test_id"]]
|
||||
attrs = r["actual_attributes"]
|
||||
# Praeambel: [0] Punkt, [1] Hoehe, [2] AS setzen, [3] Winkel,
|
||||
# [4] Seite
|
||||
if eingaben[2]["wert"] == "1":
|
||||
assert attrs.get("WINKEL_AS") == eingaben[3]["wert"], \
|
||||
f'{kopf["test_id"]}: WINKEL_AS=' \
|
||||
f'{attrs.get("WINKEL_AS")}, erwartet {eingaben[3]["wert"]}'
|
||||
assert attrs.get("SEITE_AS") == seiten[eingaben[4]["wert"]], \
|
||||
f'{kopf["test_id"]}: SEITE_AS={attrs.get("SEITE_AS")}'
|
||||
# ES: die beiden Strings direkt nach dem STEP-Marker "ES"
|
||||
for i, e in enumerate(eingaben):
|
||||
if e["typ"] == "step" and e["wert"] == "ES":
|
||||
assert attrs.get("WINKEL_ES") == eingaben[i + 1]["wert"], \
|
||||
f'{kopf["test_id"]}: WINKEL_ES=' \
|
||||
f'{attrs.get("WINKEL_ES")}'
|
||||
assert attrs.get("SEITE_ES") == \
|
||||
seiten[eingaben[i + 2]["wert"]], \
|
||||
f'{kopf["test_id"]}: SEITE_ES={attrs.get("SEITE_ES")}'
|
||||
break
|
||||
|
||||
def test_vario_kurven_gezaehlt(self, hmrec_testdata, hmrec_results):
|
||||
"""Jede Vario-Kurve im Journal muss in den VF_Bogen_*-Attributen
|
||||
auftauchen (vfl-insert-vario-kurve-block zaehlt sie mit)."""
|
||||
nach_id = {r["test_id"]: r for r in hmrec_results}
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
soll = sum(1 for e in eingaben
|
||||
if e["typ"] == "step" and e["wert"] == "Vario-Kurve")
|
||||
attrs = nach_id[kopf["test_id"]]["actual_attributes"]
|
||||
ist = sum(int(attrs.get(f"VF_Bogen_{lage}_{w}", 0) or 0)
|
||||
for lage in ("A", "I") for w in (90, 60, 30))
|
||||
assert ist == soll, \
|
||||
f'{kopf["test_id"]}: {ist} Vario-Kurven in den Attributen, ' \
|
||||
f'{soll} im Journal'
|
||||
|
||||
def test_hoehe_von_ist_starthoehe(self, hmrec_testdata,
|
||||
hmrec_results):
|
||||
nach_id = {r["test_id"]: r for r in hmrec_results}
|
||||
for kopf, eingaben in _ketten(hmrec_testdata):
|
||||
attrs = nach_id[kopf["test_id"]]["actual_attributes"]
|
||||
if "HOEHE_VON_mm" not in attrs:
|
||||
continue
|
||||
assert abs(int(attrs["HOEHE_VON_mm"]) - eingaben[1]["wert"]) <= 1, \
|
||||
f'{kopf["test_id"]}: HOEHE_VON_mm={attrs["HOEHE_VON_mm"]}, ' \
|
||||
f'Starthoehe {eingaben[1]["wert"]}'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DXF Geometrie-Pruefung (ezdxf)
|
||||
# ============================================================
|
||||
|
||||
class TestAufzeichnungGeometry:
|
||||
|
||||
def test_block_anzahl(self, hmrec_dxf, hmrec_testdata):
|
||||
"""Je Kette muss ein VF_-Block im Modelspace stehen."""
|
||||
n_vf = sum(1 for e in hmrec_dxf.modelspace()
|
||||
if e.dxftype() == "INSERT" and e.dxf.name.startswith("VF_"))
|
||||
soll = len(_ketten(hmrec_testdata))
|
||||
assert n_vf >= soll, f"VF_: {n_vf} < {soll}"
|
||||
|
||||
def test_handles_im_modelspace(self, hmrec_dxf, hmrec_results):
|
||||
"""Jedes gebaute Block-Handle (executed) liegt im Modelspace."""
|
||||
handles = {e.dxf.handle for e in hmrec_dxf.modelspace()
|
||||
if e.dxftype() == "INSERT"}
|
||||
for r in hmrec_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 ' \
|
||||
f'Modelspace'
|
||||
+79
-636
@@ -1,41 +1,32 @@
|
||||
;; ============================================================
|
||||
;; test_hundm05.lsp - Integrationstest: baut die 5 VarioFoerderer-Linienzuege
|
||||
;; der Anlage HundM (Kreisel mit fuenf daran haengenden Ketten) nach.
|
||||
;; der Anlage HundM (Kreisel mit fuenf daran haengenden Ketten) aus ihrer
|
||||
;; SPEC nach.
|
||||
;;
|
||||
;; Datenquelle: tests/testdata/hundm05.json, erzeugt von
|
||||
;; python lib/vf_journal_export.py data/polylines.dxf \
|
||||
;; tests/testdata/hundm05.json --csv results/HundM_export.csv
|
||||
;; Das sind die EINGABE-JOURNALE der echten Ketten - ausgelesen aus der XDATA
|
||||
;; (App SSG_VF_EDIT, Marker "linienzug") der VF_n-Bloecke in data/polylines.dxf,
|
||||
;; also genau die Werte, mit denen die Ketten in BricsCAD gebaut wurden.
|
||||
;; Datenquelle: tests/testdata/hundm05.json - die Ketten im Spec-Format
|
||||
;; (Domaenenwerte statt Menue-Codes, siehe Lisp/vf_spec.lsp und
|
||||
;; doc/variofoerderer.md Abschnitt 8b). Erzeugt aus den echten
|
||||
;; Aufzeichnungen mit
|
||||
;; python lib/vf_spec_export.py tests/testdata/hm_recformat.json \
|
||||
;; tests/testdata/hundm05.json
|
||||
;; und damit nachweislich dasselbe Journal wie die Vorlage
|
||||
;; (Rundlauf-Beweis in tests/test_vf_spec.py). Die Datei ist die Stelle, an
|
||||
;; der weitere Ketten der Anlage nachgetragen werden.
|
||||
;;
|
||||
;; JSON-Aufbau (FLACH, weil ssg-load-json zeilenweise liest - Details in
|
||||
;; tests/testdata/object_data.md Abschnitt 8):
|
||||
;; Kopf-Objekt (traegt "test_id"): test_id, anlagetyp, modus,
|
||||
;; anzahl_eingaben, block, dxf_handle, startpunkt_mm,
|
||||
;; rotation_grad, beschreibung, sivas_* , erwartung_hinweis
|
||||
;; Eingabe-Objekt (traegt "typ"): point_abs | point_rel | real | int |
|
||||
;; string | step - in exakter Frage-Reihenfolge
|
||||
;; Ein Kopf-Objekt beginnt eine Kette, die folgenden Eingabe-Objekte gehoeren
|
||||
;; dazu (hundm05:gruppiere).
|
||||
;; Dieser Test hat KEINE eigene Bau-Logik: er ruft vsp-bau-datei aus
|
||||
;; Lisp/vf_spec.lsp - denselben Weg, den auch der Befehl VF_SPEC_BAU nimmt.
|
||||
;; Es gibt also genau einen Code-Pfad, der aus einer Spec Geometrie erzeugt.
|
||||
;;
|
||||
;; WARUM REPLAY UND KEINE EINGABE-MOCKS (Unterschied zu test_linienzug.lsp):
|
||||
;; test_linienzug.lsp ersetzt getpoint/getstring/getint/getreal durch Mocks und
|
||||
;; rechnet Punkte aus "dL"+"hz" selbst aus. Das geht nur, weil
|
||||
;; linienzug_tests.json zu JEDEM Segment ein "hz" mitfuehrt. Ein echtes Journal
|
||||
;; tut das nicht: vfl-in-abstand journalisiert die Fahrtrichtung NUR beim
|
||||
;; allerersten Segment der Kette, jedes weitere erbt sie vom Vorgaenger. Ein
|
||||
;; Mock kann diese geerbte Richtung nicht kennen - er wuerde einen Punkt in der
|
||||
;; falschen Richtung liefern und die Projektion in vfl-neue-linie-messen ergaebe
|
||||
;; eine falsche (bis auf 0 zusammenfallende) Laenge. Darum wird hier derselbe
|
||||
;; Weg genommen, den die Produktion beim Editieren/Konvertieren nutzt:
|
||||
;; vfl-journal-replay-start + vf-linienzug-modus (siehe vfl-konvertiere-ent in
|
||||
;; Lisp/vf_linienzug.lsp). Die vfl-in-*-Wrapper ziehen ihre Werte dann direkt
|
||||
;; aus der Replay-Queue - inklusive der Sonderregel fuer "hz".
|
||||
;; ABGRENZUNG zu TEST_HM_RECFORMAT (tests/test_hm_recformat.lsp): dort werden
|
||||
;; dieselben Ketten aus dem ROHEN Journal der Kundenzeichnung gebaut. Das ist
|
||||
;; der Regressionstest fuer das Aufzeichnungsformat (bleibt ein bestehender
|
||||
;; VF_n-Block abspielbar - Doppelklick-Edit, 2D/3D-Konvertierung?) und
|
||||
;; deshalb dort auf "disabled": true, um nicht zweimal dieselbe Geometrie zu
|
||||
;; bauen.
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (VarioFoerderer inkl. vf_linienzug, Gefaellestrecke,
|
||||
;; ssg_core, ssg_dbg)
|
||||
;; Voraussetzung:
|
||||
;; - SSG_LIB geladen (VarioFoerderer inkl. vf_linienzug + vf_spec,
|
||||
;; Gefaellestrecke, ssg_core, ssg_dbg)
|
||||
;; - Umgebungsvariable DXFMAKRO gesetzt
|
||||
;;
|
||||
;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"):
|
||||
@@ -47,621 +38,73 @@
|
||||
;; TEST_HUNDM05 ; in der aktuellen Dimension (ssg-ils-dim-aktuell)
|
||||
;; TEST_HUNDM05_2D ; erzwungen 2D
|
||||
;; TEST_HUNDM05_3D ; erzwungen 3D
|
||||
;;
|
||||
;; Debug-Datei (nur wenn eingeschaltet): (dbg-schalter-on "hundm05")
|
||||
;; schreibt hundm05.dbg ins DXFM_LOG-Verzeichnis (Journal je Kette als JSON).
|
||||
;; ============================================================
|
||||
|
||||
(if (not (boundp '*hundm05-records*)) (setq *hundm05-records* nil))
|
||||
|
||||
(defun hundm05:spec-datei ()
|
||||
(strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json"))
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Ergebnis-JSON
|
||||
;; Testlauf
|
||||
;; ============================================================
|
||||
|
||||
;; --- JSON-Hilfen fuer Diagnosefelder (Strings/String-Listen) ---
|
||||
;; Die extra-Felder in hundm05:result-json werden ROH in das JSON gesetzt,
|
||||
;; ein Text muss also selbst seine Anfuehrungszeichen mitbringen und die
|
||||
;; JSON-Sonderzeichen maskieren.
|
||||
(defun hundm05:json-escape (s / out i c)
|
||||
(setq out "" i 1)
|
||||
(while (<= i (strlen s))
|
||||
(setq c (substr s i 1))
|
||||
(setq out (cond ((= c "\"") (strcat out "\\\""))
|
||||
((= c "\\") (strcat out "\\\\"))
|
||||
((= c "\n") (strcat out " "))
|
||||
(T (strcat out c))))
|
||||
(setq i (1+ i)))
|
||||
out)
|
||||
|
||||
(defun hundm05:json-str (s)
|
||||
(if s (strcat "\"" (hundm05:json-escape s) "\"") "null"))
|
||||
|
||||
(defun hundm05:json-liste (lst / s first)
|
||||
(setq s "[" first T)
|
||||
(foreach x lst
|
||||
(if (not first) (setq s (strcat s ", ")))
|
||||
(setq s (strcat s (hundm05:json-str x)) first nil))
|
||||
(strcat s "]"))
|
||||
|
||||
;; --- Headless-Diagnose der Produktion lesen ---
|
||||
;; vfl-headless-abbruch (Lisp/vf_linienzug.lsp) legt bei erschoepfter
|
||||
;; Replay-Queue Art der Eingabe, Glied-Nummer und Eingabe-Nummer ab. Das ist
|
||||
;; die eigentliche Desync-Auskunft - ohne sie weiss man nur, DASS Werte offen
|
||||
;; blieben, nicht WO der Ablauf abgewichen ist.
|
||||
(defun hundm05:headless-fehler-text ( / f)
|
||||
(if (and (boundp '*vfl-headless-fehler*) *vfl-headless-fehler*)
|
||||
(progn
|
||||
(setq f *vfl-headless-fehler*)
|
||||
(strcat (cdr (assoc "was" f)) " @ " (cdr (assoc "ort" f))))
|
||||
nil))
|
||||
|
||||
;; Zaehler der trotz allem erreichten Live-Eingaben (muss 0 bleiben).
|
||||
(defun hundm05:prompts ()
|
||||
(if (and (boundp '*hundm05-prompts*) *hundm05-prompts*) *hundm05-prompts* 0))
|
||||
|
||||
|
||||
;; "dimension" haelt fest, in welcher Dimension (2D/3D) gebaut wurde. Das ist
|
||||
;; KEINE Eigenschaft der Eingabedaten: hundm05.json beschreibt die Ketten
|
||||
;; dimensionsfrei. Womit unsere Makros bauen, entscheidet ssg-ils-dim-aktuell -
|
||||
;; unsere Bibliothek hat von jedem Block eine _2D- und eine _3D-Variante.
|
||||
;; extra: Alist zusaetzlicher Felder ((name . wert-string)), wird als
|
||||
;; JSON-Zahlen/Strings so uebernommen, wie sie uebergeben werden.
|
||||
(defun hundm05:result-json (test-id kind status block-name block-handle
|
||||
insert-point attribs extra /
|
||||
json tag val first)
|
||||
(setq json (strcat " {\n"
|
||||
" \"test_id\": \"" test-id "\",\n"
|
||||
" \"kind\": \"" kind "\",\n"
|
||||
" \"status\": \"" status "\",\n"
|
||||
" \"dimension\": \""
|
||||
(if (and (boundp '*hundm05-dimension*) *hundm05-dimension*)
|
||||
*hundm05-dimension* "?") "\",\n"
|
||||
" \"block_name\": \"" (if block-name block-name "") "\",\n"
|
||||
" \"block_handle\": \"" (if block-handle block-handle "") "\",\n"
|
||||
" \"insert_point\": ["
|
||||
(if insert-point
|
||||
(strcat (rtos (car insert-point) 2 1) ", "
|
||||
(rtos (cadr insert-point) 2 1) ", "
|
||||
(rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1))
|
||||
"0.0, 0.0, 0.0")
|
||||
"],\n"))
|
||||
(foreach f extra
|
||||
(setq json (strcat json " \"" (car f) "\": " (cdr f) ",\n")))
|
||||
(setq json (strcat json " \"actual_attributes\": {"))
|
||||
(setq first T)
|
||||
(foreach att attribs
|
||||
(setq tag (car att) val (cdr att))
|
||||
(if (not first) (setq json (strcat json ",")))
|
||||
(setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\""))
|
||||
(setq first nil))
|
||||
(setq json (strcat json "\n }\n }"))
|
||||
json
|
||||
)
|
||||
|
||||
|
||||
;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen ---
|
||||
;; status: "executed" bei sauberem Lauf; ein abweichender Status (z.B. bei
|
||||
;; einem Queue-Desync) wird durchgereicht, die Block-Angaben bleiben dabei
|
||||
;; erhalten - sonst waere der betroffene Block im Ergebnis nicht auffindbar.
|
||||
(defun hundm05:ent-json-status (test-id kind status ent extra / ed)
|
||||
(if ent
|
||||
(progn
|
||||
(setq ed (entget ent))
|
||||
(hundm05:result-json test-id kind status
|
||||
(cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed))
|
||||
(ssg-attrib-read ent) extra))
|
||||
(hundm05:result-json test-id kind "failed" nil nil nil nil extra))
|
||||
)
|
||||
|
||||
(defun hundm05:ent-json (test-id kind ent extra)
|
||||
(hundm05:ent-json-status test-id kind "executed" ent extra)
|
||||
)
|
||||
|
||||
|
||||
;; --- Letzten INSERT mit Blocknamen-Praefix suchen ---
|
||||
;; AutoLISP kennt KEIN entprev (nur entnext und entlast) - rueckwaerts durch
|
||||
;; die Zeichnung laufen geht also nicht. Gesucht wird darum VORWAERTS, wobei
|
||||
;; der letzte Treffer gewinnt. Ein blosses (entlast) genuegt nicht: nach dem
|
||||
;; Bau koennen weitere Objekte entstehen (z.B. Beschriftungstexte), entlast
|
||||
;; ist dann nicht der gesuchte Block - genau daran scheiterte der Testlauf.
|
||||
;; ab-ent = Stand der Zeichnung VOR dem Bau. Damit prueft die Suche nur die
|
||||
;; neu entstandenen Objekte: schnell, und sie kann nicht versehentlich den
|
||||
;; Block eines frueheren Baus liefern (siehe Kommentar bei -ab).
|
||||
(defun hundm05:insert-prefix-p (ent pref / ed bn)
|
||||
(setq ed (entget ent) bn (cdr (assoc 2 ed)))
|
||||
(and ed (= (cdr (assoc 0 ed)) "INSERT") bn
|
||||
(>= (strlen bn) (strlen pref))
|
||||
(= (substr bn 1 (strlen pref)) pref)))
|
||||
|
||||
;; Vorwaertslauf ab ab-ent (nil = ganze Zeichnung); der LETZTE Treffer
|
||||
;; gewinnt. Der entget-Test faengt ein inzwischen ungueltiges ab-ent ab
|
||||
;; (entget liefert dann nil, entnext wuerde einen Fehler werfen).
|
||||
(defun hundm05:suche-vorwaerts (pref ab-ent / ent found)
|
||||
(setq ent (if (and ab-ent (entget ab-ent)) (entnext ab-ent) (entnext)))
|
||||
(while ent
|
||||
(if (hundm05:insert-prefix-p ent pref) (setq found ent))
|
||||
(setq ent (entnext ent)))
|
||||
found)
|
||||
|
||||
(defun hundm05:last-insert-prefix-ab (pref ab-ent / ent)
|
||||
(if ab-ent
|
||||
;; MIT Marker nur die neu entstandenen Objekte pruefen - und BEWUSST
|
||||
;; ohne entlast-Kurzschluss: hat dieser Bau keinen Block erzeugt, waere
|
||||
;; entlast der Block eines FRUEHEREN Baus und das Ergebnis wuerde der
|
||||
;; falschen Kette zugeordnet. Kein Treffer = kein Block, das ist die
|
||||
;; richtige Antwort.
|
||||
(hundm05:suche-vorwaerts pref ab-ent)
|
||||
(progn
|
||||
;; Ohne Marker: erst entlast (der haeufige Fall, das gerade
|
||||
;; eingefuegte Objekt), sonst die ganze Zeichnung.
|
||||
(setq ent (entlast))
|
||||
(if (and ent (hundm05:insert-prefix-p ent pref))
|
||||
ent
|
||||
(hundm05:suche-vorwaerts pref nil)))))
|
||||
|
||||
(defun hundm05:last-insert-prefix (pref)
|
||||
(hundm05:last-insert-prefix-ab pref nil))
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; JSON-Eintraege -> Ketten
|
||||
;; ============================================================
|
||||
|
||||
;; Das flache JSON-Array in Ketten gruppieren: ein Objekt mit "test_id" ist
|
||||
;; ein Kopf und beginnt eine neue Kette, die folgenden Objekte mit "typ" sind
|
||||
;; ihre Eingaben (Reihenfolge = Frage-Reihenfolge, bleibt erhalten).
|
||||
;; Rueckgabe: Liste von (kopf eingaben-liste).
|
||||
(defun hundm05:gruppiere (daten / ketten kopf eingaben)
|
||||
(setq ketten '() kopf nil eingaben '())
|
||||
(foreach obj daten
|
||||
(cond
|
||||
((ssg-val obj "test_id")
|
||||
(if kopf
|
||||
(setq ketten (cons (list kopf (reverse eingaben)) ketten)))
|
||||
(setq kopf obj eingaben '()))
|
||||
((ssg-val obj "typ")
|
||||
(if kopf (setq eingaben (cons obj eingaben))))
|
||||
)
|
||||
)
|
||||
(if kopf (setq ketten (cons (list kopf (reverse eingaben)) ketten)))
|
||||
(reverse ketten)
|
||||
)
|
||||
|
||||
|
||||
;; --- Wert als String lesen (auch wenn er als Zahl im JSON steht) ---
|
||||
;; Die Menue-Antworten sind Strings ("1"/"2"/"90"/"30"). Steht in einer von
|
||||
;; Hand gepflegten Datei versehentlich 1 statt "1", wuerde ein Zahlenwert in
|
||||
;; die Replay-Queue wandern und der Vergleich (= antwort "1") schlagen fehl -
|
||||
;; darum hier immer nach String wandeln.
|
||||
(defun hundm05:als-string (v)
|
||||
(cond
|
||||
((null v) "")
|
||||
((= (type v) 'STR) v)
|
||||
((= (type v) 'INT) (itoa v))
|
||||
((= (type v) 'REAL) (rtos v 2 6))
|
||||
(T "")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; --- Einen Eingabe-Eintrag in Journal-Eintraege uebersetzen ---
|
||||
;; Journal-Format (vfl-entry->string in Lisp/vf_linienzug.lsp):
|
||||
;; ("PT" x y z) / ("REAL" . r) / ("DL" . r) / ("INT" . i) / ("STR" . s) /
|
||||
;; ("STEP" . label)
|
||||
;; Ein "point_rel" wird zu EINEM oder ZWEI Eintraegen: immer die Laenge (DL),
|
||||
;; zusaetzlich die Fahrtrichtung (REAL) nur dort, wo sie im Journal steht
|
||||
;; (allererstes Segment der Kette, siehe Kopfkommentar).
|
||||
;; Rueckgabe: Liste von Journal-Eintraegen (kann leer sein).
|
||||
(defun hundm05:eintrag->journal (e / typ w hz)
|
||||
(setq typ (ssg-val e "typ"))
|
||||
(cond
|
||||
((= typ "point_abs")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (and w (listp w) (>= (length w) 3))
|
||||
(list (cons "PT" (mapcar 'float w)))
|
||||
nil))
|
||||
((= typ "point_rel")
|
||||
(setq w (ssg-val e "dL") hz (ssg-val e "hz"))
|
||||
(cond
|
||||
((not (numberp w)) nil)
|
||||
((numberp hz) (list (cons "DL" (float w)) (cons "REAL" (float hz))))
|
||||
(T (list (cons "DL" (float w))))))
|
||||
((= typ "real")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (numberp w) (list (cons "REAL" (float w))) nil))
|
||||
((= typ "int")
|
||||
(setq w (ssg-val e "wert"))
|
||||
(if (numberp w) (list (cons "INT" (fix w))) nil))
|
||||
((= typ "string")
|
||||
(list (cons "STR" (hundm05:als-string (ssg-val e "wert")))))
|
||||
((= typ "step")
|
||||
(list (cons "STEP" (hundm05:als-string (ssg-val e "wert")))))
|
||||
(T nil)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; --- Alle Eingaben einer Kette in ein Vorwaerts-Journal uebersetzen ---
|
||||
;; Vorwaerts = Bau-Reihenfolge, genau so erwartet es vfl-journal-replay-start.
|
||||
(defun hundm05:journal-bauen (eingaben / out)
|
||||
(setq out '())
|
||||
(foreach e eingaben
|
||||
(foreach j (hundm05:eintrag->journal e)
|
||||
(setq out (cons j out))))
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
|
||||
;; --- Nicht verbrauchte Werte in der Replay-Queue zaehlen ---
|
||||
;; STEP-Marker zaehlen nicht mit: vfl-replay-pop ueberspringt sie, sie sind
|
||||
;; also kein offener Wert. Bleibt hier etwas uebrig, ist der Bau-Ablauf vom
|
||||
;; aufgezeichneten abgewichen (Desync) - das MUSS auffallen.
|
||||
(defun hundm05:queue-rest ( / n)
|
||||
(setq n 0)
|
||||
(if (and (boundp '*vfl-replay-queue*) *vfl-replay-queue*)
|
||||
(foreach e *vfl-replay-queue*
|
||||
(if (/= (car e) "STEP") (setq n (1+ n)))))
|
||||
n
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Eine Kette bauen
|
||||
;; ============================================================
|
||||
|
||||
;; Spielt das Journal einer Kette ab. Ablauf wie vfl-konvertiere-ent
|
||||
;; (Lisp/vf_linienzug.lsp): Queue setzen, vf-linienzug-modus laufen lassen,
|
||||
;; danach den entstandenen VF_n-Block einsammeln.
|
||||
;;
|
||||
;; vl-catch-all-apply ist hier BEWUSST gesetzt (anders als im interaktiven
|
||||
;; Editier-Pfad vfl-edit-ent): bricht eine Kette ab, muss die Testschleife
|
||||
;; ueber die restlichen Ketten weiterlaufen. Der Preis ist derselbe wie beim
|
||||
;; Batch-Konverter - der *error*-Handler in vf-linienzug-modus (der die
|
||||
;; Teil-Geometrie zu einem Block wickelt) kommt dann nicht zum Zug und die
|
||||
;; Teilgeometrie bleibt lose in der Zeichnung liegen. Der Testfall wird darum
|
||||
;; klar als "failed" samt Fehlertext gemeldet.
|
||||
;; *error* wird um den Aufruf herum gesichert: nach einem abgefangenen Abbruch
|
||||
;; steht dort noch der Handler-Lambda von vf-linienzug-modus (es kommt nicht
|
||||
;; mehr zu dessen (setq *error* old-error)) - ohne Restore wuerde die naechste
|
||||
;; Kette auf einem Handler mit toten dynamischen Bindungen aufsetzen.
|
||||
(defun hundm05:build-linienzug (kopf eingaben / tid journal anz offen err ent
|
||||
alt-error extra hl-fehler meldungen vor-ent)
|
||||
(setq tid (ssg-val kopf "test_id"))
|
||||
(setq journal (hundm05:journal-bauen eingaben))
|
||||
(setq anz (length journal))
|
||||
(princ (strcat "\n [LINIENZUG] " tid " -> " (itoa (length eingaben))
|
||||
" Eingaben, " (itoa anz) " Journal-Eintraege"))
|
||||
(if (ssg-val kopf "beschreibung")
|
||||
(princ (strcat "\n " (ssg-val kopf "beschreibung"))))
|
||||
(if (and (boundp '*hundm05-dbg*) *hundm05-dbg*)
|
||||
(progn
|
||||
(dbgmsg (strcat "KETTE " tid " JOURNAL " (vfl-journal->json journal)))
|
||||
(dbgflush)))
|
||||
|
||||
(cond
|
||||
;; Ohne Journal (oder ohne Startpunkt an erster Stelle) nichts bauen: ein
|
||||
;; Replay ohne PT wuerde den Startpunkt interaktiv erfragen und die ganze
|
||||
;; Kette an der falschen Stelle aufbauen.
|
||||
((null journal)
|
||||
(princ (strcat "\n FEHLER: " tid " - keine Eingaben lesbar."
|
||||
" Stehen die Zahlen-Arrays in EINER Zeile?"
|
||||
" (ssg-cfg-parse-array kann keine umgebrochenen lesen)"))
|
||||
(hundm05:result-json tid "linienzug" "failed" nil nil nil nil
|
||||
(list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa anz)))))
|
||||
|
||||
((/= (car (car journal)) "PT")
|
||||
(princ (strcat "\n FEHLER: " tid " - erster Eintrag ist kein"
|
||||
" Startpunkt (point_abs), nichts gebaut."))
|
||||
(hundm05:result-json tid "linienzug" "failed" nil nil nil nil
|
||||
(list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa anz)))))
|
||||
|
||||
(T
|
||||
;; Stand der Zeichnung VOR dem Bau: der fertige VF_-Block wird danach
|
||||
;; ab hier gesucht (siehe hundm05:last-insert-prefix-ab).
|
||||
(setq vor-ent (entlast))
|
||||
(setq alt-error *error*)
|
||||
(vfl-journal-reset)
|
||||
(vfl-journal-replay-start journal)
|
||||
(setq err (vl-catch-all-apply 'vf-linienzug-modus '()))
|
||||
(setq offen (hundm05:queue-rest))
|
||||
;; Diagnose VOR dem Reset sichern: vfl-journal-reset loescht
|
||||
;; *vfl-headless-fehler* und *vfl-meldungen* mit (beide gehoeren zum
|
||||
;; einzelnen Lauf, nicht zur Sitzung).
|
||||
(setq hl-fehler (hundm05:headless-fehler-text))
|
||||
(setq meldungen (if (boundp '*vfl-meldungen*) *vfl-meldungen* nil))
|
||||
(setq *error* alt-error)
|
||||
(vfl-journal-reset)
|
||||
(setq extra (list (cons "eingaben_gesamt" (itoa anz))
|
||||
(cons "eingaben_offen" (itoa offen))
|
||||
(cons "prompts" (itoa (hundm05:prompts)))
|
||||
(cons "headless_fehler" (hundm05:json-str hl-fehler))
|
||||
(cons "meldungen" (hundm05:json-liste meldungen))))
|
||||
(cond
|
||||
;; Der Headless-Riegel der Produktion hat gegriffen: die Journal-Daten
|
||||
;; passen nicht zum tatsaechlichen Bau-Ablauf. Praeziser als "failed",
|
||||
;; weil hl-fehler Glied UND Eingabe-Nummer nennt.
|
||||
(hl-fehler
|
||||
(princ (strcat "\n DESYNC: " tid " - " hl-fehler))
|
||||
(hundm05:ent-json-status tid "linienzug" "desync"
|
||||
(hundm05:last-insert-prefix-ab "VF_" vor-ent) extra))
|
||||
((vl-catch-all-error-p err)
|
||||
(princ (strcat "\n FEHLER: " tid " abgebrochen -> "
|
||||
(vl-catch-all-error-message err)))
|
||||
(hundm05:result-json tid "linienzug" "failed" nil nil nil nil extra))
|
||||
;; Kette gebaut, aber der Ablauf hat nicht alle aufgezeichneten Werte
|
||||
;; abgerufen: die Geometrie kann nicht der Vorlage entsprechen.
|
||||
((> offen 0)
|
||||
(princ (strcat "\n FEHLER: " tid " - " (itoa offen)
|
||||
" Journal-Werte NICHT verbraucht (Ablauf weicht"
|
||||
" von der Aufzeichnung ab)."))
|
||||
(hundm05:ent-json-status tid "linienzug" "desync"
|
||||
(hundm05:last-insert-prefix-ab "VF_" vor-ent) extra))
|
||||
(T
|
||||
(setq ent (hundm05:last-insert-prefix-ab "VF_" vor-ent))
|
||||
(if ent
|
||||
(princ (strcat " -> Block " (cdr (assoc 2 (entget ent)))))
|
||||
(princ "\n FEHLER: kein VF_-Block gefunden"))
|
||||
(hundm05:ent-json tid "linienzug" ent extra)))))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Headless-Absicherung: Dialoge aus, leere Queue bricht ab
|
||||
;; ============================================================
|
||||
;; Die Journale sind vollstaendig, es darf also keine Live-Eingabe geben.
|
||||
;; Falls doch (Desync), wuerde ein getpoint/getstring den Testlauf blockieren
|
||||
;; und ein alert-Fenster ihn haengen lassen. Darum werden die Eingabe-
|
||||
;; Funktionen fuer die Dauer des Tests durch Abbruch-Stubs ersetzt (liefern
|
||||
;; nil = Abbruch, was vf-linienzug-modus sauber beendet) und alert auf die
|
||||
;; Konsole umgeleitet. Alles mit Save/Restore, siehe hundm05:stubs-aus.
|
||||
;; Die Stubs haben feste 1- bzw. 2-Arity (AutoLISP kennt keine optionalen
|
||||
;; Parameter) - genau wie die Mocks in test_linienzug.lsp. Ruft der Bau-Ablauf
|
||||
;; eine der Funktionen mit anderer Argumentzahl auf, gibt es einen
|
||||
;; Argumentfehler statt einer Warnung; beides landet im vl-catch-all-apply um
|
||||
;; vf-linienzug-modus und meldet die Kette als "failed". Wichtig ist nur, dass
|
||||
;; kein Prompt und kein Dialog den Lauf blockiert.
|
||||
(defun hundm05:stub-warnung (was)
|
||||
(setq *hundm05-prompts* (1+ (hundm05:prompts)))
|
||||
(princ (strcat "\n WARNUNG: unerwartete Live-Eingabe (" was
|
||||
") - Replay-Queue leer, Kette wird abgebrochen."))
|
||||
nil
|
||||
)
|
||||
(defun hundm05:stub-getpoint (a b) (hundm05:stub-warnung "getpoint"))
|
||||
(defun hundm05:stub-getstring (a) (hundm05:stub-warnung "getstring"))
|
||||
(defun hundm05:stub-getint (a) (hundm05:stub-warnung "getint"))
|
||||
(defun hundm05:stub-getreal (a) (hundm05:stub-warnung "getreal"))
|
||||
(defun hundm05:stub-alert (msg)
|
||||
(princ (strcat "\n ALERT (unterdrueckt): " (if msg msg "")))
|
||||
(princ)
|
||||
)
|
||||
|
||||
(defun hundm05:stubs-an ( / )
|
||||
(setq *hundm05-alt-getpoint* vfl-getpoint
|
||||
*hundm05-alt-getstring* getstring
|
||||
*hundm05-alt-getint* getint
|
||||
*hundm05-alt-getreal* getreal
|
||||
*hundm05-alt-alert* alert)
|
||||
(setq vfl-getpoint hundm05:stub-getpoint
|
||||
getstring hundm05:stub-getstring
|
||||
getint hundm05:stub-getint
|
||||
getreal hundm05:stub-getreal
|
||||
alert hundm05:stub-alert)
|
||||
;; Wizard-Dialoge aus (waehrend eines Replays greifen sie ohnehin nicht,
|
||||
;; siehe vfl-wizard-aktiv - aber nach einem Desync wuerde ein Dialog
|
||||
;; aufgehen und warten).
|
||||
(setq *hundm05-alt-wizard* (if (boundp '*vfl-wizard-mode*) *vfl-wizard-mode*))
|
||||
(setq *vfl-wizard-mode* nil)
|
||||
(if (car (atoms-family 1 '("SSG-GUI-AUS"))) (ssg-gui-aus))
|
||||
;; Headless-Riegel der Produktion einschalten: eine erschoepfte Replay-Queue
|
||||
;; ist dann ein harter, lokalisierter Abbruch statt eines stillen Rueckfalls
|
||||
;; auf Live-Eingabe (siehe vfl-headless-abbruch). Die Stubs oben bleiben als
|
||||
;; zweites Netz - sie fangen die Prompts ausserhalb der vfl-in-*-Wrapper
|
||||
;; (Modus 3, Nebenpfade) und zaehlen sie.
|
||||
(setq *hundm05-alt-headless* (if (boundp '*vfl-headless*) *vfl-headless*))
|
||||
(setq *vfl-headless* T)
|
||||
(setq *hundm05-prompts* 0)
|
||||
(princ)
|
||||
)
|
||||
|
||||
(defun hundm05:stubs-aus ( / )
|
||||
(setq vfl-getpoint *hundm05-alt-getpoint*
|
||||
getstring *hundm05-alt-getstring*
|
||||
getint *hundm05-alt-getint*
|
||||
getreal *hundm05-alt-getreal*
|
||||
alert *hundm05-alt-alert*)
|
||||
(setq *vfl-wizard-mode* *hundm05-alt-wizard*)
|
||||
(setq *vfl-headless* *hundm05-alt-headless*)
|
||||
(if (car (atoms-family 1 '("SSG-GUI-AN"))) (ssg-gui-an))
|
||||
(princ)
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Alle Ketten bauen
|
||||
;; ============================================================
|
||||
;; Eigene Funktion, damit der Aufrufer sie in vl-catch-all-apply klammern und
|
||||
;; die Eingabe-Stubs GARANTIERT zuruecknehmen kann (ein hier entkommender
|
||||
;; Fehler wuerde sonst getstring/getpoint/alert der laufenden BricsCAD-Sitzung
|
||||
;; ersetzt zuruecklassen).
|
||||
;; Rueckgabe: Ergebnis-Liste in Bau-Reihenfolge.
|
||||
(defun hundm05:ketten-bauen (ketten / kette res out tid fehlertext)
|
||||
(setq out '())
|
||||
(foreach kette ketten
|
||||
;; Dimensions-Override je Kette neu setzen: der Abbruch-Handler in
|
||||
;; vf-linienzug-modus setzt *ssg-ils-dim* zurueck, ein Abbruch wuerde die
|
||||
;; folgenden Ketten sonst in der falschen Dimension bauen.
|
||||
(if (and (boundp '*hundm05-dim-override*) *hundm05-dim-override*)
|
||||
(setq *ssg-ils-dim* *hundm05-dim-override*))
|
||||
;; Je Kette gefangen: ein Fehler NACH dem Bau (z.B. beim Auswerten des
|
||||
;; Ergebnisses) darf nicht die restlichen Ketten mitnehmen - sonst steht
|
||||
;; am Ende "0 OK, 0 Fehler" und man sieht nicht einmal, welche Kette
|
||||
;; gebaut wurde. Der Bau selbst ist in hundm05:build-linienzug bereits
|
||||
;; separat gefangen; dieser Riegel hier deckt alles danach ab.
|
||||
(setq tid (ssg-val (car kette) "test_id"))
|
||||
(setq res (vl-catch-all-apply 'hundm05:build-linienzug
|
||||
(list (car kette) (cadr kette))))
|
||||
(if (vl-catch-all-error-p res)
|
||||
(progn
|
||||
(setq fehlertext (vl-catch-all-error-message res))
|
||||
(princ (strcat "\n FEHLER: " tid " - Ausnahme: " fehlertext))
|
||||
(setq res (hundm05:result-json tid "linienzug" "failed"
|
||||
nil nil nil nil
|
||||
(list (cons "fehler_text"
|
||||
(hundm05:json-str fehlertext)))))))
|
||||
(if res (setq out (cons res out))))
|
||||
(reverse out)
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; JSON-Export der Ergebnisse
|
||||
;; ============================================================
|
||||
(defun hundm05:export-results (tests-out-dir / out-json f first)
|
||||
(if (null *hundm05-test-results*)
|
||||
(princ "\n Keine HundM05-Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq out-json (strcat tests-out-dir "/hundm05_results.json"))
|
||||
(setq f (open out-json "w"))
|
||||
(if f
|
||||
(progn
|
||||
(write-line "[" f)
|
||||
(setq first T)
|
||||
(foreach r *hundm05-test-results*
|
||||
(if (not first) (write-line "," f))
|
||||
(write-line r f)
|
||||
(setq first nil))
|
||||
(write-line "]" f)
|
||||
(close f)
|
||||
(princ (strcat "\n Ergebnisse: " out-json)))
|
||||
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!")))))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; C:TEST_HUNDM05 - Hauptbefehl
|
||||
;; ============================================================
|
||||
(defun c:TEST_HUNDM05 ( / json-datei daten ketten results-list res
|
||||
anz-ok anz-fehler)
|
||||
;; Debug-Datei nur bei eingeschaltetem Schalter: (dbg-schalter-on "hundm05")
|
||||
(setq *hundm05-dbg*
|
||||
(and (car (atoms-family 1 '("DBG-SCHALTER-OPEN")))
|
||||
(dbg-schalter-open "hundm05" "hundm05.dbg" "DXFM_LOG")))
|
||||
|
||||
;; Benoetigte Feature-Module laden (Linienzug braucht Vario + Gefaelle)
|
||||
(ssg-ensure "VarioFoerderer")
|
||||
(ssg-ensure "Gefaellestrecke")
|
||||
|
||||
(if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS"))))
|
||||
(progn
|
||||
(princ "\n[TEST_HUNDM05] FEHLER: vf-linienzug-modus nicht geladen"
|
||||
" (VarioFoerderer/Gefaellestrecke laden).")
|
||||
(if *hundm05-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(ssg-start "TEST_HUNDM05" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
|
||||
(setvar "OSMODE" 0)
|
||||
(setvar "ATTREQ" 0)
|
||||
(setvar "ATTDIA" 0)
|
||||
|
||||
;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren
|
||||
(if (or (not *lib-initialized*) (null bogen-auf))
|
||||
(init-bibliothek))
|
||||
|
||||
;; Bau-Dimension festhalten. Die Eingabedaten sind dimensionsfrei; welche
|
||||
;; Blockvariante (_2D/_3D) verwendet wird, loest ssg-ils-dim-aktuell auf
|
||||
;; (Override *ssg-ils-dim* -> Umgebungsvariable DXFM_DIM -> Default "3D").
|
||||
(setq *hundm05-dimension* (ssg-ils-dim-aktuell))
|
||||
(princ (strcat "\n Bau-Dimension: " *hundm05-dimension*
|
||||
" (umschalten: TEST_HUNDM05_2D / TEST_HUNDM05_3D)"))
|
||||
|
||||
;; Testdaten laden
|
||||
(setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json"))
|
||||
(if (not (findfile json-datei))
|
||||
(progn
|
||||
(princ (strcat "\n[TEST_HUNDM05] FEHLER: " json-datei " nicht gefunden!"))
|
||||
(ssg-end)
|
||||
(if *hundm05-dbg* (dbgclose))
|
||||
(exit)))
|
||||
(setq daten (ssg-load-json json-datei))
|
||||
(if (null daten)
|
||||
(progn
|
||||
(princ "\n[TEST_HUNDM05] FEHLER: Testdaten konnten nicht geladen werden.")
|
||||
(ssg-end)
|
||||
(if *hundm05-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(setq ketten (hundm05:gruppiere daten))
|
||||
(if (null ketten)
|
||||
(progn
|
||||
(princ "\n[TEST_HUNDM05] FEHLER: keine Kette gefunden - erwartet werden"
|
||||
" Kopf-Objekte mit \"test_id\" und Eingaben mit \"typ\".")
|
||||
(ssg-end)
|
||||
(if *hundm05-dbg* (dbgclose))
|
||||
(exit)))
|
||||
|
||||
(defun c:TEST_HUNDM05 ( / datei)
|
||||
(setq datei (hundm05:spec-datei))
|
||||
(princ "\n\n================================================================")
|
||||
(princ "\n TEST_HUNDM05 - 5 Linienzuege der Anlage HundM (polylines.dxf)")
|
||||
(princ (strcat "\n " (itoa (length daten)) " JSON-Objekte, "
|
||||
(itoa (length ketten)) " Ketten"))
|
||||
(princ "\n TEST_HUNDM05 - 5 Linienzuege der Anlage HundM (aus der Spec)")
|
||||
(princ (strcat "\n " datei))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq anz-ok 0 anz-fehler 0)
|
||||
(hundm05:stubs-an)
|
||||
;; Stubs MUESSEN auch nach einem unerwarteten Fehler zurueckgenommen werden.
|
||||
(setq results-list (vl-catch-all-apply 'hundm05:ketten-bauen (list ketten)))
|
||||
(hundm05:stubs-aus)
|
||||
(if (vl-catch-all-error-p results-list)
|
||||
(progn
|
||||
(princ (strcat "\n[TEST_HUNDM05] FEHLER in der Kettenschleife: "
|
||||
(vl-catch-all-error-message results-list)))
|
||||
(setq results-list '())))
|
||||
|
||||
(foreach res results-list
|
||||
(if (vl-string-search "\"status\": \"executed\"" res)
|
||||
(setq anz-ok (1+ anz-ok))
|
||||
(setq anz-fehler (1+ anz-fehler))))
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, "
|
||||
(itoa anz-fehler) " Fehler"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq *hundm05-test-results* results-list)
|
||||
|
||||
(ssg-end)
|
||||
(princ "\n TEST_HUNDM05 abgeschlossen.")
|
||||
(if *hundm05-dbg* (dbgclose))
|
||||
(princ)
|
||||
)
|
||||
(cond
|
||||
((null (car (atoms-family 1 '("VSP-BAU-DATEI"))))
|
||||
(princ "\n[TEST_HUNDM05] FEHLER: vf_spec.lsp nicht geladen"
|
||||
" (VarioFoerderer laden).")
|
||||
(setq *hundm05-records* nil))
|
||||
((null (findfile datei))
|
||||
(princ (strcat "\n[TEST_HUNDM05] FEHLER: " datei " nicht gefunden!"))
|
||||
(setq *hundm05-records* nil))
|
||||
(T
|
||||
(setq *hundm05-records* (vsp-bau-datei datei))))
|
||||
(princ))
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Dieselbe Anlage in einer bestimmten Dimension bauen
|
||||
;; ============================================================
|
||||
;; Die Eingabedaten (hundm05.json) sind dimensionsfrei - sie beschreiben die
|
||||
;; Eingaben, mit denen die Ketten gebaut wurden. Ob daraus flache 2D-Symbole
|
||||
;; oder 3D-Modelle werden, entscheidet allein die Blockvariante (_2D/_3D), die
|
||||
;; ssg-ils-dim-aktuell aufloest. Unsere Bibliothek hat beide, also laesst sich
|
||||
;; dieselbe Anlage ohne Datenaenderung in 2D ODER 3D aufbauen.
|
||||
;; *hundm05-dim-override* wird in der Kettenschleife je Kette neu gesetzt
|
||||
;; (siehe dort).
|
||||
(defun hundm05:mit-dimension (dim / alt ergebnis)
|
||||
(setq alt (if (boundp '*ssg-ils-dim*) *ssg-ils-dim*))
|
||||
(setq *ssg-ils-dim* dim)
|
||||
(setq *hundm05-dim-override* dim)
|
||||
(princ (strcat "\n[TEST_HUNDM05] Dimension voruebergehend auf " dim
|
||||
" gesetzt."))
|
||||
(setq ergebnis (c:TEST_HUNDM05))
|
||||
;; Override wieder auf den vorherigen Stand - sonst baut ein folgender Test
|
||||
;; unbemerkt in der hier gewaehlten Dimension weiter.
|
||||
(setq *ssg-ils-dim* alt)
|
||||
(setq *hundm05-dim-override* nil)
|
||||
(princ (strcat "\n[TEST_HUNDM05] Dimension zurueckgesetzt auf "
|
||||
(if alt alt "Umgebung/Default") "."))
|
||||
ergebnis
|
||||
)
|
||||
;; Die Spec ist dimensionsfrei - sie beschreibt die Eingaben, mit denen die
|
||||
;; Ketten gebaut wurden. Ob daraus flache 2D-Symbole oder 3D-Modelle werden,
|
||||
;; entscheidet allein die Blockvariante (_2D/_3D), die ssg-ils-dim-aktuell
|
||||
;; aufloest. Unsere Bibliothek hat beide, also laesst sich dieselbe Anlage
|
||||
;; ohne Datenaenderung in 2D ODER 3D aufbauen.
|
||||
;; *vsp-dim-override* wird von vsp-bau-aus-spec JE KETTE angewandt (der
|
||||
;; Abbruch-Handler in vf-linienzug-modus setzt *ssg-ils-dim* zurueck).
|
||||
|
||||
(defun c:TEST_HUNDM05_2D ( / ) (hundm05:mit-dimension "2D"))
|
||||
(defun c:TEST_HUNDM05_3D ( / ) (hundm05:mit-dimension "3D"))
|
||||
(defun hundm05:mit-dimension (dim / alt)
|
||||
(setq alt (if (boundp '*vsp-dim-override*) *vsp-dim-override*))
|
||||
(setq *vsp-dim-override* dim)
|
||||
(vl-catch-all-apply 'c:TEST_HUNDM05 '())
|
||||
(setq *vsp-dim-override* alt)
|
||||
(princ))
|
||||
|
||||
(defun c:TEST_HUNDM05_2D () (hundm05:mit-dimension "2D"))
|
||||
(defun c:TEST_HUNDM05_3D () (hundm05:mit-dimension "3D"))
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Ergebnis-Export (Konvention <name>:export-results fuer test_run_all.lsp)
|
||||
;; ============================================================
|
||||
;; Geschrieben wird hier und nicht in c:TEST_HUNDM05, damit der Testrunner
|
||||
;; das Zielverzeichnis bestimmt (er uebergibt tests/output).
|
||||
|
||||
(defun hundm05:export-results (out-dir / pfad)
|
||||
(if (null *hundm05-records*)
|
||||
(princ "\n Keine HundM05-Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir out-dir)
|
||||
(setq pfad (strcat out-dir "/hundm05_results.json"))
|
||||
(vsp-results-schreiben *hundm05-records* pfad)))
|
||||
(princ))
|
||||
|
||||
(princ "\nTEST_HUNDM05 geladen. Aufruf: TEST_HUNDM05 (bzw. _2D / _3D)")
|
||||
(princ)
|
||||
|
||||
+100
-348
@@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_hundm05.py - Validiert die 5 VarioFoerderer-Linienzuege der Anlage HundM.
|
||||
test_hundm05.py - Validiert die aus der SPEC gebaute Anlage HundM.
|
||||
|
||||
Zwei Ebenen:
|
||||
1. Die Testdaten selbst (tests/testdata/hundm05.json): Struktur der
|
||||
Eingabe-Journale und Lesbarkeit durch den LISP-Parser.
|
||||
2. Die von TEST_HUNDM05 (test_hundm05.lsp) erzeugten Ergebnisse
|
||||
(output/hundm05_results.json + output/hundm05_tests.dxf): jede Kette
|
||||
gebaut, kein Journal-Wert offen, Attribute passen zu den Eingaben.
|
||||
Eingabe: tests/testdata/hundm05.json (die 5 Ketten im Spec-Format).
|
||||
Gebaut von TEST_HUNDM05 bzw. VF_SPEC_BAU in BricsCAD, Ergebnis in
|
||||
tests/output/hundm05_results.json.
|
||||
|
||||
Die Testdaten stammen aus lib/vf_journal_export.py (Journale der VF_n-Bloecke
|
||||
aus data/polylines.dxf, XDATA-App SSG_VF_EDIT) und nicht aus Handarbeit -
|
||||
Schema siehe tests/testdata/object_data.md Abschnitt 8.
|
||||
Diese Tests brauchen also einen CAD-Lauf und werden sonst uebersprungen.
|
||||
Den UEBERSETZER (Spec -> Journal) prueft tests/test_vf_spec.py ohne CAD;
|
||||
das Aufzeichnungsformat prueft tests/test_hm_recformat.py.
|
||||
|
||||
Der Ergebnis-Record je Kette kommt aus Lisp/vf_spec.lsp (vsp-result-json).
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -20,361 +19,114 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
HIER = os.path.dirname(__file__)
|
||||
SPEC_JSON = os.path.join(HIER, "testdata", "hundm05.json")
|
||||
|
||||
EINGABE_TYPEN = ("point_abs", "point_rel", "real", "int", "string", "step")
|
||||
# TEST_HUNDM05 schreibt nach tests/output. Ein Lauf von VF_SPEC_BAU mit
|
||||
# gesetztem DXFM_VF_SPEC_OUT kann die Datei woanders ablegen - darum beide
|
||||
# ueblichen Orte pruefen, statt den Test stumm zu ueberspringen.
|
||||
ERGEBNIS_ORTE = [os.path.join(HIER, "output", "hundm05_results.json"),
|
||||
os.path.join(HIER, "..", "results", "hundm05_results.json")]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Hilfsfunktionen: flaches JSON in Ketten gruppieren
|
||||
# ============================================================
|
||||
# Gleiche Regel wie hundm05:gruppiere in test_hundm05.lsp: ein Objekt mit
|
||||
# "test_id" ist ein Kopf und beginnt eine Kette, die folgenden Objekte mit
|
||||
# "typ" sind ihre Eingaben.
|
||||
|
||||
def _ketten(testdata):
|
||||
ketten = []
|
||||
for obj in testdata:
|
||||
if "test_id" in obj:
|
||||
ketten.append((obj, []))
|
||||
elif "typ" in obj and ketten:
|
||||
ketten[-1][1].append(obj)
|
||||
return ketten
|
||||
@pytest.fixture(scope="module")
|
||||
def spec_objekte():
|
||||
if not os.path.isfile(SPEC_JSON):
|
||||
pytest.skip("%s fehlt (lib/vf_spec_export.py erzeugt es)" % SPEC_JSON)
|
||||
with open(SPEC_JSON, encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def _journal_laenge(eingaben):
|
||||
"""Anzahl der Journal-Eintraege, die der Treiber daraus baut: ein
|
||||
point_rel mit "hz" wird zu ZWEI Eintraegen (DL + REAL)."""
|
||||
return sum(2 if (e["typ"] == "point_rel" and "hz" in e) else 1
|
||||
for e in eingaben)
|
||||
@pytest.fixture(scope="module")
|
||||
def spec_ergebnisse():
|
||||
for pfad in ERGEBNIS_ORTE:
|
||||
if os.path.isfile(pfad):
|
||||
with open(pfad, encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
pytest.skip("hundm05_results.json fehlt - TEST_HUNDM05 in BricsCAD "
|
||||
"ausfuehren")
|
||||
|
||||
|
||||
def _pfad(*teile):
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), *teile)
|
||||
class TestSpecBau:
|
||||
|
||||
def test_je_spec_ein_ergebnis(self, spec_objekte, spec_ergebnisse):
|
||||
soll = [o["spec_id"] for o in spec_objekte if "spec_id" in o]
|
||||
ist = [r["spec_id"] for r in spec_ergebnisse]
|
||||
assert ist == soll
|
||||
|
||||
# ============================================================
|
||||
# Testdaten-Pruefung (hundm05.json selbst)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Testdaten:
|
||||
|
||||
def test_fuenf_ketten(self, hundm05_testdata):
|
||||
"""Die Anlage hat fuenf am Kreisel haengende Linienzuege."""
|
||||
ketten = _ketten(hundm05_testdata)
|
||||
assert len(ketten) == 5, \
|
||||
f"{len(ketten)} Ketten in hundm05.json, erwartet 5"
|
||||
|
||||
def test_jedes_objekt_zuordenbar(self, hundm05_testdata):
|
||||
"""Kein Objekt darf zwischen Kopf und Eingabe fallen - sonst
|
||||
verschluckt hundm05:gruppiere es stillschweigend."""
|
||||
for i, obj in enumerate(hundm05_testdata):
|
||||
assert ("test_id" in obj) or ("typ" in obj), \
|
||||
f"Objekt {i} ist weder Kopf noch Eingabe: {sorted(obj)}"
|
||||
|
||||
def test_kopf_felder(self, hundm05_testdata):
|
||||
for kopf, _ in _ketten(hundm05_testdata):
|
||||
assert kopf["test_id"].startswith("VF_"), kopf["test_id"]
|
||||
# "typ" wuerde den Kopf zu einer Eingabe machen (siehe
|
||||
# hundm05:gruppiere) - der Anlagetyp heisst darum "anlagetyp".
|
||||
assert "typ" not in kopf, \
|
||||
f'{kopf["test_id"]}: Kopf darf kein Feld "typ" tragen'
|
||||
assert kopf["anlagetyp"] == "linienzug", kopf["test_id"]
|
||||
assert kopf["modus"] == 1, kopf["test_id"]
|
||||
assert kopf["block"].startswith("VF_"), kopf["block"]
|
||||
assert len(kopf["startpunkt_mm"]) == 3, kopf["test_id"]
|
||||
|
||||
def test_anzahl_eingaben_stimmt(self, hundm05_testdata):
|
||||
"""anzahl_eingaben ist die Selbstauskunft des Kopfes - weicht sie ab,
|
||||
fehlen Eingaben (z.B. durch eine Handaenderung)."""
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
assert kopf["anzahl_eingaben"] == len(eingaben), \
|
||||
f'{kopf["test_id"]}: anzahl_eingaben=' \
|
||||
f'{kopf["anzahl_eingaben"]}, gezaehlt {len(eingaben)}'
|
||||
|
||||
def test_eingabe_typen_bekannt(self, hundm05_testdata):
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
for i, e in enumerate(eingaben):
|
||||
assert e["typ"] in EINGABE_TYPEN, \
|
||||
f'{kopf["test_id"]} Eingabe {i}: unbekannter Typ ' \
|
||||
f'{e["typ"]}'
|
||||
assert e.get("kommentar"), \
|
||||
f'{kopf["test_id"]} Eingabe {i}: kein Kommentar'
|
||||
|
||||
def test_kette_beginnt_mit_startpunkt(self, hundm05_testdata):
|
||||
"""Erste Eingabe = absoluter Startpunkt, zweite = Starthoehe. Fehlt
|
||||
der Punkt, wuerde das Replay ihn interaktiv erfragen und die Kette an
|
||||
der falschen Stelle aufbauen (siehe hundm05:build-linienzug)."""
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
assert eingaben[0]["typ"] == "point_abs", kopf["test_id"]
|
||||
assert eingaben[1]["typ"] == "real", kopf["test_id"]
|
||||
punkt = eingaben[0]["wert"]
|
||||
assert len(punkt) == 3, kopf["test_id"]
|
||||
assert punkt == kopf["startpunkt_mm"], \
|
||||
f'{kopf["test_id"]}: point_abs {punkt} != startpunkt_mm ' \
|
||||
f'{kopf["startpunkt_mm"]}'
|
||||
assert eingaben[1]["wert"] == punkt[2], \
|
||||
f'{kopf["test_id"]}: Starthoehe {eingaben[1]["wert"]} != ' \
|
||||
f'Z des Startpunkts {punkt[2]}'
|
||||
|
||||
def test_hz_nur_beim_ersten_segment(self, hundm05_testdata):
|
||||
"""vfl-in-abstand journalisiert die Fahrtrichtung NUR beim ersten
|
||||
Segment der Kette; jedes weitere erbt sie vom Vorgaenger. Ein "hz" an
|
||||
spaeterer Stelle waere ein Wert zu viel in der Replay-Queue und
|
||||
verschiebt alle folgenden Antworten (Desync)."""
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
rel = [e for e in eingaben if e["typ"] == "point_rel"]
|
||||
assert rel, f'{kopf["test_id"]}: kein Segment'
|
||||
assert "hz" in rel[0], \
|
||||
f'{kopf["test_id"]}: erstes Segment ohne hz'
|
||||
for i, e in enumerate(rel[1:], start=2):
|
||||
assert "hz" not in e, \
|
||||
f'{kopf["test_id"]}: Segment {i} traegt hz - nur das ' \
|
||||
f'erste Segment darf eines haben'
|
||||
|
||||
def test_segmentlaengen_positiv(self, hundm05_testdata):
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
for e in eingaben:
|
||||
if e["typ"] == "point_rel":
|
||||
assert e["dL"] > 0, f'{kopf["test_id"]}: dL={e["dL"]}'
|
||||
|
||||
def test_menue_antworten_sind_strings(self, hundm05_testdata):
|
||||
"""Menue-Antworten muessen Strings bleiben ("1"/"2"/"90"/"30"): der
|
||||
Produktionscode vergleicht mit (= antwort "1"), eine Zahl wuerde
|
||||
stumm in den Default-Zweig laufen."""
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
for i, e in enumerate(eingaben):
|
||||
if e["typ"] in ("string", "step"):
|
||||
assert isinstance(e["wert"], str), \
|
||||
f'{kopf["test_id"]} Eingabe {i}: {e["wert"]!r} ist ' \
|
||||
f'kein String'
|
||||
|
||||
def test_glieder_vorhanden(self, hundm05_testdata):
|
||||
"""Jede Kette hat Glied-Marker (STEP) - sie sind der Anker fuer den
|
||||
Sektions-/Glied-Edit und die Kurzbeschreibung."""
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
glieder = [e["wert"] for e in eingaben if e["typ"] == "step"]
|
||||
assert glieder, f'{kopf["test_id"]}: kein STEP-Marker'
|
||||
for g in glieder:
|
||||
assert g in ("Linie-GF", "Linie-VF", "Linie", "GF-Bogen",
|
||||
"Horizontal-VF", "Vario-Kurve", "ES"), \
|
||||
f'{kopf["test_id"]}: unbekanntes Glied {g}'
|
||||
|
||||
def test_arrays_einzeilig(self):
|
||||
"""Jedes Array muss komplett in EINER Zeile stehen.
|
||||
|
||||
ssg-parse-json-array (Lisp/ssg_core.lsp) liest die Datei zeilenweise,
|
||||
und ssg-cfg-parse-array braucht das ganze Array in einem String. Ein
|
||||
von json.dump(indent=2) umgebrochenes Array wird zu nil - der Treiber
|
||||
baut die Kette dann nicht an ihrer Position. Das ist genau der
|
||||
Fehler, der die Anlage einmal komplett am Ursprung aufgebaut hat, und
|
||||
er ist in der Zeichnung nicht als Datenfehler zu erkennen.
|
||||
"""
|
||||
with open(_pfad("testdata", "hundm05.json"), encoding="utf-8") as fh:
|
||||
for nr, zeile in enumerate(fh, start=1):
|
||||
offen = zeile.count("[") - zeile.count("]")
|
||||
# Die aeusserste Klammer des Dokuments darf allein stehen.
|
||||
if zeile.strip() in ("[", "]"):
|
||||
continue
|
||||
assert offen == 0, \
|
||||
f"Zeile {nr}: Array ueber mehrere Zeilen umgebrochen - " \
|
||||
f"ssg-cfg-parse-array kann das nicht lesen: {zeile.strip()!r}"
|
||||
|
||||
def test_flach_fuer_lisp_parser(self, hundm05_testdata):
|
||||
"""Die Datei muss FLACH bleiben.
|
||||
|
||||
ssg-load-json / ssg-parse-json-array behandelt jedes "{" als
|
||||
Objektanfang und jedes "}"/"}," als Objektende - es kennt keine
|
||||
Verschachtelung. Ein Objekt in einem Objekt (oder ein Array von
|
||||
Objekten wie "eingaben": [...]) wuerde das umgebende Objekt
|
||||
verschlucken. Dieser Test zaehlt die Objekte so, wie der LISP-Parser
|
||||
sie zaehlen wuerde, und vergleicht mit dem echten JSON.
|
||||
"""
|
||||
objekte = 0
|
||||
with open(_pfad("testdata", "hundm05.json"), encoding="utf-8") as fh:
|
||||
for zeile in fh:
|
||||
gestrippt = zeile.strip()
|
||||
if gestrippt == "{":
|
||||
objekte += 1
|
||||
elif gestrippt in ("}", "},"):
|
||||
continue
|
||||
elif gestrippt.startswith('"'):
|
||||
# Ein Wert, der ein Objekt oder eine Objektliste eroeffnet,
|
||||
# macht die Datei fuer den LISP-Parser unlesbar.
|
||||
assert not gestrippt.endswith(("{", "[")), \
|
||||
f"verschachtelter Wert: {gestrippt!r}"
|
||||
assert objekte == len(hundm05_testdata), \
|
||||
f"LISP-Parser saehe {objekte} Objekte, json.load sieht " \
|
||||
f"{len(hundm05_testdata)}"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Ergebnis-Pruefung (results.json vs. testdata)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Results:
|
||||
|
||||
def test_je_kette_ein_ergebnis(self, hundm05_testdata, hundm05_results):
|
||||
soll = [k["test_id"] for k, _ in _ketten(hundm05_testdata)]
|
||||
ist = [r["test_id"] for r in hundm05_results]
|
||||
assert ist == soll, f"Ergebnisse {ist}, erwartet {soll}"
|
||||
for r in hundm05_results:
|
||||
assert r["kind"] == "linienzug", f'{r["test_id"]}: {r["kind"]}'
|
||||
|
||||
def test_status_executed(self, hundm05_results):
|
||||
""""desync" (Journal-Werte nicht verbraucht) und "failed" (Abbruch)
|
||||
sind beide echte Fehler - siehe hundm05:build-linienzug."""
|
||||
for r in hundm05_results:
|
||||
def test_status_executed(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["status"] == "executed", \
|
||||
f'{r["test_id"]}: Status={r["status"]}'
|
||||
f'{r["spec_id"]}: Status={r["status"]}, {r.get("fehler_text")}'
|
||||
|
||||
def test_journal_vollstaendig_verbraucht(self, hundm05_testdata,
|
||||
hundm05_results):
|
||||
"""Alle aufgezeichneten Werte muessen im Bau abgerufen worden sein.
|
||||
Bleibt etwas offen, ist der Ablauf von der Aufzeichnung abgewichen und
|
||||
die Geometrie kann der Vorlage nicht entsprechen."""
|
||||
soll = {k["test_id"]: _journal_laenge(e)
|
||||
for k, e in _ketten(hundm05_testdata)}
|
||||
for r in hundm05_results:
|
||||
assert "eingaben_offen" in r, (
|
||||
f'{r["test_id"]}: kein eingaben_offen im Ergebnis - die Kette '
|
||||
f'ist vor der Auswertung abgebrochen (status='
|
||||
f'{r.get("status")!r}, {r.get("fehler_text")})')
|
||||
def test_keine_spec_fehler(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert not r["spec_fehler"], \
|
||||
f'{r["spec_id"]}: {r["spec_fehler"]}'
|
||||
|
||||
def test_kein_prompt(self, spec_ergebnisse):
|
||||
"""Der positive Beweis fuer "kein Dialog, keine Konsolenfrage": die
|
||||
zaehlenden Stubs wurden nie erreicht."""
|
||||
for r in spec_ergebnisse:
|
||||
assert r["prompts"] == 0, \
|
||||
f'{r["spec_id"]}: {r["prompts"]} Live-Eingabe(n) erreicht'
|
||||
|
||||
def test_journal_vollstaendig_verbraucht(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["eingaben_offen"] == 0, \
|
||||
f'{r["test_id"]}: {r["eingaben_offen"]} Journal-Werte offen'
|
||||
assert r["eingaben_gesamt"] == soll[r["test_id"]], \
|
||||
f'{r["test_id"]}: {r["eingaben_gesamt"]} Journal-Eintraege ' \
|
||||
f'abgespielt, erwartet {soll[r["test_id"]]}'
|
||||
f'{r["spec_id"]}: {r["eingaben_offen"]} Werte offen'
|
||||
assert r["eingaben_gesamt"] > 0
|
||||
|
||||
def test_keine_live_eingabe(self, hundm05_results):
|
||||
"""Kein Prompt, kein Dialog: der Treiber schaltet *vfl-headless* ein
|
||||
(harter Abbruch statt stillem Rueckfall auf Live-Eingabe, siehe
|
||||
vfl-headless-abbruch in Lisp/vf_linienzug.lsp) UND ersetzt zusaetzlich
|
||||
getpoint/getstring/getint/getreal durch zaehlende Stubs. Beide Wege
|
||||
muessen leer bleiben - "prompts" ist der positive Beweis, dass keine
|
||||
Eingabe ausserhalb der vfl-in-*-Wrapper erreicht wurde."""
|
||||
for r in hundm05_results:
|
||||
if "prompts" in r:
|
||||
assert r["prompts"] == 0, \
|
||||
f'{r["test_id"]}: {r["prompts"]} Live-Eingabe(n) erreicht'
|
||||
assert not r.get("headless_fehler"), \
|
||||
f'{r["test_id"]}: Headless-Abbruch bei {r["headless_fehler"]}'
|
||||
def test_glieder_wie_geplant(self, spec_ergebnisse):
|
||||
"""Der Desync-Detektor, der auch dann greift, wenn die Queue aufgeht:
|
||||
eine geometrisch abgewiesene Sektion verbraucht nur weniger
|
||||
Eintraege, die Glied-Folge weicht dann aber ab."""
|
||||
for r in spec_ergebnisse:
|
||||
assert r["glieder_ist"] == r["glieder_soll"], \
|
||||
f'{r["spec_id"]}: gebaut {r["glieder_ist"]}, ' \
|
||||
f'geplant {r["glieder_soll"]}'
|
||||
|
||||
def test_keine_geometrie_meldungen(self, hundm05_results):
|
||||
"""*vfl-meldungen* sammelt die abgewiesenen Sektionen (frueher
|
||||
blockierende alert-Fenster, jetzt vfl-meldung). Eine Meldung heisst:
|
||||
ein Glied liess sich geometrisch nicht bauen - der Nachbau weicht dann
|
||||
von der Vorlage ab, auch wenn das Journal aufgeht."""
|
||||
for r in hundm05_results:
|
||||
assert not r.get("meldungen"), \
|
||||
f'{r["test_id"]}: Meldungen aus dem Bau: {r["meldungen"]}'
|
||||
def test_keine_meldungen(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert not r["meldungen"], \
|
||||
f'{r["spec_id"]}: Meldungen aus dem Bau: {r["meldungen"]}'
|
||||
|
||||
def test_vf_block_erzeugt(self, hundm05_results):
|
||||
for r in hundm05_results:
|
||||
def test_vf_block_erzeugt(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["block_name"].startswith("VF_"), \
|
||||
f'{r["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block'
|
||||
assert r["block_handle"], f'{r["test_id"]}: kein Handle'
|
||||
f'{r["spec_id"]}: Block {r["block_name"]!r}'
|
||||
assert r["block_handle"]
|
||||
|
||||
def test_dimension_einheitlich(self, hundm05_results):
|
||||
"""Alle Ergebnisse eines Laufs stammen aus EINER Bau-Dimension.
|
||||
|
||||
Die Eingabedaten sind dimensionsfrei; ob _2D- oder _3D-Blockvarianten
|
||||
verwendet werden, loest ssg-ils-dim-aktuell auf. Ein Lauf, in dem beide
|
||||
vorkommen, waere ein halb umgeschalteter Zustand (z.B. weil ein
|
||||
vorheriger Test *ssg-ils-dim* nicht zurueckgesetzt hat).
|
||||
"""
|
||||
dims = {r.get("dimension") for r in hundm05_results}
|
||||
assert len(dims) == 1, f"gemischte Bau-Dimensionen im Lauf: {dims}"
|
||||
dim = dims.pop()
|
||||
assert dim in ("2D", "3D"), \
|
||||
f'Bau-Dimension "{dim}" ist weder 2D noch 3D'
|
||||
|
||||
def test_startpunkt_getroffen(self, hundm05_testdata, hundm05_results):
|
||||
"""Der VF_n-Block wird mit dem Kettenstartpunkt als Basispunkt
|
||||
gewickelt (ssg-block-wrap-welt in vfl-block-erstellen) - sein
|
||||
Einfuegepunkt muss also der Startpunkt aus den Eingaben sein."""
|
||||
soll = {k["test_id"]: k["startpunkt_mm"]
|
||||
for k, _ in _ketten(hundm05_testdata)}
|
||||
for r in hundm05_results:
|
||||
p = soll[r["test_id"]]
|
||||
def test_startpunkt_getroffen(self, spec_objekte, spec_ergebnisse):
|
||||
"""Der Einfuegepunkt des VF_-Blocks ist der Kettenstart aus der
|
||||
Spec - er ist der einzige absolute Anker der ganzen Kette."""
|
||||
soll = dict((o["spec_id"], o["start_punkt"]) for o in spec_objekte
|
||||
if "spec_id" in o)
|
||||
for r in spec_ergebnisse:
|
||||
erwartet = soll[r["spec_id"]]
|
||||
ist = r["insert_point"]
|
||||
for achse, i in (("X", 0), ("Y", 1), ("Z", 2)):
|
||||
assert abs(ist[i] - p[i]) < 1.0, \
|
||||
f'{r["test_id"]}: Einfuegepunkt {achse} {ist[i]} != ' \
|
||||
f'{p[i]}'
|
||||
for achse, (e, i) in enumerate(zip(erwartet, ist)):
|
||||
assert abs(e - i) < 1.0, \
|
||||
f'{r["spec_id"]}: Achse {achse} ist {i}, erwartet {e}'
|
||||
|
||||
def test_as_es_attribute_aus_eingaben(self, hundm05_testdata,
|
||||
hundm05_results):
|
||||
"""WINKEL_AS/SEITE_AS und WINKEL_ES/SEITE_ES muessen die Antworten aus
|
||||
dem Journal wiedergeben (AS steht in der Praeambel, ES im ES-Glied)."""
|
||||
seiten = {"1": "links", "2": "rechts"}
|
||||
nach_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
r = nach_id[kopf["test_id"]]
|
||||
attrs = r["actual_attributes"]
|
||||
# Praeambel: [0] Punkt, [1] Hoehe, [2] AS setzen, [3] Winkel,
|
||||
# [4] Seite
|
||||
if eingaben[2]["wert"] == "1":
|
||||
assert attrs.get("WINKEL_AS") == eingaben[3]["wert"], \
|
||||
f'{kopf["test_id"]}: WINKEL_AS=' \
|
||||
f'{attrs.get("WINKEL_AS")}, erwartet {eingaben[3]["wert"]}'
|
||||
assert attrs.get("SEITE_AS") == seiten[eingaben[4]["wert"]], \
|
||||
f'{kopf["test_id"]}: SEITE_AS={attrs.get("SEITE_AS")}'
|
||||
# ES: die beiden Strings direkt nach dem STEP-Marker "ES"
|
||||
for i, e in enumerate(eingaben):
|
||||
if e["typ"] == "step" and e["wert"] == "ES":
|
||||
assert attrs.get("WINKEL_ES") == eingaben[i + 1]["wert"], \
|
||||
f'{kopf["test_id"]}: WINKEL_ES=' \
|
||||
f'{attrs.get("WINKEL_ES")}'
|
||||
assert attrs.get("SEITE_ES") == \
|
||||
seiten[eingaben[i + 2]["wert"]], \
|
||||
f'{kopf["test_id"]}: SEITE_ES={attrs.get("SEITE_ES")}'
|
||||
break
|
||||
|
||||
def test_vario_kurven_gezaehlt(self, hundm05_testdata, hundm05_results):
|
||||
"""Jede Vario-Kurve im Journal muss in den VF_Bogen_*-Attributen
|
||||
auftauchen (vfl-insert-vario-kurve-block zaehlt sie mit)."""
|
||||
nach_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
soll = sum(1 for e in eingaben
|
||||
if e["typ"] == "step" and e["wert"] == "Vario-Kurve")
|
||||
attrs = nach_id[kopf["test_id"]]["actual_attributes"]
|
||||
ist = sum(int(attrs.get(f"VF_Bogen_{lage}_{w}", 0) or 0)
|
||||
for lage in ("A", "I") for w in (90, 60, 30))
|
||||
assert ist == soll, \
|
||||
f'{kopf["test_id"]}: {ist} Vario-Kurven in den Attributen, ' \
|
||||
f'{soll} im Journal'
|
||||
|
||||
def test_hoehe_von_ist_starthoehe(self, hundm05_testdata,
|
||||
hundm05_results):
|
||||
nach_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for kopf, eingaben in _ketten(hundm05_testdata):
|
||||
attrs = nach_id[kopf["test_id"]]["actual_attributes"]
|
||||
if "HOEHE_VON_mm" not in attrs:
|
||||
def test_gleiche_ergebnisse_wie_journal_pfad(self, spec_ergebnisse):
|
||||
"""Spec-Pfad == Journal-Pfad: dieselben Ketten, gebaut aus der Spec
|
||||
statt aus dem aufgezeichneten Journal, muessen dieselben Bloecke mit
|
||||
denselben Attributen ergeben. Vergleich gegen die Ergebnisse von
|
||||
TEST_HUNDM05, falls beide Laeufe vorliegen."""
|
||||
pfad = os.path.join(HIER, "output", "hm_recformat_results.json")
|
||||
if not os.path.isfile(pfad):
|
||||
pytest.skip("tests/output/hm_recformat_results.json fehlt "
|
||||
"(TEST_HM_RECFORMAT nicht gelaufen)")
|
||||
with open(pfad, encoding="utf-8") as fh:
|
||||
journal_ergebnisse = json.load(fh)
|
||||
if len(journal_ergebnisse) != len(spec_ergebnisse):
|
||||
pytest.skip("unterschiedlich viele Ketten in den beiden Laeufen")
|
||||
for spec_r, journal_r in zip(spec_ergebnisse, journal_ergebnisse):
|
||||
if journal_r["status"] != "executed":
|
||||
continue
|
||||
assert abs(int(attrs["HOEHE_VON_mm"]) - eingaben[1]["wert"]) <= 1, \
|
||||
f'{kopf["test_id"]}: HOEHE_VON_mm={attrs["HOEHE_VON_mm"]}, ' \
|
||||
f'Starthoehe {eingaben[1]["wert"]}'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DXF Geometrie-Pruefung (ezdxf)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Geometry:
|
||||
|
||||
def test_block_anzahl(self, hundm05_dxf, hundm05_testdata):
|
||||
"""Je Kette muss ein VF_-Block im Modelspace stehen."""
|
||||
n_vf = sum(1 for e in hundm05_dxf.modelspace()
|
||||
if e.dxftype() == "INSERT" and e.dxf.name.startswith("VF_"))
|
||||
soll = len(_ketten(hundm05_testdata))
|
||||
assert n_vf >= soll, f"VF_: {n_vf} < {soll}"
|
||||
|
||||
def test_handles_im_modelspace(self, hundm05_dxf, hundm05_results):
|
||||
"""Jedes gebaute Block-Handle (executed) liegt im Modelspace."""
|
||||
handles = {e.dxf.handle for e in hundm05_dxf.modelspace()
|
||||
if e.dxftype() == "INSERT"}
|
||||
for r in hundm05_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 ' \
|
||||
f'Modelspace'
|
||||
for tag, wert in journal_r["actual_attributes"].items():
|
||||
assert spec_r["actual_attributes"].get(tag) == wert, \
|
||||
f'{spec_r["spec_id"]}: Attribut {tag} ist ' \
|
||||
f'{spec_r["actual_attributes"].get(tag)!r}, ' \
|
||||
f'Journal-Pfad hatte {wert!r}'
|
||||
|
||||
@@ -8,7 +8,7 @@ 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/hundm05.json).
|
||||
(tests/testdata/hm_recformat.json).
|
||||
|
||||
Die 5 echten HundM-Ketten deckten zwei Zweige NICHT ab, weil ihre Daten sie
|
||||
nicht enthalten:
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
;; (lib/vf_journal_export.py) - drei Implementierungen derselben
|
||||
;; Grammatik, die einander pruefen.
|
||||
;; 2. Die 5 echten HundM-Ketten: die Spec-Datei
|
||||
;; tests/testdata/vf_spec_hundm05.json muss dieselben Journale ergeben
|
||||
;; wie das aufgezeichnete Protokoll tests/testdata/hundm05.json.
|
||||
;; tests/testdata/hundm05.json muss dieselben Journale ergeben
|
||||
;; wie das aufgezeichnete Protokoll tests/testdata/hm_recformat.json.
|
||||
;; Das ist der Beweis "Spec-Pfad == Journal-Pfad" auf Datenebene; der
|
||||
;; Beweis auf Geometrie-Ebene ist VF_SPEC_BAU (siehe tests/test_vf_spec.py).
|
||||
;;
|
||||
@@ -26,8 +26,9 @@
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_vf_spec.lsp"))
|
||||
;; TEST_VF_SPEC
|
||||
;;
|
||||
;; Speichert tests/output/vf_spec_results.json ist NICHT dieser Test -
|
||||
;; das schreibt VF_SPEC_BAU. Hier: tests/output/vf_spec_unit_results.json.
|
||||
;; Dieser Test baut KEINE Geometrie - er prueft nur den Uebersetzer.
|
||||
;; Ergebnis: tests/output/vf_spec_unit_results.json (Pass/Fail-Zaehler).
|
||||
;; Die gebaute Anlage prueft TEST_HUNDM05 (tests/test_hundm05.lsp).
|
||||
;; ============================================================
|
||||
|
||||
(setq *tsp-pass* 0 *tsp-fail* 0 *tsp-fails* '())
|
||||
@@ -362,8 +363,8 @@
|
||||
;; ============================================================
|
||||
;; 4. Die 5 echten HundM-Ketten: Spec == aufgezeichnetes Journal
|
||||
;; ============================================================
|
||||
;; Das aufgezeichnete Protokoll (hundm05.json) in Journal-Tokens
|
||||
;; zurueckuebersetzen - dieselbe Umrechnung wie in test_hundm05.lsp, hier
|
||||
;; Das aufgezeichnete Protokoll (hm_recformat.json) in Journal-Tokens
|
||||
;; zurueckuebersetzen - dieselbe Umrechnung wie in test_hm_recformat.lsp, hier
|
||||
;; bewusst dupliziert, damit der Test ohne den anderen Treiber laeuft.
|
||||
|
||||
(defun tsp-als-string (v)
|
||||
@@ -410,9 +411,9 @@
|
||||
|
||||
(defun tsp-tests-echte-ketten ( / p-datei s-datei p-daten specs journale i n
|
||||
spec soll ist sid)
|
||||
(setq p-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json"))
|
||||
(setq p-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hm_recformat.json"))
|
||||
(setq s-datei (strcat (getenv "DXFMAKRO")
|
||||
"/tests/testdata/vf_spec_hundm05.json"))
|
||||
"/tests/testdata/hundm05.json"))
|
||||
(cond
|
||||
((null (findfile p-datei))
|
||||
(princ (strcat "\n [SKIP] " p-datei " fehlt")))
|
||||
|
||||
+16
-120
@@ -1,20 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_vf_spec.py - Validiert die Spec-Ebene des VF-Linienzugs.
|
||||
|
||||
Drei Ebenen, die ersten zwei laufen ohne BricsCAD:
|
||||
test_vf_spec.py - Validiert den Spec-UEBERSETZER des VF-Linienzugs.
|
||||
Laeuft komplett ohne BricsCAD.
|
||||
|
||||
1. Rundlauf: fuer jede der 5 echten HundM-Ketten muss
|
||||
journal_aus_spec(spec_aus_journal(tokens)) == tokens gelten. Das
|
||||
beweist, dass die Spec das Eingabe-Journal VERLUSTFREI abbildet - sonst
|
||||
waere ein spec-gebauter Nachbau eine andere Kette als das Original.
|
||||
2. Die Spec-Testdaten (tests/testdata/vf_spec_hundm05.json): Struktur,
|
||||
Lesbarkeit durch den LISP-Parser, und dass sie dieselben Journale
|
||||
ergeben wie tests/testdata/hundm05.json.
|
||||
3. Die Ergebnisse von VF_SPEC_BAU (tests/output/vf_spec_results.json):
|
||||
jede Kette gebaut, kein Prompt, kein offener Journal-Wert, Glied-Folge
|
||||
wie geplant.
|
||||
2. Die Spec-Testdaten (tests/testdata/hundm05.json): Struktur, Lesbarkeit
|
||||
durch den LISP-Parser, und dass sie dieselben Journale ergeben wie die
|
||||
Aufzeichnung tests/testdata/hm_recformat.json.
|
||||
3. Validierung: was die Spec nicht hergibt, muss ein Fehler sein.
|
||||
4. Die Erwartungswerte in tests/test_vf_spec.lsp stimmen mit dem hier
|
||||
geprueften Emitter ueberein.
|
||||
|
||||
Die GEBAUTE Geometrie prueft tests/test_hundm05.py (braucht BricsCAD).
|
||||
|
||||
Der LISP-Emitter (vsp-journal in Lisp/vf_spec.lsp) ist die zweite
|
||||
Implementierung derselben Grammatik; er wird in tests/test_vf_spec.lsp gegen
|
||||
@@ -32,14 +33,11 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
|
||||
import vf_spec_export as vse # noqa: E402
|
||||
|
||||
HIER = os.path.dirname(__file__)
|
||||
PROTOKOLL = os.path.join(HIER, "testdata", "hundm05.json")
|
||||
SPEC_JSON = os.path.join(HIER, "testdata", "vf_spec_hundm05.json")
|
||||
|
||||
# VF_SPEC_BAU schreibt nach tests/output. Aeltere Laeufe (und ein Lauf mit
|
||||
# gesetztem DXFM_VF_SPEC_OUT) koennen die Datei woanders haben - darum beide
|
||||
# ueblichen Orte pruefen, statt den Test stumm zu ueberspringen.
|
||||
ERGEBNIS_ORTE = [os.path.join(HIER, "output", "vf_spec_results.json"),
|
||||
os.path.join(HIER, "..", "results", "vf_spec_results.json")]
|
||||
# Die AUFZEICHNUNG (rohes Journal aus der Kundenzeichnung, Frage-Antwort-
|
||||
# Protokoll) und die daraus abgeleitete SPEC. Erstere ist die Vorlage, an
|
||||
# der die zweite gemessen wird.
|
||||
PROTOKOLL = os.path.join(HIER, "testdata", "hm_recformat.json")
|
||||
SPEC_JSON = os.path.join(HIER, "testdata", "hundm05.json")
|
||||
|
||||
|
||||
def _lade(pfad, was):
|
||||
@@ -49,14 +47,6 @@ def _lade(pfad, was):
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def _lade_erstes(orte, was):
|
||||
for pfad in orte:
|
||||
if os.path.isfile(pfad):
|
||||
with open(pfad, encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
pytest.skip("%s fehlt (%s)" % (" bzw. ".join(orte), was))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def protokoll():
|
||||
return _lade(PROTOKOLL, "lib/vf_journal_export.py erzeugt es")
|
||||
@@ -67,13 +57,8 @@ def spec_objekte():
|
||||
return _lade(SPEC_JSON, "lib/vf_spec_export.py erzeugt es")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def spec_ergebnisse():
|
||||
return _lade_erstes(ERGEBNIS_ORTE, "VF_SPEC_BAU in BricsCAD erzeugt es")
|
||||
|
||||
|
||||
def _ketten(protokoll):
|
||||
"""Gleiche Gruppierregel wie hundm05:gruppiere in LISP."""
|
||||
"""Gleiche Gruppierregel wie hmrec:gruppiere in LISP."""
|
||||
ketten = []
|
||||
for obj in protokoll:
|
||||
if "test_id" in obj:
|
||||
@@ -458,92 +443,3 @@ class TestLispErwartungen:
|
||||
f" LISP: {ist}\n Python: {soll}\n"
|
||||
f"Entweder ist die Tokenfolge im LISP-Test falsch uebertragen, "
|
||||
f"oder die Spec hier und dort beschreibt nicht dasselbe.")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 5. Ergebnisse von VF_SPEC_BAU (braucht BricsCAD)
|
||||
# ============================================================
|
||||
|
||||
class TestSpecBau:
|
||||
|
||||
def test_je_spec_ein_ergebnis(self, spec_objekte, spec_ergebnisse):
|
||||
soll = [o["spec_id"] for o in spec_objekte if "spec_id" in o]
|
||||
ist = [r["spec_id"] for r in spec_ergebnisse]
|
||||
assert ist == soll
|
||||
|
||||
def test_status_executed(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["status"] == "executed", \
|
||||
f'{r["spec_id"]}: Status={r["status"]}, {r.get("fehler_text")}'
|
||||
|
||||
def test_keine_spec_fehler(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert not r["spec_fehler"], \
|
||||
f'{r["spec_id"]}: {r["spec_fehler"]}'
|
||||
|
||||
def test_kein_prompt(self, spec_ergebnisse):
|
||||
"""Der positive Beweis fuer "kein Dialog, keine Konsolenfrage": die
|
||||
zaehlenden Stubs wurden nie erreicht."""
|
||||
for r in spec_ergebnisse:
|
||||
assert r["prompts"] == 0, \
|
||||
f'{r["spec_id"]}: {r["prompts"]} Live-Eingabe(n) erreicht'
|
||||
|
||||
def test_journal_vollstaendig_verbraucht(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["eingaben_offen"] == 0, \
|
||||
f'{r["spec_id"]}: {r["eingaben_offen"]} Werte offen'
|
||||
assert r["eingaben_gesamt"] > 0
|
||||
|
||||
def test_glieder_wie_geplant(self, spec_ergebnisse):
|
||||
"""Der Desync-Detektor, der auch dann greift, wenn die Queue aufgeht:
|
||||
eine geometrisch abgewiesene Sektion verbraucht nur weniger
|
||||
Eintraege, die Glied-Folge weicht dann aber ab."""
|
||||
for r in spec_ergebnisse:
|
||||
assert r["glieder_ist"] == r["glieder_soll"], \
|
||||
f'{r["spec_id"]}: gebaut {r["glieder_ist"]}, ' \
|
||||
f'geplant {r["glieder_soll"]}'
|
||||
|
||||
def test_keine_meldungen(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert not r["meldungen"], \
|
||||
f'{r["spec_id"]}: Meldungen aus dem Bau: {r["meldungen"]}'
|
||||
|
||||
def test_vf_block_erzeugt(self, spec_ergebnisse):
|
||||
for r in spec_ergebnisse:
|
||||
assert r["block_name"].startswith("VF_"), \
|
||||
f'{r["spec_id"]}: Block {r["block_name"]!r}'
|
||||
assert r["block_handle"]
|
||||
|
||||
def test_startpunkt_getroffen(self, spec_objekte, spec_ergebnisse):
|
||||
"""Der Einfuegepunkt des VF_-Blocks ist der Kettenstart aus der
|
||||
Spec - er ist der einzige absolute Anker der ganzen Kette."""
|
||||
soll = dict((o["spec_id"], o["start_punkt"]) for o in spec_objekte
|
||||
if "spec_id" in o)
|
||||
for r in spec_ergebnisse:
|
||||
erwartet = soll[r["spec_id"]]
|
||||
ist = r["insert_point"]
|
||||
for achse, (e, i) in enumerate(zip(erwartet, ist)):
|
||||
assert abs(e - i) < 1.0, \
|
||||
f'{r["spec_id"]}: Achse {achse} ist {i}, erwartet {e}'
|
||||
|
||||
def test_gleiche_ergebnisse_wie_journal_pfad(self, spec_ergebnisse):
|
||||
"""Spec-Pfad == Journal-Pfad: dieselben Ketten, gebaut aus der Spec
|
||||
statt aus dem aufgezeichneten Journal, muessen dieselben Bloecke mit
|
||||
denselben Attributen ergeben. Vergleich gegen die Ergebnisse von
|
||||
TEST_HUNDM05, falls beide Laeufe vorliegen."""
|
||||
pfad = os.path.join(HIER, "output", "hundm05_results.json")
|
||||
if not os.path.isfile(pfad):
|
||||
pytest.skip("tests/output/hundm05_results.json fehlt "
|
||||
"(TEST_HUNDM05 nicht gelaufen)")
|
||||
with open(pfad, encoding="utf-8") as fh:
|
||||
journal_ergebnisse = json.load(fh)
|
||||
if len(journal_ergebnisse) != len(spec_ergebnisse):
|
||||
pytest.skip("unterschiedlich viele Ketten in den beiden Laeufen")
|
||||
for spec_r, journal_r in zip(spec_ergebnisse, journal_ergebnisse):
|
||||
if journal_r["status"] != "executed":
|
||||
continue
|
||||
for tag, wert in journal_r["actual_attributes"].items():
|
||||
assert spec_r["actual_attributes"].get(tag) == wert, \
|
||||
f'{spec_r["spec_id"]}: Attribut {tag} ist ' \
|
||||
f'{spec_r["actual_attributes"].get(tag)!r}, ' \
|
||||
f'Journal-Pfad hatte {wert!r}'
|
||||
|
||||
Vendored
+1492
File diff suppressed because it is too large
Load Diff
Vendored
+267
-1341
File diff suppressed because it is too large
Load Diff
Vendored
+18
-9
@@ -407,7 +407,7 @@ Dasselbe Protokoll steht produktionsseitig als **XDATA am fertigen
|
||||
wiederabspielbar (`vfl-journal-replay-start` + `vf-linienzug-modus`, genutzt
|
||||
vom Doppelklick-Editieren). Ein Journal aus einer echten Zeichnung laesst
|
||||
sich darum 1:1 in dieses Schema zuruecklesen — genau das tut
|
||||
`lib/vf_journal_export.py` (siehe Abschnitt 8, `hundm05.json`). Zwei
|
||||
`lib/vf_journal_export.py` (siehe Abschnitt 8, `hm_recformat.json`). Zwei
|
||||
Unterschiede zum handgeschriebenen `linienzug_tests.json`:
|
||||
|
||||
- Zusaetzlicher Eintragstyp `step` (Glied-Marker aus `vfl-journal-mark`,
|
||||
@@ -427,7 +427,7 @@ blieb an einem `getpoint` haengen. Mit `*vfl-headless*` = T ist das jetzt ein
|
||||
harter Abbruch mit Fundstelle (`vfl-headless-abbruch` -> `*vfl-headless-fehler*`
|
||||
nennt Art der Eingabe, Glied-Nummer und Eingabe-Nummer). Die Meldungen der
|
||||
geometrisch abgewiesenen Sektionen laufen ueber `vfl-meldung` statt `alert` und
|
||||
stehen in `*vfl-meldungen*`, blockieren also nichts mehr. `tests/test_hundm05.lsp`
|
||||
stehen in `*vfl-meldungen*`, blockieren also nichts mehr. `tests/test_hm_recformat.lsp`
|
||||
schaltet beides ein; die Diagnose landet im Ergebnis-JSON
|
||||
(`headless_fehler`, `meldungen`, `prompts`).
|
||||
|
||||
@@ -615,7 +615,7 @@ Vollstaendiges JSON:
|
||||
"endpunkt": [220499.01, 154926.53, 0.0]
|
||||
}
|
||||
```
|
||||
Einen Treiber dafuer gibt es derzeit nicht mehr: `tests/test_hundm05.lsp`
|
||||
Einen Treiber dafuer gibt es derzeit nicht mehr: `tests/test_hm_recformat.lsp`
|
||||
spielt seit dem Umbau auf die Linienzug-Journale nur noch Ketten ab (siehe
|
||||
Abschnitt 8); der fruehere `hundm05:build-gerade` steht in der Git-Historie.
|
||||
|
||||
@@ -640,9 +640,18 @@ Abschnitt 8); der fruehere `hundm05:build-gerade` steht in der Git-Historie.
|
||||
| Omniflo TV_*/APBW_* | - | **nein** (nicht implementiert, DUMMY) |
|
||||
| Separator/Scanner | `ils-insert-sensor` | **nein** (Schleife mit `getpoint`/`pause`) |
|
||||
|
||||
## 8. HundM05 — die 5 Linienzuege am Kreisel (`hundm05.json`)
|
||||
## 8. HundM05 — die 5 Linienzuege am Kreisel
|
||||
|
||||
`tests/testdata/hundm05.json` enthaelt die **Eingabe-Journale der 5
|
||||
Zwei Dateien beschreiben dieselben 5 Ketten, in zwei Formaten:
|
||||
|
||||
| Datei | Inhalt | Test |
|
||||
|---|---|---|
|
||||
| `tests/testdata/hm_recformat.json` | die **Aufzeichnung**: rohes Eingabe-Journal aus der XDATA der Kundenzeichnung, als Frage-Antwort-Protokoll | `TEST_HM_RECFORMAT` (in `alltests.json` abgeschaltet) |
|
||||
| `tests/testdata/hundm05.json` | die daraus abgeleitete **Spec** (Domaenenwerte statt Menue-Codes, 52 statt 281 Objekte) - hier werden weitere Ketten nachgetragen | `TEST_HUNDM05` |
|
||||
|
||||
Die Spec ist das Eingabeformat, die Aufzeichnung die Vorlage, an der sie gemessen wird (Rundlauf-Beweis in `tests/test_vf_spec.py`). Warum beide bleiben: die Aufzeichnung ist die einzige eingecheckte Kopie der Originaldaten (`data/polylines.dxf` ist zu gross fuer das Repo) UND der einzige Test dafuer, dass ein BESTEHENDER `VF_n`-Block weiter abspielbar ist - also dass Doppelklick-Edit und 2D/3D-Konvertierung an Altbestand funktionieren. Gebaut wird routinemaessig nur noch aus der Spec, damit nicht zweimal dieselbe Geometrie entsteht.
|
||||
|
||||
`tests/testdata/hm_recformat.json` enthaelt die **Eingabe-Journale der 5
|
||||
VarioFoerderer-Linienzuege** aus `data/polylines.dxf` (die Anlage HundM, ein
|
||||
Kreisel mit fuenf daran haengenden Ketten). Quelle ist nicht die gezeichnete
|
||||
Geometrie, sondern die XDATA `SSG_VF_EDIT` (Marker `"linienzug"`) an den
|
||||
@@ -675,7 +684,7 @@ Neu erzeugen:
|
||||
|
||||
```
|
||||
python lib/vf_journal_export.py data/polylines.dxf \
|
||||
tests/testdata/hundm05.json --csv results/HundM_export.csv
|
||||
tests/testdata/hm_recformat.json --csv results/HundM_export.csv
|
||||
```
|
||||
|
||||
Die frueheren Inhalte (Kreisel/Gefaellestrecke/Sensor-Eintraege aus dem
|
||||
@@ -690,10 +699,10 @@ Kopf-Objekte verschlucken. Darum steht je Kette ein Kopf-Objekt (traegt
|
||||
`linienzug_tests.json`. Der Anlagetyp im Kopf heisst `anlagetyp` und NICHT
|
||||
`typ`, damit Kopf und Eingabe unterscheidbar bleiben.
|
||||
|
||||
Abgespielt wird das per `tests/test_hundm05.lsp` (`TEST_HUNDM05`,
|
||||
Abgespielt wird das per `tests/test_hm_recformat.lsp` (`TEST_HM_RECFORMAT`,
|
||||
`TEST_HUNDM05_2D`/`_3D`): der Treiber gruppiert die Objekte
|
||||
(`hundm05:gruppiere`), baut daraus das Vorwaerts-Journal
|
||||
(`hundm05:eintrag->journal`) und spielt es je Kette ueber
|
||||
(`hmrec:gruppiere`), baut daraus das Vorwaerts-Journal
|
||||
(`hmrec:eintrag->journal`) und spielt es je Kette ueber
|
||||
`vfl-journal-replay-start` + `vf-linienzug-modus` ab — denselben Weg nimmt die
|
||||
Produktion beim Editieren/Konvertieren (`vfl-konvertiere-ent`). Bewusst KEINE
|
||||
Eingabe-Mocks wie in `test_linienzug.lsp`: ein Mock muesste zu jedem Segment
|
||||
|
||||
Vendored
-418
@@ -1,418 +0,0 @@
|
||||
[
|
||||
{
|
||||
"spec_id": "VF_spec_LZ_01",
|
||||
"modus": 1,
|
||||
"start_punkt": [4912.239129, 1230.886425, 2154.0],
|
||||
"start_hoehe": 2154.0,
|
||||
"as": 1,
|
||||
"as_winkel": "90",
|
||||
"as_seite": "links",
|
||||
"beschreibung": "Linienzug (Modus 1), Start Z2154: GF-Linie 2249 mm / 3 Grad -> Horizontal-VF 3257 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Horizontal-VF 1210 mm -> Vario-Kurve 30 rechts innen -> Vario-Kurve 30 links innen -> ES 90 links",
|
||||
"sivas_id": "0025",
|
||||
"sivas_bezeichnung": "VarioFoerderer :1",
|
||||
"planquadrat": "B/1",
|
||||
"block": "VF_87",
|
||||
"dxf_handle": "1BEB5",
|
||||
"quelle": "INSERT 1BEB5 (Block VF_87) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [4912.239129, 1230.886425, 0.000000]"
|
||||
},
|
||||
{
|
||||
"glied": "Linie-GF",
|
||||
"nr": 1,
|
||||
"dl": 2249.25106,
|
||||
"hz": 90.0,
|
||||
"gefaelle": "winkel",
|
||||
"winkel": 3.0,
|
||||
"ende": "nein"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 2,
|
||||
"dl": 3256.853781,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "nein",
|
||||
"vf_separator": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1434.100265,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 3,
|
||||
"dl": 1210.083164,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "ja-mit-es"
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 30,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1830.286247,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 30,
|
||||
"seite": "links",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "ES",
|
||||
"nr": 4,
|
||||
"winkel": "90",
|
||||
"seite": "links"
|
||||
},
|
||||
{
|
||||
"spec_id": "VF_spec_LZ_02",
|
||||
"modus": 1,
|
||||
"start_punkt": [8571.95068, 1276.061424, 2494.930831],
|
||||
"start_hoehe": 2494.930831,
|
||||
"as": 1,
|
||||
"as_winkel": "90",
|
||||
"as_seite": "links",
|
||||
"beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links",
|
||||
"sivas_id": "0019",
|
||||
"sivas_bezeichnung": "VarioFoerderer :5",
|
||||
"planquadrat": "D/1",
|
||||
"block": "VF_46",
|
||||
"dxf_handle": "EFD4",
|
||||
"quelle": "INSERT EFD4 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]"
|
||||
},
|
||||
{
|
||||
"glied": "Linie-GF",
|
||||
"nr": 1,
|
||||
"dl": 3149.399248,
|
||||
"hz": 90.0,
|
||||
"gefaelle": "winkel",
|
||||
"winkel": 3.0,
|
||||
"ende": "nein"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 2,
|
||||
"dl": 1574.912802,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "nein",
|
||||
"vf_separator": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 338.966171,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "auf-ab",
|
||||
"dl": 4528.583638,
|
||||
"hoehe": 2335.0,
|
||||
"winkel_idx": 4
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1443.864872,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "Linie",
|
||||
"nr": 3,
|
||||
"dl": 1153.407829,
|
||||
"hoehe": 2154.0,
|
||||
"segment_typ": "GF"
|
||||
},
|
||||
{
|
||||
"glied": "ES",
|
||||
"nr": 4,
|
||||
"winkel": "90",
|
||||
"seite": "links"
|
||||
},
|
||||
{
|
||||
"spec_id": "VF_spec_LZ_03",
|
||||
"modus": 1,
|
||||
"start_punkt": [12916.465798, 1276.061424, 2494.930831],
|
||||
"start_hoehe": 2494.930831,
|
||||
"as": 1,
|
||||
"as_winkel": "90",
|
||||
"as_seite": "links",
|
||||
"beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links",
|
||||
"sivas_id": "0018",
|
||||
"sivas_bezeichnung": "VarioFoerderer :4",
|
||||
"planquadrat": "F/1",
|
||||
"block": "VF_46",
|
||||
"dxf_handle": "FA5A",
|
||||
"quelle": "INSERT FA5A (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]"
|
||||
},
|
||||
{
|
||||
"glied": "Linie-GF",
|
||||
"nr": 1,
|
||||
"dl": 3149.399248,
|
||||
"hz": 90.0,
|
||||
"gefaelle": "winkel",
|
||||
"winkel": 3.0,
|
||||
"ende": "nein"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 2,
|
||||
"dl": 1574.912802,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "nein",
|
||||
"vf_separator": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 338.966171,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "auf-ab",
|
||||
"dl": 4528.583638,
|
||||
"hoehe": 2335.0,
|
||||
"winkel_idx": 4
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1443.864872,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "Linie",
|
||||
"nr": 3,
|
||||
"dl": 1153.407829,
|
||||
"hoehe": 2154.0,
|
||||
"segment_typ": "GF"
|
||||
},
|
||||
{
|
||||
"glied": "ES",
|
||||
"nr": 4,
|
||||
"winkel": "90",
|
||||
"seite": "links"
|
||||
},
|
||||
{
|
||||
"spec_id": "VF_spec_LZ_04",
|
||||
"modus": 1,
|
||||
"start_punkt": [17201.778323, 1205.360995, 2154.0],
|
||||
"start_hoehe": 2154.0,
|
||||
"as": 1,
|
||||
"as_winkel": "90",
|
||||
"as_seite": "links",
|
||||
"beschreibung": "Linienzug (Modus 1), Start Z2154: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links",
|
||||
"sivas_id": "0020",
|
||||
"sivas_bezeichnung": "VarioFoerderer :6",
|
||||
"planquadrat": "H/1",
|
||||
"block": "VF_46",
|
||||
"dxf_handle": "E498",
|
||||
"quelle": "INSERT E498 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]"
|
||||
},
|
||||
{
|
||||
"glied": "Linie-GF",
|
||||
"nr": 1,
|
||||
"dl": 3149.399248,
|
||||
"hz": 90.0,
|
||||
"gefaelle": "winkel",
|
||||
"winkel": 3.0,
|
||||
"ende": "nein"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 2,
|
||||
"dl": 1574.912802,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "nein",
|
||||
"vf_separator": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 338.966171,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "auf-ab",
|
||||
"dl": 4528.583638,
|
||||
"hoehe": 2335.0,
|
||||
"winkel_idx": 4
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1443.864872,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "Linie",
|
||||
"nr": 3,
|
||||
"dl": 1153.407829,
|
||||
"hoehe": 2154.0,
|
||||
"segment_typ": "GF"
|
||||
},
|
||||
{
|
||||
"glied": "ES",
|
||||
"nr": 4,
|
||||
"winkel": "90",
|
||||
"seite": "links"
|
||||
},
|
||||
{
|
||||
"spec_id": "VF_spec_LZ_05",
|
||||
"modus": 1,
|
||||
"start_punkt": [21490.273781, 1195.739767, 2494.930831],
|
||||
"start_hoehe": 2494.930831,
|
||||
"as": 1,
|
||||
"as_winkel": "90",
|
||||
"as_seite": "links",
|
||||
"beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links",
|
||||
"sivas_id": "0008",
|
||||
"sivas_bezeichnung": "VarioFoerderer :3",
|
||||
"planquadrat": "J/1",
|
||||
"block": "VF_46",
|
||||
"dxf_handle": "104E0",
|
||||
"quelle": "INSERT 104E0 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]"
|
||||
},
|
||||
{
|
||||
"glied": "Linie-GF",
|
||||
"nr": 1,
|
||||
"dl": 3149.399248,
|
||||
"hz": 90.0,
|
||||
"gefaelle": "winkel",
|
||||
"winkel": 3.0,
|
||||
"ende": "nein"
|
||||
},
|
||||
{
|
||||
"glied": "Horizontal-VF",
|
||||
"nr": 2,
|
||||
"dl": 1574.912802,
|
||||
"gf_verteilung": "einlauf",
|
||||
"vf_sep_vor": 0,
|
||||
"vf_sep_nach": 0,
|
||||
"vf_ende": "nein",
|
||||
"vf_separator": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 338.966171,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "vario-kurve",
|
||||
"winkel": 90,
|
||||
"seite": "rechts",
|
||||
"variante": "innen"
|
||||
},
|
||||
{
|
||||
"sub": "auf-ab",
|
||||
"dl": 4528.583638,
|
||||
"hoehe": 2335.0,
|
||||
"winkel_idx": 4
|
||||
},
|
||||
{
|
||||
"sub": "horizontal",
|
||||
"dl": 1443.864872,
|
||||
"sep_vor": 0,
|
||||
"sep_nach": 0
|
||||
},
|
||||
{
|
||||
"sub": "motorstation"
|
||||
},
|
||||
{
|
||||
"glied": "Linie",
|
||||
"nr": 3,
|
||||
"dl": 1153.407829,
|
||||
"hoehe": 2154.0,
|
||||
"segment_typ": "GF"
|
||||
},
|
||||
{
|
||||
"glied": "ES",
|
||||
"nr": 4,
|
||||
"winkel": "90",
|
||||
"seite": "links"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user