- 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
139 lines
5.5 KiB
Python
139 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
model_diff.py
|
|
|
|
Vergleicht zwei mit extract_model_tree.py erzeugte Modellbäume (z. B.
|
|
model_tree_5.0.json gegen model_tree_8.0.json) auf Modell- und Feldebene
|
|
und sortiert jedes Modell in eine von vier Kategorien:
|
|
|
|
- entfernt: Modell gibt es in der ersten Version, aber nicht mehr
|
|
in der zweiten.
|
|
- neu: Modell gibt es erst in der zweiten Version.
|
|
- geändert: Modell existiert in beiden, aber mind. ein Feld wurde
|
|
entfernt oder hat sich (Typ/Relation/required)
|
|
geändert. Das sind die Kandidaten, bei denen sich ein
|
|
echter Testlauf gegen beide Versionen lohnt.
|
|
- unverändert / nur ergänzt: Modell ist identisch geblieben oder hat nur
|
|
zusätzliche Felder bekommen - hier ist über den reinen
|
|
Code-Vergleich hinaus erstmal nichts Auffälliges zu
|
|
erwarten (Laufzeitverhalten wie Constraints/Defaults
|
|
kann natürlich trotzdem abweichen, das sieht man mit
|
|
dieser rein statischen Analyse nicht).
|
|
|
|
Nutzung:
|
|
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
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def diff_field(fa, fb):
|
|
changes = []
|
|
if fa["type"] != fb["type"]:
|
|
changes.append(f"type: {fa['type']} -> {fb['type']}")
|
|
if fa["relation"] != fb["relation"]:
|
|
changes.append(f"relation: {fa['relation']} -> {fb['relation']}")
|
|
if fa["required"] != fb["required"]:
|
|
changes.append(f"required: {fa['required']} -> {fb['required']}")
|
|
return changes
|
|
|
|
|
|
def diff_trees(tree_a, tree_b):
|
|
result = {}
|
|
for model_name in sorted(set(tree_a) | set(tree_b)):
|
|
if model_name not in tree_b:
|
|
result[model_name] = {"category": "entfernt", "added": [], "removed": [], "changed": {}}
|
|
continue
|
|
if model_name not in tree_a:
|
|
result[model_name] = {"category": "neu", "added": [], "removed": [], "changed": {}}
|
|
continue
|
|
|
|
fields_a = tree_a[model_name]["fields"]
|
|
fields_b = tree_b[model_name]["fields"]
|
|
added = sorted(set(fields_b) - set(fields_a))
|
|
removed = sorted(set(fields_a) - set(fields_b))
|
|
changed = {}
|
|
for name in sorted(set(fields_a) & set(fields_b)):
|
|
c = diff_field(fields_a[name], fields_b[name])
|
|
if c:
|
|
changed[name] = c
|
|
|
|
if removed or changed:
|
|
category = "geändert"
|
|
elif added:
|
|
category = "nur ergänzt"
|
|
else:
|
|
category = "unverändert"
|
|
|
|
result[model_name] = {
|
|
"category": category,
|
|
"added": added,
|
|
"removed": removed,
|
|
"changed": changed,
|
|
}
|
|
return result
|
|
|
|
|
|
def write_report(diff, out_path, label_a, label_b):
|
|
by_category = {}
|
|
for model_name, info in diff.items():
|
|
by_category.setdefault(info["category"], []).append(model_name)
|
|
|
|
lines = [f"# Modellbaum-Diff: {label_a} -> {label_b}\n"]
|
|
for category, count_note in [
|
|
("entfernt", "Modell existiert nicht mehr"),
|
|
("geändert", "Feld(er) entfernt oder geändert - Kandidat für einen echten Vergleichslauf"),
|
|
("neu", f"Modell gibt es erst ab {label_b}"),
|
|
("nur ergänzt", "nur neue Felder dazugekommen, Rest unverändert"),
|
|
("unverändert", "komplett identisch geblieben"),
|
|
]:
|
|
models = sorted(by_category.get(category, []))
|
|
lines.append(f"\n## {category} ({len(models)}) - {count_note}\n")
|
|
for model_name in models:
|
|
info = diff[model_name]
|
|
if category == "geändert":
|
|
lines.append(f"\n### `{model_name}`")
|
|
if info["removed"]:
|
|
lines.append(f"- entfernte Felder: {', '.join(info['removed'])}")
|
|
for field_name, changes in info["changed"].items():
|
|
lines.append(f"- **{field_name}**: {'; '.join(changes)}")
|
|
if info["added"]:
|
|
lines.append(f"- (zusätzlich neue Felder: {', '.join(info['added'])})")
|
|
else:
|
|
lines.append(f"- `{model_name}`")
|
|
|
|
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--tree-a", required=True)
|
|
parser.add_argument("--tree-b", required=True)
|
|
parser.add_argument("--label-a", default="A")
|
|
parser.add_argument("--label-b", default="B")
|
|
parser.add_argument("--output", default="reports/model_diff.json")
|
|
parser.add_argument("--report", default="reports/model_diff.md")
|
|
args = parser.parse_args()
|
|
|
|
tree_a = json.loads(Path(args.tree_a).read_text(encoding="utf-8"))
|
|
tree_b = json.loads(Path(args.tree_b).read_text(encoding="utf-8"))
|
|
|
|
diff = diff_trees(tree_a, tree_b)
|
|
|
|
Path(args.output).write_text(json.dumps(diff, indent=2, ensure_ascii=False, sort_keys=True), encoding="utf-8")
|
|
write_report(diff, Path(args.report), args.label_a, args.label_b)
|
|
|
|
counts = {}
|
|
for info in diff.values():
|
|
counts[info["category"]] = counts.get(info["category"], 0) + 1
|
|
summary = ", ".join(f"{v} {k}" for k, v in counts.items())
|
|
print(f"{summary} -> {args.output}, {args.report}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|