Files
starRiverProperty/maven-cw-elevator-application/tools/elevator_api_parity/parity/compare.py
T
反编译工作区 038f846dad docs+test: 发布包对拍计划、pytest 双端 API 对拍与 run_elevator_parity 脚本
- docs/elevator-api-parity: 计划/报告模板/示例
- tools/elevator_api_parity: 端点目录、fixtures、对拍 client/compare、报告生成
- scripts/run_elevator_parity: JDK8 构建 + 单测/对拍(无服务时跳过对拍用例)

Made-with: Cursor

Former-commit-id: 3d54a40e1a7ae0b1724261d4f18910a6f415f853
2026-04-25 09:50:32 +08:00

95 lines
2.8 KiB
Python

"""
Compare two JSON response bodies. CloudwalkResult: compare top-level `code` when
both parse as objects; also deep-compare with optional key stripping.
"""
from __future__ import annotations
import copy
import json
from typing import Any, List, Set
# JSONPath-like: tuple of keys in order for nesting
DEFAULT_STRIP_PATHS: Set[tuple] = {
# ("data", "ts"),
}
def _strip(obj: Any, rules: Set[tuple], prefix: tuple) -> Any:
if not isinstance(obj, (dict, list)):
return obj
if isinstance(obj, list):
return [_strip(x, rules, prefix + (i,)) for i, x in enumerate(obj)]
out = copy.deepcopy(obj)
for k, v in list(obj.items()) if isinstance(obj, dict) else []:
p = prefix + (k,)
if p in rules:
if k in out:
del out[k]
continue
if isinstance(v, (dict, list)):
out[k] = _strip(v, rules, p)
return out
def parse_json_loose(text: str) -> Any:
if not (text or "").strip():
return None
return json.loads(text)
def normalize(
data: Any,
strip_paths: Set[tuple] | None = None,
) -> str:
rules = strip_paths if strip_paths is not None else DEFAULT_STRIP_PATHS
s = _strip(data, rules, tuple())
return json.dumps(s, ensure_ascii=False, sort_keys=True, default=str)
def business_code(data: Any) -> str | None:
if isinstance(data, dict) and "code" in data:
c = data.get("code")
return str(c) if c is not None else None
return None
def same_shape(old_text: str, new_text: str) -> bool:
try:
a = parse_json_loose(old_text)
b = parse_json_loose(new_text)
except json.JSONDecodeError:
return False
if type(a) != type(b):
return False
if isinstance(a, dict) and isinstance(b, dict) and a.keys() == b.keys():
return all(type(a[k]) is type(b[k]) for k in a)
if isinstance(a, list) and isinstance(b, list):
if len(a) == len(b) and len(a) == 0:
return True
return False
def describe_diff(
old_text: str, new_text: str, compare_norm: str = "deep"
) -> str:
try:
o = parse_json_loose(old_text)
n = parse_json_loose(new_text)
except json.JSONDecodeError as e:
return f"json parse: {e}"
if compare_norm == "code_only":
c1, c2 = business_code(o), business_code(n)
if c1 == c2:
return ""
return f"business code: old={c1!r} new={c2!r}"
no = normalize(o)
nn = normalize(n)
if no == nn:
return ""
if business_code(o) is not None and business_code(n) is not None and business_code(o) != business_code(
n
):
return f"normalized JSON differs; first code: old={business_code(o)} new={business_code(n)}"
return "normalized JSON differs (see full bodies in test log)"