litellm/tests/rust-python-harness/shared/tracing/steps.py
yujonglee ee08c36fc0
refactor(tests): restructure rust python harness around strategy definitions (#39628)
* wip

* refactor(tests): move sdk function tracing into rust python harness

* dead code

* fix: handle harness keyboard interrupts

* refactor(tests): deduplicate rust python harness helpers

* fix(harness): expose validated strategy choices

* wip

* refactor(harness): let strategies own parity reports

* docs(harness): update strategy structure

* refactor(harness): localize strategy report views

* wip

* fix(harness): satisfy mapping runner type checks

* fix(harness): clarify trace parity output

* wip

* fix(harness): clarify unit mapping report

* fix(harness): finalize trace parity contracts

* refactor(harness): structure parity contracts

* feat: derive unit test mapping from traces

* feat(harness): map rstest test families

* feat(ocr): port Azure document intelligence tests

* feat(harness): enforce complete unit mappings

* feat(ocr): add reducto core transforms

* feat(harness): classify host-only unit tests

* fix(ocr): complete Rust provider plumbing

* fix(harness): reuse OCR parity workers
2026-09-03 21:15:01 -07:00

271 lines
9.9 KiB
Python

from __future__ import annotations
import re
from collections import Counter
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Final, Literal
from .profiler import FunctionTraceEvent
Engine = Literal["python", "rust"]
@dataclass(frozen=True, slots=True)
class TraceMapping:
span: str
python: re.Pattern[str] | None
rust: str | None
def mapping(
*,
python_frame: str | None = None,
rust_span: str | None = None,
span: str | None = None,
) -> TraceMapping:
if rust_span is None:
if python_frame is None:
raise ValueError("mapping needs a python_frame pattern, a rust_span name, or both")
if span is None:
raise ValueError("a python-only mapping needs an explicit span to compare under")
return TraceMapping(span, re.compile(python_frame), None)
if python_frame is None:
return TraceMapping(rust_span, None, rust_span)
if span is not None and span != rust_span:
raise ValueError(f"span {span!r} disagrees with rust_span {rust_span!r}")
return TraceMapping(rust_span, re.compile(python_frame), rust_span)
@dataclass(frozen=True, slots=True)
class TraceContract:
unordered_children_of: frozenset[str] = frozenset()
@dataclass(frozen=True, slots=True)
class PipelineStep:
id: int
parent_id: int | None
span: str
raw: str
@dataclass(frozen=True, slots=True)
class PipelineProjection:
steps: tuple[PipelineStep, ...] = ()
unmatched: int = 0
def _span_for(engine: Engine, function: str, mappings: Sequence[TraceMapping]) -> str | None:
matches: Final = tuple(
item.span
for item in mappings
if (
engine == "python"
and item.python is not None
and item.python.search(function)
or engine == "rust"
and item.rust == function
)
)
if len(matches) > 1:
raise ValueError(f"{engine} event {function!r} matches multiple trace mappings: {matches}")
if matches:
return matches[0]
return function if engine == "rust" else None
def pipeline_projection(
engine: Engine, events: Sequence[FunctionTraceEvent], mappings: Sequence[TraceMapping]
) -> PipelineProjection:
raw_parents: dict[int, int | None] = {}
projected_ids: set[int] = set()
shown: list[PipelineStep] = []
unmatched: int = 0
for event in events:
if event.id in raw_parents:
raise ValueError(f"duplicate trace event id {event.id}")
if event.parent_id is not None and event.parent_id not in raw_parents:
raise ValueError(f"trace event {event.id} references unknown or later parent {event.parent_id}")
raw_parents[event.id] = event.parent_id
span = _span_for(engine, event.function, mappings)
if span is None:
unmatched += 1
continue
parent_id: int | None = event.parent_id
while parent_id is not None and parent_id not in projected_ids:
parent_id = raw_parents[parent_id]
shown.append(PipelineStep(event.id, parent_id, span, event.raw))
projected_ids.add(event.id)
return PipelineProjection(tuple(shown), unmatched)
@dataclass(frozen=True, slots=True)
class TraceNode:
id: int
span: str
children: tuple[TraceNode, ...]
def trace_depths(steps: Sequence[PipelineStep]) -> dict[int, int]:
depths: dict[int, int] = {}
for step in steps:
depths[step.id] = 0 if step.parent_id is None else depths[step.parent_id] + 1
return depths
def _forest(steps: Sequence[PipelineStep]) -> tuple[TraceNode, ...]:
children: dict[int | None, list[PipelineStep]] = {}
known: set[int] = set()
for step in steps:
if step.id in known:
raise ValueError(f"duplicate projected event id {step.id}")
if step.parent_id is not None and step.parent_id not in known:
raise ValueError(f"projected event {step.id} references unknown or later parent {step.parent_id}")
known.add(step.id)
children.setdefault(step.parent_id, []).append(step)
def node(step: PipelineStep) -> TraceNode:
return TraceNode(step.id, step.span, tuple(node(child) for child in children.get(step.id, ())))
return tuple(node(step) for step in children.get(None, ()))
def _exclusive_spans(engine: Engine, mappings: Sequence[TraceMapping]) -> frozenset[str]:
return frozenset(
item.span
for item in mappings
if (engine == "python" and item.rust is None) or (engine == "rust" and item.python is None)
)
def _comparable_steps(
engine: Engine, steps: Sequence[PipelineStep], mappings: Sequence[TraceMapping]
) -> tuple[PipelineStep, ...]:
exclusive: Final = _exclusive_spans(engine, mappings)
raw_parents: Final = {step.id: step.parent_id for step in steps}
included: Final = {step.id for step in steps if step.span not in exclusive}
comparable: list[PipelineStep] = []
for step in steps:
if step.id not in included:
continue
parent_id: int | None = step.parent_id
while parent_id is not None and parent_id not in included:
parent_id = raw_parents[parent_id]
comparable.append(PipelineStep(step.id, parent_id, step.span, step.raw))
return tuple(comparable)
def _signature(node: TraceNode, contract: TraceContract) -> tuple[object, ...]:
children: tuple[tuple[object, ...], ...] = tuple(_signature(child, contract) for child in node.children)
normalized: Final = tuple(sorted(children, key=repr)) if node.span in contract.unordered_children_of else children
return (node.span, normalized)
def trace_signature(
engine: Engine,
steps: Sequence[PipelineStep],
mappings: Sequence[TraceMapping],
contract: TraceContract,
) -> tuple[tuple[object, ...], ...]:
return tuple(_signature(root, contract) for root in _forest(_comparable_steps(engine, steps, mappings)))
@dataclass(frozen=True, slots=True)
class TraceDiff:
python_only: tuple[str, ...]
rust_only: tuple[str, ...]
shared_order_matches: bool
missing_mappings: tuple[str, ...] = ()
first_difference: str | None = None
@property
def matches(self) -> bool:
return (
not self.python_only
and not self.rust_only
and not self.missing_mappings
and self.shared_order_matches
)
def _missing_mappings(
python: Sequence[PipelineStep], rust: Sequence[PipelineStep], mappings: Sequence[TraceMapping]
) -> tuple[str, ...]:
python_seen: Final = frozenset(step.span for step in python)
rust_seen: Final = frozenset(step.span for step in rust)
return tuple(
item.span
for item in mappings
if (item.python is not None and item.span not in python_seen)
or (item.rust is not None and item.span not in rust_seen)
)
def _first_difference(
python: Sequence[PipelineStep],
rust: Sequence[PipelineStep],
mappings: Sequence[TraceMapping],
contract: TraceContract,
) -> str | None:
python_forest: Final = _forest(_comparable_steps("python", python, mappings))
rust_forest: Final = _forest(_comparable_steps("rust", rust, mappings))
def compare_children(
python_nodes: Sequence[TraceNode], rust_nodes: Sequence[TraceNode], path: str, *, unordered: bool
) -> str | None:
if unordered:
python_signatures: Final = Counter(_signature(node, contract) for node in python_nodes)
rust_signatures: Final = Counter(_signature(node, contract) for node in rust_nodes)
if python_signatures != rust_signatures:
return f"{path}: unordered child subtree multiset differs"
return None
for index in range(max(len(python_nodes), len(rust_nodes))):
child_path = f"{path}/child[{index + 1}]"
if index >= len(python_nodes):
return f"{child_path}: Rust has extra {rust_nodes[index].span!r}"
if index >= len(rust_nodes):
return f"{child_path}: Python has extra {python_nodes[index].span!r}"
python_node = python_nodes[index]
rust_node = rust_nodes[index]
if python_node.span != rust_node.span:
return f"{child_path}: Python={python_node.span!r}, Rust={rust_node.span!r}"
difference = compare_children(
python_node.children,
rust_node.children,
f"{child_path}/{python_node.span}",
unordered=python_node.span in contract.unordered_children_of,
)
if difference is not None:
return difference
return None
return compare_children(python_forest, rust_forest, "root", unordered=False)
def trace_diff(
python: Sequence[PipelineStep],
rust: Sequence[PipelineStep],
mappings: Sequence[TraceMapping] = (),
contract: TraceContract = TraceContract(),
) -> TraceDiff:
python_comparable: Final = _comparable_steps("python", python, mappings)
rust_comparable: Final = _comparable_steps("rust", rust, mappings)
python_spans: Final = tuple(step.span for step in python_comparable)
rust_spans: Final = tuple(step.span for step in rust_comparable)
python_counts: Final = Counter(python_spans)
rust_counts: Final = Counter(rust_spans)
python_only_counts: Final = python_counts - rust_counts
rust_only_counts: Final = rust_counts - python_counts
python_only: Final = tuple(
span for span, count in python_only_counts.items() for _ in range(count)
)
rust_only: Final = tuple(span for span, count in rust_only_counts.items() for _ in range(count))
first_difference: Final = _first_difference(python, rust, mappings, contract)
return TraceDiff(
python_only=python_only,
rust_only=rust_only,
shared_order_matches=bool(python_comparable or rust_comparable) and first_difference is None,
missing_mappings=_missing_mappings(python, rust, mappings),
first_difference=first_difference,
)