mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
* refactor(python-bridge): split non-streaming bridge modules * refactor(python-bridge): bring shared function tracing into route layer * feat(dev): list Python route functions and call sites * feat(dev): list Rust route functions and call sites * docs(dev): record OCR parity gaps across Python and Rust * feat(dev): list executed SDK calls with runtime tracing * feat(dev): report Python vs Rust SDK pipeline steps in one CLI * feat(dev): side-by-side pipeline step report in compare CLI * fix(dev): drop invalid Final annotations in compare cell loop * feat(dev): blue python-only and yellow rust-only steps in compare CLI * feat(dev): vertical layout with section spacing in compare CLI * fix(dev): validate SDK trace stages across sync and async routes * refactor(rust): align SDK route call structure with Python * refactor(python-bridge): share sync and async route call wrappers * refactor(dev): split compare CLI into fixtures, runtime, and report modules * fix(ci): run SDK trace tests and satisfy test lint
167 lines
4.9 KiB
Python
167 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
from types import FunctionType
|
|
from typing import Final, cast
|
|
|
|
import pytest
|
|
|
|
from tests.sdk_function_trace import (
|
|
FunctionTraceEvent,
|
|
TraceScenario,
|
|
TraceStep,
|
|
assert_function_trace_parity,
|
|
)
|
|
from tests.sdk_function_trace.profiler import profile_python
|
|
|
|
|
|
class First:
|
|
@staticmethod
|
|
def run() -> None:
|
|
return None
|
|
|
|
|
|
class Second:
|
|
@staticmethod
|
|
def run() -> None:
|
|
return None
|
|
|
|
|
|
def test_profiler_matches_code_objects_and_keeps_repeated_calls() -> None:
|
|
with profile_python((First.run,)) as profiler:
|
|
Second.run()
|
|
First.run()
|
|
First.run()
|
|
|
|
assert profiler.events == [
|
|
FunctionTraceEvent(function="run", depth=0),
|
|
FunctionTraceEvent(function="run", depth=0),
|
|
]
|
|
|
|
|
|
def test_profiler_records_selected_function_nesting_depth() -> None:
|
|
class Nested:
|
|
@staticmethod
|
|
def run() -> None:
|
|
First.run()
|
|
|
|
with profile_python((Nested.run, First.run)) as profiler:
|
|
Nested.run()
|
|
|
|
assert profiler.events == [
|
|
FunctionTraceEvent(function="run", depth=0),
|
|
FunctionTraceEvent(function="run", depth=1),
|
|
]
|
|
|
|
|
|
def test_profiler_restores_previous_profiler_after_failure() -> None:
|
|
previous: Final = sys.getprofile()
|
|
|
|
with profile_python((First.run,)) as outer:
|
|
with pytest.raises(RuntimeError, match="stop"):
|
|
with profile_python((Second.run,)):
|
|
raise RuntimeError("stop")
|
|
assert sys.getprofile() is outer
|
|
First.run()
|
|
|
|
assert sys.getprofile() is previous
|
|
assert outer.events == [FunctionTraceEvent(function="run", depth=0)]
|
|
|
|
|
|
def test_profiler_does_not_count_coroutine_resumption_as_another_call() -> None:
|
|
async def suspended() -> None:
|
|
await asyncio.sleep(0)
|
|
First.run()
|
|
await asyncio.sleep(0)
|
|
|
|
with profile_python((suspended, First.run)) as profiler:
|
|
asyncio.run(suspended())
|
|
|
|
assert profiler.events == [
|
|
FunctionTraceEvent(function="suspended", depth=0),
|
|
FunctionTraceEvent(function="run", depth=1),
|
|
]
|
|
|
|
|
|
def test_source_profiler_records_real_frame_ancestry() -> None:
|
|
def outer() -> None:
|
|
First.run()
|
|
|
|
with profile_python(source_root=Path(__file__).parent) as profiler:
|
|
outer()
|
|
Second.run()
|
|
|
|
outer_event, first_event, second_event = (
|
|
event for event in profiler.events if event.function.startswith("test_profiler.py:")
|
|
)
|
|
assert first_event.ancestors is not None
|
|
assert outer_event.function in first_event.ancestors
|
|
assert second_event.ancestors is not None
|
|
assert outer_event.function not in second_event.ancestors
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"rust_trace",
|
|
[
|
|
(),
|
|
(FunctionTraceEvent(function="renamed", depth=0),),
|
|
(FunctionTraceEvent(function="run", depth=1),),
|
|
(FunctionTraceEvent(function="run", depth=0),) * 2,
|
|
],
|
|
ids=["missing", "renamed", "wrong-depth", "extra-call"],
|
|
)
|
|
def test_harness_rejects_rust_function_trace_drift(rust_trace: tuple[FunctionTraceEvent, ...]) -> None:
|
|
with pytest.raises(AssertionError, match="Rust function trace differs"):
|
|
assert_function_trace_parity(
|
|
TraceScenario(
|
|
steps=(TraceStep(cast(FunctionType, First.run), depth=0),),
|
|
invoke_python=First.run,
|
|
invoke_rust=lambda: rust_trace,
|
|
)
|
|
)
|
|
|
|
|
|
def test_harness_rejects_python_function_trace_drift() -> None:
|
|
with pytest.raises(AssertionError, match="Python function trace differs"):
|
|
assert_function_trace_parity(
|
|
TraceScenario(
|
|
steps=(TraceStep(cast(FunctionType, First.run), depth=0),),
|
|
invoke_python=Second.run,
|
|
invoke_rust=lambda: (FunctionTraceEvent(function="run", depth=0),),
|
|
)
|
|
)
|
|
|
|
|
|
def test_harness_accepts_matching_traces() -> None:
|
|
assert_function_trace_parity(
|
|
TraceScenario(
|
|
steps=(TraceStep(cast(FunctionType, First.run), depth=0),),
|
|
invoke_python=First.run,
|
|
invoke_rust=lambda: (FunctionTraceEvent(function="run", depth=0),),
|
|
)
|
|
)
|
|
|
|
|
|
def test_harness_rejects_reordered_calls() -> None:
|
|
def begin() -> None:
|
|
return None
|
|
|
|
def finish() -> None:
|
|
return None
|
|
|
|
with pytest.raises(AssertionError, match="Rust function trace differs"):
|
|
assert_function_trace_parity(
|
|
TraceScenario(
|
|
steps=(
|
|
TraceStep(cast(FunctionType, begin), depth=0),
|
|
TraceStep(cast(FunctionType, finish), depth=0),
|
|
),
|
|
invoke_python=lambda: (begin(), finish()),
|
|
invoke_rust=lambda: (
|
|
FunctionTraceEvent(function="finish", depth=0),
|
|
FunctionTraceEvent(function="begin", depth=0),
|
|
),
|
|
)
|
|
)
|