mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-16 23:41:43 +00:00
135 lines
5.3 KiB
Python
135 lines
5.3 KiB
Python
"""Fixture-mode selection and per-test determinism for record/replay e2e runs.
|
|
|
|
``E2E_FIXTURE_MODE`` is live (the default; nothing changes), record, or replay.
|
|
This module owns everything mode-shaped that is independent of the provider
|
|
edge itself: parsing the raw env value, the collection-time gate that aborts a
|
|
run whose mode can never work (unknown value, or replay against a missing or
|
|
stale bundle), the pytest report-header lines, the running test's node id, and
|
|
the deterministic per-test marker that lets a replay run regenerate exactly
|
|
the requests the record run sent. The provider-edge server that records and
|
|
serves provider traffic lives in provider_edge.py (LIT-5745).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import os
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Final, Literal, assert_never
|
|
|
|
from fixture_bundle import (
|
|
FreshBundle,
|
|
StaleBundle,
|
|
UnreadableBundle,
|
|
check_freshness,
|
|
format_age,
|
|
)
|
|
from fixture_profile import match_profile
|
|
|
|
type FixtureMode = Literal["live", "record", "replay"]
|
|
|
|
FIXTURE_MODES: Final[tuple[FixtureMode, ...]] = ("live", "record", "replay")
|
|
|
|
SESSION_TEST_KEY: Final = "session"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class InvalidFixtureMode:
|
|
value: str
|
|
|
|
|
|
def parse_fixture_mode(raw: str) -> FixtureMode | InvalidFixtureMode:
|
|
normalized = raw.strip().lower() or "live"
|
|
match normalized:
|
|
case "live" | "record" | "replay":
|
|
return normalized
|
|
case _:
|
|
return InvalidFixtureMode(value=raw)
|
|
|
|
|
|
def current_test_key() -> str:
|
|
"""The pytest node id of the running test, from the PYTEST_CURRENT_TEST env
|
|
var pytest maintains (``<nodeid> (setup|call|teardown)``); ``session`` for
|
|
calls outside any test (e.g. session-finish cleanup)."""
|
|
raw = os.environ.get("PYTEST_CURRENT_TEST", "")
|
|
if not raw:
|
|
return SESSION_TEST_KEY
|
|
return raw.rsplit(" (", 1)[0]
|
|
|
|
|
|
class ReplayMiss(AssertionError):
|
|
"""Replay had no recorded interaction for a provider call the proxy made.
|
|
The suite drifted from the bundle (or the bundle from the suite): re-record."""
|
|
|
|
|
|
_marker_ordinals: Final[dict[str, int]] = {}
|
|
|
|
|
|
def deterministic_marker() -> str:
|
|
"""Stable stand-in for uuid-based unique markers in record and replay modes:
|
|
the Nth marker of a test is a pure function of the test's node id and N, so a
|
|
replay run regenerates exactly the model names, prompts, and tags the record
|
|
run sent and every recorded provider interaction still matches its key."""
|
|
test_key = current_test_key()
|
|
ordinal = _marker_ordinals.get(test_key, 0)
|
|
_marker_ordinals[test_key] = ordinal + 1
|
|
return hashlib.sha1(f"{test_key}#{ordinal}".encode()).hexdigest()[:12]
|
|
|
|
|
|
def fixture_mode_collection_error(mode_raw: str, bundle_dir: Path, *, now: datetime) -> str | None:
|
|
"""Session-abort reason for a fixture-mode setup that can never work, or None.
|
|
Called at collection time (conftest pytest_sessionstart) so a stale or missing
|
|
bundle fails the whole run up front, naming the bundle age, instead of failing
|
|
every test individually."""
|
|
match_profile()
|
|
mode = parse_fixture_mode(mode_raw)
|
|
match mode:
|
|
case InvalidFixtureMode(value=value):
|
|
return f"E2E_FIXTURE_MODE={value!r} is not one of {', '.join(FIXTURE_MODES)}"
|
|
case "live" | "record":
|
|
return None
|
|
case "replay":
|
|
freshness = check_freshness(bundle_dir, now=now, profile=match_profile())
|
|
match freshness:
|
|
case FreshBundle():
|
|
return None
|
|
case StaleBundle(recorded_at=recorded_at, age=age, limit=limit):
|
|
return (
|
|
f"fixture bundle at {bundle_dir} is stale: recorded {recorded_at.isoformat()}, "
|
|
f"age {format_age(age)} exceeds the {limit.days}-day limit; "
|
|
"re-record with E2E_FIXTURE_MODE=record"
|
|
)
|
|
case UnreadableBundle(reason=reason):
|
|
return f"E2E_FIXTURE_MODE=replay cannot use bundle at {bundle_dir}: {reason}"
|
|
case _:
|
|
assert_never(freshness)
|
|
case _:
|
|
assert_never(mode)
|
|
|
|
|
|
def fixture_report_lines(mode_raw: str, bundle_dir: Path, *, now: datetime) -> list[str]:
|
|
"""pytest report-header lines; empty in live mode so an unset
|
|
E2E_FIXTURE_MODE keeps today's output byte-identical."""
|
|
match_profile()
|
|
mode = parse_fixture_mode(mode_raw)
|
|
match mode:
|
|
case InvalidFixtureMode() | "live":
|
|
return []
|
|
case "record":
|
|
return [f"e2e fixture mode: record -> {bundle_dir}"]
|
|
case "replay":
|
|
freshness = check_freshness(bundle_dir, now=now, profile=match_profile())
|
|
match freshness:
|
|
case FreshBundle(manifest=manifest):
|
|
return [
|
|
f"e2e fixture mode: replay <- {bundle_dir} "
|
|
f"(recorded {manifest.recorded_at.isoformat()}, harness {manifest.harness_version})"
|
|
]
|
|
case StaleBundle() | UnreadableBundle():
|
|
return [f"e2e fixture mode: replay <- {bundle_dir}"]
|
|
case _:
|
|
assert_never(freshness)
|
|
case _:
|
|
assert_never(mode)
|