mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 16:01:39 +00:00
150 lines
5.5 KiB
Python
150 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def _summary_path(path: Path) -> Path:
|
|
if path.is_dir():
|
|
return path / "summary.json"
|
|
return path
|
|
|
|
|
|
def load_baseline_summary(path: Path) -> dict[str, Any]:
|
|
summary_path = _summary_path(path)
|
|
try:
|
|
data = json.loads(summary_path.read_text(encoding="utf-8"))
|
|
except FileNotFoundError as error:
|
|
raise RuntimeError(f"missing baseline report summary: {summary_path}") from error
|
|
except json.JSONDecodeError as error:
|
|
raise RuntimeError(f"invalid baseline report summary JSON: {summary_path}") from error
|
|
if not isinstance(data, dict):
|
|
raise RuntimeError(f"baseline report summary must be an object: {summary_path}")
|
|
suites = data.get("suites")
|
|
if suites is not None and not isinstance(suites, list):
|
|
raise RuntimeError(f"baseline report summary suites must be a list: {summary_path}")
|
|
return data
|
|
|
|
|
|
def _suite_key(summary: dict[str, Any]) -> str | None:
|
|
suite = summary.get("suite")
|
|
return str(suite) if suite else None
|
|
|
|
|
|
def _by_suite(summaries: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
return {key: summary for summary in summaries if (key := _suite_key(summary)) is not None}
|
|
|
|
|
|
def _int_value(value: Any) -> int | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _total_failures(summary: dict[str, Any] | None) -> int | None:
|
|
if summary is None:
|
|
return None
|
|
return _int_value(summary.get("total_failures"))
|
|
|
|
|
|
def _gate_failures(summary: dict[str, Any] | None) -> int | None:
|
|
if summary is None:
|
|
return None
|
|
return _int_value(summary.get("gate_failures", summary.get("total_failures")))
|
|
|
|
|
|
def _case_count(summary: dict[str, Any] | None) -> int | None:
|
|
if summary is None:
|
|
return None
|
|
cases = summary.get("cases")
|
|
if isinstance(cases, (dict, list, tuple)):
|
|
return len(cases)
|
|
if cases is not None:
|
|
return _int_value(cases)
|
|
runs = summary.get("runs")
|
|
if isinstance(runs, (dict, list, tuple)):
|
|
return len(runs)
|
|
return None
|
|
|
|
|
|
def _delta(current: int | None, baseline: int | None) -> int | None:
|
|
if current is None or baseline is None:
|
|
return None
|
|
return current - baseline
|
|
|
|
|
|
def _suite_status(current: dict[str, Any] | None, baseline: dict[str, Any] | None) -> str:
|
|
if baseline is None:
|
|
return "added"
|
|
if current is None:
|
|
return "removed"
|
|
fields = (
|
|
_total_failures(current) != _total_failures(baseline),
|
|
_gate_failures(current) != _gate_failures(baseline),
|
|
_case_count(current) != _case_count(baseline),
|
|
)
|
|
return "changed" if any(fields) else "unchanged"
|
|
|
|
|
|
def build_report_diff(
|
|
*,
|
|
current_summaries: list[dict[str, Any]],
|
|
baseline_summary: dict[str, Any],
|
|
baseline_path: Path,
|
|
) -> dict[str, Any]:
|
|
baseline_summaries = [summary for summary in baseline_summary.get("suites", []) if isinstance(summary, dict)]
|
|
current_by_suite = _by_suite(current_summaries)
|
|
baseline_by_suite = _by_suite(baseline_summaries)
|
|
rows: list[dict[str, Any]] = []
|
|
|
|
for suite in sorted(current_by_suite.keys() | baseline_by_suite.keys()):
|
|
current = current_by_suite.get(suite)
|
|
baseline = baseline_by_suite.get(suite)
|
|
current_total = _total_failures(current)
|
|
baseline_total = _total_failures(baseline)
|
|
current_gate = _gate_failures(current)
|
|
baseline_gate = _gate_failures(baseline)
|
|
current_cases = _case_count(current)
|
|
baseline_cases = _case_count(baseline)
|
|
rows.append(
|
|
{
|
|
"suite": suite,
|
|
"status": _suite_status(current, baseline),
|
|
"baseline_total_failures": baseline_total,
|
|
"current_total_failures": current_total,
|
|
"total_failures_delta": _delta(current_total, baseline_total),
|
|
"baseline_gate_failures": baseline_gate,
|
|
"current_gate_failures": current_gate,
|
|
"gate_failures_delta": _delta(current_gate, baseline_gate),
|
|
"baseline_cases": baseline_cases,
|
|
"current_cases": current_cases,
|
|
"cases_delta": _delta(current_cases, baseline_cases),
|
|
}
|
|
)
|
|
|
|
baseline_total_failures = sum(_total_failures(summary) or 0 for summary in baseline_summaries)
|
|
current_total_failures = sum(_total_failures(summary) or 0 for summary in current_summaries)
|
|
baseline_gate_failures = sum(_gate_failures(summary) or 0 for summary in baseline_summaries)
|
|
current_gate_failures = sum(_gate_failures(summary) or 0 for summary in current_summaries)
|
|
by_status = {status: sum(1 for row in rows if row["status"] == status) for status in ("added", "removed", "changed", "unchanged")}
|
|
|
|
return {
|
|
"schema_version": 1,
|
|
"baseline": str(_summary_path(baseline_path)),
|
|
"summary": {
|
|
**by_status,
|
|
"total_changes": by_status["added"] + by_status["removed"] + by_status["changed"],
|
|
"baseline_total_failures": baseline_total_failures,
|
|
"current_total_failures": current_total_failures,
|
|
"total_failures_delta": current_total_failures - baseline_total_failures,
|
|
"baseline_gate_failures": baseline_gate_failures,
|
|
"current_gate_failures": current_gate_failures,
|
|
"gate_failures_delta": current_gate_failures - baseline_gate_failures,
|
|
},
|
|
"suites": rows,
|
|
}
|