- leere, frisch initialisierte Datenbanken der Versionen verglichen - datasetprogramme erkennen jetzt auch suche nach standardtabellen wie uom
7.6 KiB
Modellbaum-Vergleich (5.0 ↔ 8.0)
Werkzeug für die Testdaten-Pipeline: Bevor Testdaten von Hand ergänzt werden, erstmal statisch (kein laufendes Tryton nötig) Schwarz auf Weiß sehen, welche Modelle/Felder es in einer Version überhaupt gibt - damit weder Felder aus 8.0 übernommen werden, die es in 5.0 noch gar nicht gibt (würde beim Anlegen nur Fehler werfen), noch Aufwand in Modelle gesteckt wird, die sich zwischen den Versionen gar nicht verändert haben.
Workflow
1. extract_model_tree.py – Modellbaum aus dem Quellcode ziehen
Scannt den Python-Code ausgewählter Module per AST (kein Import, keine
laufende Instanz) und baut pro Modell (__name__) die Liste aller per
fields.X(...) definierten Felder, mit Typ, Relationsziel
(Many2One/One2Many/Many2Many/One2One/Reference) und - falls literal im
Code erkennbar - required. Modelle werden über PoolMeta-Erweiterungen
hinweg zusammengeführt: Fügt z. B. stock_split eine Klasse mit
__name__ = 'stock.move' hinzu, landen deren Felder unter demselben
Modell-Eintrag, mit Vermerk, aus welchem Modul sie stammen.
Pro Modell wird zusätzlich persisted erkannt (erbt die Klasse ModelSQL
o. ä., oder nur ModelView - dann ist es ein reines Wizard-/
Transient-Formular ohne eigene Tabelle).
Verfolgt zusätzlich ganz normale Python-Vererbung innerhalb derselben
Datei: erbt eine Klasse mit eigenem __name__ von einer anderen lokalen
Klasse - z. B. class BOMOutput(BOMInput): __name__ = 'production.bom.output'
in production/bom.py, die selbst keine einzige fields.X(...)-Zeile
deklariert, sondern alle Felder von BOMInput übernimmt - werden die
geerbten Felder mit aufgenommen (eigene Felder überschreiben geerbte bei
Namensgleichheit). Ohne das sähe ein rein über Vererbung wiederverwendetes
Modell wie eines OHNE Felder aus, und Änderungen daran (z. B. eine
Feldumbenennung im Elternteil) blieben komplett unsichtbar - genau das hat
sql_dump_diff.py (Schritt 4) einmal live aufgedeckt.
Die Basisklassen-Auflösung ist bewusst auf "gleiche Datei" beschränkt
(nicht "gleiches Modul"): derselbe lokale Klassenname wie Cron taucht
innerhalb eines Moduls oft mehrfach für völlig unterschiedliche Klassen
auf (Tryton-Konvention: eine PoolMeta-Erweiterung heißt oft genauso wie
das Original, z. B. currency/currency.py definiert class Cron(ModelSQL, ModelView): __name__ = 'currency.cron', während currency/ir.py
unabhängig davon eine andere Klasse class Cron(metaclass=PoolMeta): zur
Erweiterung von ir.cron definiert) - ein modulweites Registry würde sich
da gegenseitig überschreiben.
python3 extract_model_tree.py --repo /mnt/tryton \
--modules company country currency party product ... \
--output reports/model_tree_5.0.json --report reports/model_tree_5.0.md
Für eine zweite Version denselben Befehl gegen einen zweiten Checkout
laufen lassen (z. B. per git worktree add /tmp/tryton-8.0 8.0, ohne den
Haupt-Checkout umzuschalten).
2. model_diff.py – zwei Modellbäume vergleichen
Sortiert jedes Modell in eine von fünf Kategorien:
- entfernt – Modell gibt es in Version A, nicht mehr in B
- geändert – Feld(er) entfernt oder Typ/Relation/required geändert → echter Kandidat für einen Vergleichslauf
- neu – Modell gibt es erst in B
- nur ergänzt – nur neue Felder dazugekommen, Rest unverändert
- unverändert – komplett identisch geblieben
python3 model_diff.py --tree-a reports/model_tree_5.0.json --label-a 5.0 \
--tree-b reports/model_tree_8.0.json --label-b 8.0 \
--output reports/model_diff.json --report reports/model_diff.md
3. model_coverage.py – gegen die aktuelle Testdaten-Abdeckung abgleichen
Vergleicht einen Modellbaum mit einer gemergten dataset.yaml
(reports/<version>_testdata_merged.yaml, siehe
TESTDATEN.md) und zeigt komplett unabgedeckte Modelle sowie
pro teilweise abgedecktem Modell die ungenutzten Felder (required zuerst).
Drei Kategorien werden aus der Liste unabgedeckter Modelle per Default herausgefiltert (nie von Hand testdatenrelevant, jeweils per Flag wieder einschaltbar):
ir.*/res.*-Kernmodelle (--include-framework)- Wizard-/Transient-Formulare,
persisted: false(--include-transient) - automatisch verwaltete Many2Many-Verknüpfungstabellen, erkennbar am
Bindestrich im Namen wie
party.party-party.category(--include-relations)
Mit --diff reports/model_diff.json wird zusätzlich pro Modell die
Diff-Kategorie aus Schritt 2 eingeblendet und die Liste danach sortiert:
geändert/entfernt zuerst (lohnt sich, dort Testdaten zu ergänzen),
nur ergänzt/unverändert zuletzt (dort ist über den reinen
Codevergleich hinaus erstmal nichts Auffälliges zu erwarten).
python3 model_coverage.py --model-tree reports/model_tree_5.0.json \
--dataset reports/5_testdata_merged.yaml --diff reports/model_diff.json \
--report reports/coverage_5.0.md
4. sql_dump_diff.py – gegen den echten DB-Zustand validieren
Alles bisherige ist reine Code-Analyse. Zum Cross-Check: zwei mit
pg_dump (Klartext-Format) erzeugte Dumps frisch initialisierter (leerer)
Datenbanken beider Versionen strukturell vergleichen - Tabellen
entfernt/neu, Spalten hinzugefügt/entfernt/Typ geändert, Zeilenzahl
(z. B. mehr XML-Fixture-Daten in der neueren Version). Zeilen werden
bereits vollständig geparst (für eine spätere inhaltliche
Vergleichs-Erweiterung, sobald es Snapshots mit echten Testdaten gibt),
aktuell nutzt der Report davon aber nur die Zeilenzahl.
Mit --model-diff reports/model_diff.json wird pro betroffener Tabelle
zusätzlich geprüft, ob die rein statische Code-Analyse (Schritt 2)
dieselbe Änderung schon vorhergesagt hatte (Modellname -> Tabellenname:
Punkte durch Unterstriche ersetzen, wie extract_updates.py/
compare_snapshots.py es auch tun) - Übereinstimmung bzw. Abweichung wird
markiert.
python3 sql_dump_diff.py --dump-a data/leer5.sql --label-a 5.0 \
--dump-b data/leer8.sql --label-b 8.0 \
--model-diff reports/model_diff.json \
--output reports/dump_diff_5.0_8.0.json --report reports/dump_diff_5.0_8.0.md
Stand: 78 von 92 vergleichbaren Fällen stimmen überein (~85%). Die
verbleibenden Abweichungen gehen meist in eine erwartbare Richtung: die
Code-Analyse findet MEHR Veränderung, als im leeren DB-Schema sichtbar
ist (z. B. ein entferntes Function-Feld ohne eigene Spalte, oder
Selection-Wertänderungen ohne Typänderung) - kein Fehler, sondern zwei
Werkzeuge, die unterschiedliche Ebenen messen (Python-Feld-Definition vs.
physisches SQL-Schema). Der production.bom.output-Fund oben (Schritt 1)
kam genau aus einer vorherigen Abweichung dieser Art zustande.
Bekannte Grenzen
- Rein statische Analyse: Felder, die nicht als einfaches
name = fields.X(...)auf Klassenebene stehen (z. B. per Schleife/Helper dynamisch erzeugt), werden nicht erkannt. - Python-Vererbung wird nur INNERHALB derselben Datei aufgelöst (s. o.). Erbt eine Klasse von einer Basisklasse aus einer anderen Datei desselben oder eines anderen Moduls (Import statt lokaler Definition), werden deren Felder nicht gefunden - unauffällig, aber ein bekannter blinder Fleck.
persisted-Erkennung ist eine Textsuche über die Basisklassen-Ausdrücke (ModelSQL/Workflow/DeactivableMixin) - deckt den Normalfall ab, ist aber kein echtes Type-Checking.- Erkennt nur Unterschiede auf Ebene der Feld-Definition im Code
(Typ/Relation/required). Laufzeitverhalten wie Constraints, Defaults oder
DB-Spaltenpräzision kann trotzdem abweichen, ohne hier aufzufallen - dafür
bleibt der eigentliche Testlauf über
create_dataset.py/compare_snapshots.pynötig.