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

157 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Modellbaum-Vergleich (5.0 ↔ 8.0)
Werkzeug für die [Testdaten-Pipeline](TESTDATEN.md): 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.
```bash
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
```bash
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](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).
```bash
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.
```bash
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.