Files
uha4 a55774eff2 neues skript sql_dump_diff.py zum Vergleich von 2 sql-dumps.
- leere, frisch initialisierte Datenbanken der Versionen verglichen
- datasetprogramme erkennen jetzt auch suche nach standardtabellen wie uom
2026-08-22 17:45:46 +02:00

7.6 KiB
Raw Permalink Blame History

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.py nötig.