feat: add detailed method changes in compatibility analysis and update tests
This commit is contained in:
@@ -66,14 +66,55 @@ def _axis_counts(block: dict[str, Any]) -> dict[str, int]:
|
||||
}
|
||||
|
||||
|
||||
def _changes_str(changes: dict[str, list]) -> str:
|
||||
return ", ".join("{0} {1}→{2}".format(f, v[0], v[1]) for f, v in sorted(changes.items()))
|
||||
|
||||
|
||||
def _types_named(block: dict[str, Any]) -> dict[str, list[str]]:
|
||||
"""Type-axis changes as display strings (types are global, not owner-scoped)."""
|
||||
return {
|
||||
"added": sorted("{0} → {1} (size {2})".format(
|
||||
t["script_name"], t.get("cpp_class") or "?", t.get("object_size")) for t in block["added"]),
|
||||
"removed": sorted(t["script_name"] for t in block["removed"]),
|
||||
"changed": sorted("{0} ({1})".format(c["item"]["script_name"], _changes_str(c["changes"]))
|
||||
for c in block["changed"]),
|
||||
}
|
||||
|
||||
|
||||
def _owner_named(block: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Owner-scoped axis (methods/events/fields) grouped by class, so the diff reads as
|
||||
'which class gained/lost/changed which member' — the bit that was missing from the counts."""
|
||||
by: dict[str, dict[str, list[str]]] = {}
|
||||
|
||||
def slot(owner: str) -> dict[str, list[str]]:
|
||||
return by.setdefault(owner or "?", {"added": [], "removed": [], "changed": []})
|
||||
|
||||
for it in block["added"]:
|
||||
slot(it["owner"])["added"].append(it["name"])
|
||||
for it in block["removed"]:
|
||||
slot(it["owner"])["removed"].append(it["name"])
|
||||
for c in block["changed"]:
|
||||
it = c["item"]
|
||||
slot(it["owner"])["changed"].append("{0} ({1})".format(it["name"], _changes_str(c["changes"])))
|
||||
|
||||
return {"by_owner": {o: {k: sorted(v) for k, v in d.items()} for o, d in sorted(by.items())}}
|
||||
|
||||
|
||||
def _vs_reference(reference: Snapshot, variant: Snapshot) -> dict[str, Any]:
|
||||
"""How a divergent variant differs from its library's reference variant: per-axis +/-/~ counts
|
||||
plus the pooled surface-overlap score, reusing the existing diff/similarity engines."""
|
||||
"""How a divergent variant differs from its library's reference variant: per-axis +/-/~ counts,
|
||||
the pooled surface-overlap score, and a `detail` block naming *which* classes/methods/events/
|
||||
fields changed (so the table can say 'CMC_Animo lost SHOW', not just 'methods −1')."""
|
||||
diff = compute_diff(reference, variant)
|
||||
return {
|
||||
"overall": surface_similarity(reference, variant)["overall"],
|
||||
"axes": {axis: _axis_counts(diff[axis]) for axis in _AXES},
|
||||
"moved_methods": len(diff["moved_methods"]),
|
||||
"detail": {
|
||||
"types": _types_named(diff["types"]),
|
||||
"methods": _owner_named(diff["methods"]),
|
||||
"events": _owner_named(diff["events"]),
|
||||
"fields": _owner_named(diff["fields"]),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +257,46 @@ def _short(sha: str | None) -> str:
|
||||
return (sha or "?")[:8]
|
||||
|
||||
|
||||
def _signed(sign: str, items: list[str], cap: int = 15) -> str:
|
||||
head = [sign + it for it in items[:cap]]
|
||||
if len(items) > cap:
|
||||
head.append("…+{0}".format(len(items) - cap))
|
||||
return ", ".join(head)
|
||||
|
||||
|
||||
def _emit_detail(out: list[str], vs: dict[str, Any]) -> None:
|
||||
"""Nested, named breakdown of a divergent variant: which types changed, and per class which
|
||||
methods/events/fields were added (`+`), removed (`−`) or changed (`~`)."""
|
||||
det = vs.get("detail") or {}
|
||||
|
||||
t = det.get("types") or {}
|
||||
toks = []
|
||||
if t.get("added"):
|
||||
toks.append(_signed("+", t["added"]))
|
||||
if t.get("removed"):
|
||||
toks.append(_signed("−", t["removed"]))
|
||||
if t.get("changed"):
|
||||
toks.append(_signed("~", t["changed"]))
|
||||
if toks:
|
||||
out.append(" - typy: {0}".format(" ".join(toks)))
|
||||
|
||||
for axis, label in (("methods", "metody"), ("events", "eventy"), ("fields", "pola")):
|
||||
by = (det.get(axis) or {}).get("by_owner") or {}
|
||||
if not by:
|
||||
continue
|
||||
out.append(" - {0} (klasy ze zmianami):".format(label))
|
||||
for owner in by: # already sorted at build time
|
||||
d = by[owner]
|
||||
parts = []
|
||||
if d.get("added"):
|
||||
parts.append(_signed("+", d["added"]))
|
||||
if d.get("removed"):
|
||||
parts.append(_signed("−", d["removed"]))
|
||||
if d.get("changed"):
|
||||
parts.append(_signed("~", d["changed"]))
|
||||
out.append(" - **{0}**: {1}".format(owner, " ".join(parts)))
|
||||
|
||||
|
||||
def render_markdown(table: dict[str, Any]) -> str:
|
||||
games = table["games"]
|
||||
libs = table["libraries"]
|
||||
@@ -224,7 +305,8 @@ def render_markdown(table: dict[str, Any]) -> str:
|
||||
"Grupowane po nazwie biblioteki. Komórka: pusta = brak; `✓` = obecna (jedyny wariant); "
|
||||
"litera wariantu (`A`, `B\\*`, …) gdy ta sama nazwa biblioteki ma **różne powierzchnie "
|
||||
"silnika** — `\\*` oznacza rozbieżność względem wariantu referencyjnego (`A`). "
|
||||
"Szczegóły rozbieżności w sekcji *Warianty* niżej."
|
||||
"Sekcja *Warianty* niżej wymienia per klasa **które** metody/eventy/pola doszły (`+`), "
|
||||
"zniknęły (`−`) lub się zmieniły (`~`)."
|
||||
)
|
||||
out.append("")
|
||||
out.append("_Wygenerowano: {0} · gier: {1} · bibliotek: {2}_".format(
|
||||
@@ -276,6 +358,7 @@ def render_markdown(table: dict[str, Any]) -> str:
|
||||
moved = " · przeniesionych metod {0}".format(vs["moved_methods"]) if vs["moved_methods"] else ""
|
||||
out.append(
|
||||
" - vs **A**: overlap {0}% · {1}{2}".format(vs["overall"], deltas, moved))
|
||||
_emit_detail(out, vs)
|
||||
out.append("")
|
||||
|
||||
return "\n".join(out).rstrip() + "\n"
|
||||
|
||||
Reference in New Issue
Block a user