1
0
This commit is contained in:
Aiden
2026-05-27 11:50:10 +10:00
parent 0d099235c5
commit c0304c575c
55 changed files with 26035 additions and 16 deletions

View File

@@ -0,0 +1,121 @@
from __future__ import annotations
import argparse
import json
import sys
from collections import Counter
from pathlib import Path
from typing import Any, TextIO
from .bench_connect_lcd import format_frame, label_frame, parse_frame
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Compare two serial_scenario result JSON files and highlight extra button/report traffic."
)
parser.add_argument("baseline", type=Path, help="baseline result JSON, usually a no-button run")
parser.add_argument("candidate", type=Path, help="candidate result JSON, usually a one-button run")
parser.add_argument("--show-labels", action="store_true", help="also show label count deltas")
return parser
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
args = build_arg_parser().parse_args(argv)
baseline = _load_result(args.baseline)
candidate = _load_result(args.candidate)
print(format_comparison(baseline, candidate, show_labels=args.show_labels), file=stdout)
return 0
def _load_result(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8") as handle:
result = json.load(handle)
if not isinstance(result, dict):
raise SystemExit(f"{path} is not a result JSON object")
result["_path"] = str(path)
return result
def format_comparison(
baseline: dict[str, Any],
candidate: dict[str, Any],
*,
show_labels: bool = False,
) -> str:
base_targets = Counter(_string_int_mapping(baseline.get("ack_targets", {})))
candidate_targets = Counter(_string_int_mapping(candidate.get("ack_targets", {})))
target_delta = candidate_targets - base_targets
lines = [
"Serial scenario comparison",
f"baseline={baseline.get('_path', '')}",
f"candidate={candidate.get('_path', '')}",
f"baseline_log={baseline.get('log', '')}",
f"candidate_log={candidate.get('log', '')}",
f"baseline_rx_frames={baseline.get('rx_frames', 0)} candidate_rx_frames={candidate.get('rx_frames', 0)}",
f"baseline_ack_sent={baseline.get('ack_sent', 0)} candidate_ack_sent={candidate.get('ack_sent', 0)}",
]
if target_delta:
lines.append("extra ACK-target frames in candidate:")
for frame_text, count in sorted(target_delta.items(), key=_frame_sort_key):
lines.append(f" +{count:3d} {frame_text} {_describe_frame(frame_text)}")
else:
lines.append("no extra ACK-target frames found in candidate")
missing = base_targets - candidate_targets
if missing:
lines.append("baseline ACK-target frames missing/decreased in candidate:")
for frame_text, count in sorted(missing.items(), key=_frame_sort_key):
lines.append(f" -{count:3d} {frame_text} {_describe_frame(frame_text)}")
if show_labels:
base_labels = Counter(_string_int_mapping(baseline.get("labels", {})))
candidate_labels = Counter(_string_int_mapping(candidate.get("labels", {})))
label_delta = candidate_labels - base_labels
lines.append("label count increases:")
if label_delta:
for label, count in sorted(label_delta.items()):
lines.append(f" +{count:3d} {label}")
else:
lines.append(" none")
return "\n".join(lines)
def _string_int_mapping(raw: Any) -> dict[str, int]:
if not isinstance(raw, dict):
return {}
output: dict[str, int] = {}
for key, value in raw.items():
try:
output[str(key)] = int(value)
except (TypeError, ValueError):
continue
return output
def _describe_frame(frame_text: str) -> str:
frame = parse_frame(frame_text)
selector = ((frame[1] & 0x7F) << 7) | frame[2]
value = (frame[3] << 8) | frame[4]
label = label_frame(frame) or "checksum_ok_unlabeled"
return f"cmd=0x{frame[0]:02X} selector=0x{selector:04X} value=0x{value:04X} label={label}"
def _frame_sort_key(item: tuple[str, int]) -> tuple[int, int, int, str]:
frame_text, _count = item
try:
frame = parse_frame(frame_text)
except argparse.ArgumentTypeError:
return (0xFFFF, 0xFFFF, 0xFFFF, frame_text)
selector = ((frame[1] & 0x7F) << 7) | frame[2]
value = (frame[3] << 8) | frame[4]
return (selector, frame[0], value, frame_text)
__all__ = [
"build_arg_parser",
"format_comparison",
"main",
]