Proteus-Zweig: dataset.yaml-Pipeline und 5.0/8.0-Modellbaumvergleich
- create_dataset.py, compare_snapshots.py, rst_to_dataset.py: Proteus- Testdatensatz anlegen/vergleichen bzw. aus *.rst-Szenarien extrahieren - create_dataset.py: Feld-Alias-Fallback (FIELD_RENAMES) für bekannte Umbenennungen wie stock.move.uom -> unit, bidirektional - merge_dedup_rsts.py: Extrahierte YAMLs pro Version zusammenführen, versionsinterne Duplikate entfernen, abhängigkeitskorrekt sortieren - extract_model_tree.py: Modellbaum (Modelle/Felder) statisch aus dem Tryton-Quellcode ziehen, für 5.0 und 8.0 - model_diff.py, model_coverage.py: 5.0<->8.0-Modelländerungen finden und gegen die aktuelle Testdatenabdeckung abgleichen, priorisiert nach tatsächlicher Änderung - reports/*_merged.yaml: Extraktionsergebnisse für die aktiven Module (rohe *.rst-Quellen bewusst nicht mitversioniert) - Ordner doc ergänzt
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# 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).
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 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.
|
||||
- `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.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Proteus-Testdaten-Pipeline
|
||||
|
||||
Ergänzender Ansatz zur [Codeanalyse](UPDATEANALYSE.md): Statt den
|
||||
`__register__`-Code zu lesen, wird ein deklarativ definierter Testdatensatz
|
||||
über Proteus in zwei frisch installierten Tryton-Instanzen (5.0 und 8.0)
|
||||
tatsächlich angelegt. Ziel ist, dieselbe Datei gegen beide Versionen laufen
|
||||
zu lassen, damit sowohl die Skript-Ausgabe (`compare_snapshots.py`) als
|
||||
auch anschließend die SQL-Dumps beider Datenbanken vergleichbar sind -
|
||||
dafür müssen inhaltlich identische Daten in derselben Reihenfolge
|
||||
eingespielt werden.
|
||||
|
||||
## Workflow
|
||||
|
||||
```
|
||||
tests/scenario_*.rst (Tryton-Repo, pro Modul)
|
||||
│ rst_to_dataset.py
|
||||
▼
|
||||
rsts/<version>e/*.yaml (ein YAML-Record pro angelegtem Proteus-Objekt)
|
||||
│ rsts_merge_dedup.py
|
||||
▼
|
||||
reports/<version>_testdata_merged.yaml (dedupliziert, abhängigkeitskorrekt sortiert)
|
||||
│ create_dataset.py --database ...
|
||||
▼
|
||||
snapshot_<version>.json
|
||||
│ compare_snapshots.py snapshot_a.json snapshot_b.json
|
||||
▼
|
||||
Fund-Liste: status_changed / value_changed / missing_record
|
||||
```
|
||||
|
||||
### 1. `rst_to_dataset.py` – Testdaten aus den Modul-eigenen Tests extrahieren
|
||||
|
||||
Tryton-Module bringen eigene `tests/scenario_*.rst`-Doctests mit (ausführbarer
|
||||
Proteus-Code in `>>>`-Zeilen). Das Skript parst diese Dateien per AST und
|
||||
erkennt:
|
||||
|
||||
```
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> party = Party(name='Testfirma GmbH')
|
||||
>>> party.vat_number = '123'
|
||||
>>> party.save()
|
||||
```
|
||||
|
||||
→ wird zu einem YAML-Record mit `key`, `model`, `fields`. Referenzen auf
|
||||
zuvor im selben File angelegte Records werden zu `{"$ref": "..."}`,
|
||||
`Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`. Alles
|
||||
nicht automatisch Auflösbare (Wizards, `.click(...)`, Helper-Funktionsaufrufe)
|
||||
wird als `{"$manual": "<Ausdruck>"}` markiert oder ganz übersprungen -
|
||||
bewusst sichtbar unvollständig statt still falsch.
|
||||
|
||||
Record-Keys werden mit dem Dateinamen präfixiert, damit beim
|
||||
Zusammenführen mehrerer Module keine Kollisionen entstehen.
|
||||
|
||||
### 2. `rsts_merge_dedup.py` – pro Version zusammenführen
|
||||
|
||||
Liest alle `rsts/<version>e/*.yaml` eines Verzeichnisses ein und entfernt
|
||||
Records mit identischem `model`+`fields` (z. B. dasselbe
|
||||
Basis-`product.template`, das in vielen Szenarien wiederverwendet wird).
|
||||
`$ref`-Verweise auf entfernte Duplikate werden auf den behaltenen Record
|
||||
umgeschrieben. Danach wird per Kahn-Algorithmus so sortiert, dass
|
||||
`$ref`-Abhängigkeiten immer VOR dem referenzierenden Record stehen, aber
|
||||
innerhalb der Freiheitsgrade weiterhin nach `model` gruppiert wird.
|
||||
|
||||
**Achtung:** Dedupliziert bewusst GLOBAL, auch innerhalb derselben Datei.
|
||||
Bei Szenarien, die zwei absichtlich gleich aussehende Records anlegen (z. B.
|
||||
`scenario_party_replace.rst`: zwei Parteien namens "Pam", um eine zu
|
||||
ersetzen), verändert das den Sinn des Szenarios - für einen groben
|
||||
Testdatensatz ist das in Ordnung, für szenario-treue Tests nicht.
|
||||
|
||||
### 3. `create_dataset.py` – gegen eine echte Instanz anlegen
|
||||
|
||||
Legt die Records der Reihe nach per Proteus an, liest jeden Wert danach
|
||||
FRISCH VOM SERVER zurück (nicht aus dem lokalen Proteus-Cache), damit
|
||||
automatische Reformatierung sichtbar wird. Ergebnis: ein JSON-Snapshot
|
||||
(`status`/`error`/`stored` pro Record).
|
||||
|
||||
**Feld-Umbenennungen zwischen Versionen** (`FIELD_RENAMES`): Da dieselbe
|
||||
YAML gegen mehrere Versionen laufen soll, aber Felder umbenannt werden
|
||||
können (bestätigt: `stock.move.uom` → `stock.move.unit` zwischen 5.0 und
|
||||
8.0), prüft das Skript pro Modell zuerst per `fields_get()`, ob der
|
||||
YAML-Feldname auf dem jeweiligen Server existiert, und weicht sonst auf
|
||||
einen bekannten, bidirektionalen Alias aus. Ein wirklich unbekanntes Feld
|
||||
bleibt trotzdem ein Fehler - das ist ja gerade das gesuchte Signal für eine
|
||||
echte Schema-Änderung. Weitere Umbenennungen bei Bedarf in `FIELD_RENAMES`
|
||||
ergänzen, sobald ein Lauf sie aufdeckt.
|
||||
|
||||
### 4. `compare_snapshots.py` – zwei Versionen gegenüberstellen
|
||||
|
||||
Vergleicht zwei Snapshots (gleiche YAML, zwei Versionen/Datenbanken) und
|
||||
markiert `status_changed` (ein Record schlägt nur in einer Version fehl -
|
||||
Kandidat für Format-/Constraint-/Pflichtfeld-Änderung), `value_changed`
|
||||
(gespeicherter Wert weicht trotz gleichem Input ab - automatische
|
||||
Reformatierung) und `missing_record`.
|
||||
|
||||
## Stand
|
||||
|
||||
- `rst_to_dataset.py` und `rsts_merge_dedup.py` sind gegen echte
|
||||
`scenario_*.rst`-Dateien aus `/mnt/tryton` (5.0- und 8.0-Branch) getestet,
|
||||
siehe `reports/5_testdata_merged.yaml` / `reports/8_testdata_merged.yaml`
|
||||
(jeweils NUR aus den Szenarien der jeweiligen Version extrahiert, noch
|
||||
NICHT zu einer gemeinsamen Datei zusammengeführt).
|
||||
- `create_dataset.py` und `compare_snapshots.py` sind noch nicht gegen eine
|
||||
echte Tryton-Instanz gelaufen (nur syntaktisch geprüft).
|
||||
- Welche Modelle/Felder überhaupt Testdaten brauchen bzw. wo sich 5.0 und
|
||||
8.0 tatsächlich unterscheiden, siehe [MODELLVERGLEICH.md](MODELLVERGLEICH.md)
|
||||
- das steuert, wo als Nächstes von Hand YAML-Records ergänzt werden.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Tryton DB-Migrationsanalyse (AST-Zweig)
|
||||
|
||||
Analysiert per statischem Code-Scan, welche Datenbank-Operationen die
|
||||
`__register__`-Methoden aller Tryton-Module über mehrere Versionen hinweg
|
||||
ausführen – ohne die Module tatsächlich zu installieren/upzudaten.
|
||||
|
||||
## Workflow
|
||||
|
||||
```bash
|
||||
./run.sh
|
||||
```
|
||||
|
||||
Checkt der Reihe nach jeden Versions-Branch (`5.0` bis `8.0`) im
|
||||
Tryton-Repo aus, lässt `extract_updates.py` pro Version über alle konfigurierten
|
||||
Module laufen (Ergebnis: `data/<version>/<modul>.json`), und ruft danach
|
||||
`compare_updates.py` auf, das daraus die aggregierten Reports in `reports/updateChanges/*.md`
|
||||
erzeugt (ein Report pro Modul, gegliedert nach Tabelle und Version).
|
||||
|
||||
Einzeln aufrufbar:
|
||||
```bash
|
||||
python3 extract_updates.py --repo <pfad-zum-tryton-clone> --output data
|
||||
python3 compare_updates.py
|
||||
```
|
||||
|
||||
## Was `extract_updates.py` erkennt
|
||||
|
||||
- Erkannte DB-Operationen (`column_rename`, `add_column`, `update`,
|
||||
`insert`, `execute`, ... – siehe `INTERESTING_OPS`) inkl. Tabelle(n),
|
||||
Zeile, Bedingung (`if`-Kontext), Phase (`before_super`/`after_super`).
|
||||
- Steigt in `for`/`while`/`with`/`try` ab (nicht nur `if`).
|
||||
- Löst Aufrufe an Helper-Methoden derselben Klasse auf
|
||||
(`cls._migrate_x(...)`) und markiert die gefundenen Operationen mit
|
||||
`"via": [{"method": ..., "line": ...}]`.
|
||||
- Loggt alle sonstigen, nicht erkannten Aufrufe zusätzlich als
|
||||
`"unresolved": true` – Rohmaterial für die manuelle Sichtung, gefiltert
|
||||
um bekanntes Rauschen (`Pool()`, `Transaction()`, `cursor`, `.append`,
|
||||
`pool.get`, `logger.*`, `__table__`/`__table_handler__`).
|
||||
|
||||
## Was `extract_updates.py` NICHT erkennt (bewusste Grenzen)
|
||||
|
||||
- **Cross-Datei-Aufrufe**: Ruft `__register__` eine Funktion aus einem
|
||||
anderen Modul/Utility-Modul auf (z. B. `migrate_property(...)` aus
|
||||
`trytond.model.modelstorage`), wird das nicht verfolgt – landet als
|
||||
`unresolved`, der eigentliche DB-Zugriff bleibt unsichtbar.
|
||||
- **XML-Datendateien** (`ir.model.data`) – bewusst ausgeklammert, da
|
||||
Konfigurationsdaten ohnehin manuell geprüft werden müssen.
|
||||
- **`.sql`-Dateien** in Modulen – werden nicht gescannt.
|
||||
- **Implizite Schema-Änderungen** durch reine Feldtyp-Änderungen im
|
||||
Python-Code (ohne expliziten `add_column`/`alter_size`-Aufruf).
|
||||
|
||||
## `compare_updates.py` – Aggregation & Dedup
|
||||
|
||||
Fasst pro Tabelle die Operationen aller Versionen zusammen und zeigt eine
|
||||
Operation nur dann erneut an, wenn sie sich vom **tatsächlichen,
|
||||
vollständigen Bestand der unmittelbar vorherigen Version** unterscheidet
|
||||
(nicht: irgendwann schon mal gesehen). Das behandelt korrekt sowohl den
|
||||
Normalfall (Migrationscode bleibt über viele Versionen unverändert – wird
|
||||
nur einmal angezeigt) als auch den Fall, dass ein Block zwischenzeitlich
|
||||
durch etwas anderes ersetzt und später identisch wieder eingeführt wird
|
||||
(taucht dann korrekt erneut auf).
|
||||
|
||||
Zeigt keine "wurde entfernt"-Markierung – nur Neues/Verändertes.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
- Modul-/Repo-Defaults: oben in `extract_updates.py` (`DEFAULT_REPO`,
|
||||
`DEFAULT_MODULES`).
|
||||
- Versions-Range: `START`/`END` in `run.sh`.
|
||||
- Rauschfilter für `unresolved`: `IGNORED_UNRESOLVED_*` in `extract_updates.py` –
|
||||
bei Bedarf erweitern, wenn sich beim Sichten neue, eindeutig irrelevante
|
||||
Muster zeigen (siehe Git-Historie für die bisherige Herleitung).
|
||||
|
||||
## Verwandter Zweig
|
||||
|
||||
Ergänzender, unabhängiger Ansatz: Proteus-basierter Vergleich (echte
|
||||
Testdaten in zwei frisch installierten Versionen anlegen, Fehler/
|
||||
Reformatierung erkennen) – siehe [doc/TESTDATEN.md](TESTDATEN.md) und
|
||||
[doc/MODELLVERGLEICH.md](MODELLVERGLEICH.md).
|
||||
Reference in New Issue
Block a user