mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
299 lines
11 KiB
Python
299 lines
11 KiB
Python
from __future__ import annotations
|
||
|
||
import os
|
||
import shlex
|
||
import sys
|
||
from collections.abc import Sequence
|
||
from contextlib import AbstractContextManager
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from .models import (
|
||
Coverage,
|
||
HarnessRun,
|
||
RunStatus,
|
||
Strategy,
|
||
section_confidence,
|
||
)
|
||
|
||
STATUS_GLYPHS = {
|
||
RunStatus.NOT_RUN: "·",
|
||
RunStatus.QUEUED: "○",
|
||
RunStatus.RUNNING: "◉",
|
||
RunStatus.PASSED: "✓",
|
||
RunStatus.FAILED: "✗",
|
||
RunStatus.SKIPPED: "↷",
|
||
RunStatus.ERROR: "!",
|
||
RunStatus.MISSING: "?",
|
||
RunStatus.PLANNED: "—",
|
||
RunStatus.NOT_APPLICABLE: "n/a",
|
||
}
|
||
|
||
STATUS_STYLES = {
|
||
RunStatus.QUEUED: "dim",
|
||
RunStatus.RUNNING: "bold cyan",
|
||
RunStatus.PASSED: "bold green",
|
||
RunStatus.FAILED: "bold red",
|
||
RunStatus.SKIPPED: "yellow",
|
||
RunStatus.ERROR: "bold red",
|
||
RunStatus.MISSING: "magenta",
|
||
RunStatus.PLANNED: "dim",
|
||
RunStatus.NOT_APPLICABLE: "dim",
|
||
}
|
||
|
||
|
||
def _format_duration(seconds: float) -> str:
|
||
if seconds < 1:
|
||
return f"{seconds * 1000:.0f}ms"
|
||
if seconds < 60:
|
||
return f"{seconds:.1f}s"
|
||
return f"{int(seconds // 60)}m {seconds % 60:.0f}s"
|
||
|
||
|
||
def _rerun_command(nodeid: str) -> str:
|
||
if nodeid.startswith("unit-suite:"):
|
||
return "uv run python -m tests.rust-python-harness.strategies.unit_tests.runner --plain"
|
||
return f"poetry run pytest {shlex.quote(nodeid)} -q -o consider_namespace_packages=true"
|
||
|
||
|
||
def _summary(run: HarnessRun) -> tuple[int, int, int, int]:
|
||
outcomes: dict[str, RunStatus] = {}
|
||
for result in run.results.values():
|
||
outcomes.update(result.outcomes)
|
||
return (
|
||
list(outcomes.values()).count(RunStatus.PASSED),
|
||
list(outcomes.values()).count(RunStatus.FAILED),
|
||
list(outcomes.values()).count(RunStatus.ERROR),
|
||
list(outcomes.values()).count(RunStatus.SKIPPED),
|
||
)
|
||
|
||
|
||
def _cell_text(run: HarnessRun, strategy_id: str, sdk_function: str, surface: str = "sdk") -> tuple[str, str]:
|
||
key = f"{strategy_id}:{sdk_function}" if surface == "sdk" else f"{strategy_id}:gateway:{sdk_function}"
|
||
result = run.results.get(key)
|
||
if result is None:
|
||
return "", ""
|
||
counts = ""
|
||
if result.total:
|
||
counts = f" {len(result.completed)}/{result.total}"
|
||
coverage = " ◐" if result.case.coverage is Coverage.PARTIAL else ""
|
||
return f"{STATUS_GLYPHS[result.status]}{counts}{coverage}", STATUS_STYLES.get(
|
||
result.status, ""
|
||
)
|
||
|
||
|
||
class RichDashboard(AbstractContextManager["RichDashboard"]):
|
||
def __init__(
|
||
self,
|
||
strategies: Sequence[Strategy],
|
||
confidence_strategies: Sequence[Strategy],
|
||
) -> None:
|
||
from rich.console import Console
|
||
from rich.live import Live
|
||
|
||
self.strategies = strategies
|
||
self.confidence_strategies = confidence_strategies
|
||
self.console = Console()
|
||
self.live: Any = Live(
|
||
console=self.console, refresh_per_second=12, transient=False
|
||
)
|
||
|
||
def _table(self, run: HarnessRun) -> Any:
|
||
from rich import box
|
||
from rich.table import Table
|
||
from rich.text import Text
|
||
|
||
columns = tuple(dict.fromkeys((case.surface, case.sdk_function) for strategy in self.strategies for case in strategy.cases))
|
||
narrow = self.console.width < 96
|
||
if narrow:
|
||
table = Table(box=box.SIMPLE_HEAVY, expand=True, show_header=False)
|
||
table.add_column("Strategy", ratio=3)
|
||
table.add_column("Results", ratio=5)
|
||
for strategy in self.strategies:
|
||
values = []
|
||
for surface, sdk_function in columns:
|
||
value, style = _cell_text(run, strategy.id, sdk_function, surface)
|
||
if value:
|
||
values.append(
|
||
Text.assemble((f"{surface}/{sdk_function} ", "dim"), (value, style))
|
||
)
|
||
table.add_row(strategy.label, Text(" ").join(values))
|
||
return table
|
||
|
||
table = Table(box=box.ROUNDED, expand=True, title="Strategy × API")
|
||
table.add_column("Strategy", ratio=3)
|
||
for surface, label in columns:
|
||
table.add_column(label if surface == "sdk" else f"gateway/{label}", justify="center", ratio=1)
|
||
for strategy in self.strategies:
|
||
cells = []
|
||
for surface, sdk_function in columns:
|
||
value, style = _cell_text(run, strategy.id, sdk_function, surface)
|
||
cells.append(Text(value, style=style))
|
||
table.add_row(strategy.label, *cells)
|
||
return table
|
||
|
||
def __enter__(self) -> "RichDashboard":
|
||
self.live.__enter__()
|
||
return self
|
||
|
||
def __exit__(self, *args: object) -> None:
|
||
self.live.__exit__(*args)
|
||
|
||
def update(self, run: HarnessRun) -> None:
|
||
from rich.markup import escape
|
||
from rich.panel import Panel
|
||
|
||
active = run.current_nodeid or "Waiting for test events…"
|
||
if len(active) > max(40, self.console.width - 16):
|
||
active = f"…{active[-(self.console.width - 17):]}"
|
||
passed, failed, errors, skipped = _summary(run)
|
||
progress = (
|
||
f"[bold]{run.completed_tests}/{run.unique_tests}[/bold] tests "
|
||
f"[green]{passed} passed[/green] [red]{failed + errors} failed[/red] "
|
||
f"[yellow]{skipped} skipped[/yellow] [dim]{_format_duration(run.duration)}[/dim]"
|
||
)
|
||
legend = "✓ pass ✗ fail ! error ↷ skip\n? configured test missing — planned ◐ partial coverage"
|
||
self.live.update(
|
||
Panel(
|
||
self._table(run),
|
||
title="⚡ Rust ↔ Python parity lab",
|
||
subtitle=f"{progress}\n[dim]{escape(active)}[/dim]\n{legend}",
|
||
border_style="cyan",
|
||
)
|
||
)
|
||
|
||
def finish(self, run: HarnessRun, exit_code: int) -> None:
|
||
self.update(run)
|
||
if run.failures:
|
||
from rich.markup import escape
|
||
from rich.panel import Panel
|
||
|
||
for nodeid, detail in run.failures[:5]:
|
||
rerun = _rerun_command(nodeid)
|
||
self.console.print(
|
||
Panel(
|
||
f"{escape(detail)}\n\n[bold]Rerun just this test[/bold]\n"
|
||
f"[cyan]{escape(rerun)}[/cyan]",
|
||
title=f"✗ {escape(nodeid)}",
|
||
border_style="red",
|
||
)
|
||
)
|
||
durations: dict[str, float] = {}
|
||
for result in run.results.values():
|
||
for nodeid, duration in result.durations.items():
|
||
durations[nodeid] = max(duration, durations.get(nodeid, 0.0))
|
||
if durations:
|
||
slow = sorted(durations.items(), key=lambda item: item[1], reverse=True)[:3]
|
||
self.console.print(
|
||
"[bold]Slowest tests[/bold] "
|
||
+ " • ".join(
|
||
f"{Path(nodeid).name} [dim]{_format_duration(duration)}[/dim]"
|
||
for nodeid, duration in slow
|
||
)
|
||
)
|
||
from rich import box
|
||
from rich.table import Table
|
||
|
||
confidence_table = Table(
|
||
title="Port confidence by API", box=box.ROUNDED, expand=True
|
||
)
|
||
confidence_table.add_column("SDK section")
|
||
confidence_table.add_column("Score", justify="right")
|
||
confidence_table.add_column("Confidence")
|
||
confidence_table.add_column("Strategy evidence", ratio=4)
|
||
confidence_styles = {"HIGH": "green", "MEDIUM": "yellow", "LOW": "red"}
|
||
for score in section_confidence(run, self.confidence_strategies):
|
||
confidence_table.add_row(
|
||
score.sdk_function,
|
||
f"{score.verified_strategies}/{score.required_strategies} {score.percentage}%",
|
||
f"[{confidence_styles[score.level.value]}]{score.level.value}[/]",
|
||
" ".join(score.details),
|
||
)
|
||
self.console.print(confidence_table)
|
||
self.console.print(
|
||
"[dim]Score = required strategies with passing evidence. "
|
||
"LOC coverage remains a separate report.[/dim]"
|
||
)
|
||
style = "green" if exit_code == 0 else "red"
|
||
self.console.print(
|
||
f"[{style}]Harness finished in {_format_duration(run.duration)} "
|
||
f"(exit {exit_code})[/{style}]"
|
||
)
|
||
|
||
|
||
class PlainDashboard(AbstractContextManager["PlainDashboard"]):
|
||
def __init__(
|
||
self,
|
||
strategies: Sequence[Strategy],
|
||
confidence_strategies: Sequence[Strategy],
|
||
) -> None:
|
||
self.strategies = strategies
|
||
self.confidence_strategies = confidence_strategies
|
||
self._seen: dict[str, tuple[RunStatus, int]] = {}
|
||
|
||
def __enter__(self) -> "PlainDashboard":
|
||
print("Rust <-> Python SDK parity harness", flush=True)
|
||
return self
|
||
|
||
def __exit__(self, *args: object) -> None:
|
||
return None
|
||
|
||
def update(self, run: HarnessRun) -> None:
|
||
for key, result in run.results.items():
|
||
state = (result.status, len(result.completed))
|
||
if self._seen.get(key) != state:
|
||
self._seen[key] = state
|
||
progress = (
|
||
f" {len(result.completed)}/{result.total}" if result.total else ""
|
||
)
|
||
print(
|
||
f"{STATUS_GLYPHS[result.status]} {key}: {result.status.value}{progress}",
|
||
flush=True,
|
||
)
|
||
|
||
def finish(self, run: HarnessRun, exit_code: int) -> None:
|
||
self.update(run)
|
||
passed, failed, errors, skipped = _summary(run)
|
||
print(
|
||
f"Summary: {passed} passed, {failed} failed, {errors} errors, "
|
||
f"{skipped} skipped in {_format_duration(run.duration)}",
|
||
flush=True,
|
||
)
|
||
for nodeid, detail in run.failures[:5]:
|
||
print(f"{nodeid}: {detail}", flush=True)
|
||
print(f"Rerun: {_rerun_command(nodeid)}", flush=True)
|
||
print("Port confidence by API", flush=True)
|
||
for score in section_confidence(run, self.confidence_strategies):
|
||
print(
|
||
f" {score.sdk_function:12} "
|
||
f"{score.verified_strategies}/{score.required_strategies} "
|
||
f"{score.percentage:3}% {score.level.value:6} "
|
||
f"{' | '.join(score.details)}",
|
||
flush=True,
|
||
)
|
||
print(
|
||
" Score = required strategies with passing evidence; LOC is reported separately.",
|
||
flush=True,
|
||
)
|
||
print(f"Harness finished with exit code {exit_code}", flush=True)
|
||
|
||
|
||
def make_dashboard(
|
||
strategies: Sequence[Strategy],
|
||
plain: bool = False,
|
||
confidence_strategies: Sequence[Strategy] | None = None,
|
||
) -> RichDashboard | PlainDashboard:
|
||
confidence_strategies = confidence_strategies or strategies
|
||
interactive_terminal = (
|
||
sys.stdout.isatty()
|
||
and not os.environ.get("CI")
|
||
and os.environ.get("TERM") != "dumb"
|
||
)
|
||
if not plain and interactive_terminal:
|
||
try:
|
||
import rich # noqa: F401
|
||
|
||
return RichDashboard(strategies, confidence_strategies)
|
||
except ImportError:
|
||
pass
|
||
return PlainDashboard(strategies, confidence_strategies)
|