mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-27 01:22:18 +00:00
A test's JUnit report says whether it passed, never what it did or where a failing test died. This records that from the harness, so nothing about it is hand-written and it cannot drift from what the test actually ran
`@step("create team with a budget")` from the new tests/e2e/e2e_metadata.py goes on harness helpers, never on tests, and appends its label to the running test's step log in call order. The label is recorded before the wrapped call, so a helper that raises still leaves its own label last: a failing test's last step is where it died. Every public harness method that performs an action now carries one, 355 across the client modules, lifecycle, idp, the logging readers, migrations and the claude_code driver
Only the outermost step records, tracked per thread. Harness layers call each other (ResourceManager.key goes through ProxyClient.generate_key, a domain client wraps the shared ProxyClient), so every layer carries a label and the story still reads at the level the test called in at, one beat per action. A step above @contextmanager holds the guard through __enter__ and __exit__, so a context's cleanup never lands behind the step a test died on, and a bare generator function is refused at import because its body interleaves with its caller's. Consecutive duplicates collapse and the log caps at 50, so a poll loop is one beat rather than fifty. The wrapper is a frame, so the eight cleanup and retry warnings raised directly inside decorated helpers use stacklevel=2 + STEP_FRAMES to keep reporting at their caller
The log is emptied first thing in pytest_runtest_setup and attached from the existing pytest_runtest_makereport wrapper after setup and again after call, so a test that errors in a fixture keeps the steps recorded before the crash. Teardown does not attach: finalizer steps are cleanup. Each attach drops the item's earlier step entries, so the second attach and a --reruns 1 retry replace the story rather than doubling it
Steps ride out as repeated <property name="step"> entries behind the fixed package/covers/source prefix, which stays byte-identical. The project-releaser emitter already regroups them into the results JSON's steps array. test_junit_report.py runs real pytest with --junitxml against this conftest, in-process and under -n 2, and pins the passing, failing, setup-error, rerun and wide-scope-fixture cases on the parsed XML
179 lines
7.3 KiB
Python
179 lines
7.3 KiB
Python
"""Shared body for the `passthrough` × <provider> compat cells.
|
||
|
||
Every other matrix row drives the proxy's `/v1/messages` translation
|
||
layer: Claude Code speaks the first-party Anthropic wire and LiteLLM
|
||
transforms the request per provider. This row instead exercises
|
||
LiteLLM's *native passthrough* routes -- the "LLM gateway"
|
||
configuration documented at https://code.claude.com/docs/en/gateway --
|
||
where Claude Code speaks each cloud's own wire format and the proxy
|
||
forwards it, attaching provider credentials on the way out:
|
||
|
||
anthropic ANTHROPIC_BASE_URL={proxy}/anthropic. The CLI's
|
||
first-party wire, forwarded verbatim to
|
||
api.anthropic.com, so the model ids are real
|
||
Anthropic ids rather than proxy aliases.
|
||
bedrock_invoke CLAUDE_CODE_USE_BEDROCK=1 +
|
||
ANTHROPIC_BEDROCK_BASE_URL={proxy}/bedrock. The
|
||
CLI POSTs /model/{model}/invoke-with-response-stream;
|
||
the proxy recognizes a router alias in the model
|
||
segment, rewrites it to the deployment's upstream
|
||
model id, and SigV4-signs with its own AWS creds.
|
||
vertex_ai CLAUDE_CODE_USE_VERTEX=1 +
|
||
ANTHROPIC_VERTEX_BASE_URL={proxy}/vertex_ai/v1.
|
||
The CLI POSTs
|
||
.../models/{model}:streamRawPredict; the proxy
|
||
resolves a router alias in the model segment and
|
||
takes project, location, and credentials from the
|
||
deployment (which is why the deployment must set
|
||
`use_in_pass_through: true` -- see
|
||
test_config.yaml).
|
||
azure CLAUDE_CODE_USE_FOUNDRY=1 +
|
||
ANTHROPIC_FOUNDRY_BASE_URL={proxy}/azure. Foundry
|
||
mode sends the model in the JSON body, not the
|
||
URL, so the proxy's /azure route cannot resolve a
|
||
router alias and falls back to the env-configured
|
||
AZURE_API_BASE / AZURE_API_KEY target.
|
||
bedrock_converse not applicable -- Claude Code's bedrock mode is
|
||
InvokeModel-only; no Converse-wire client exists.
|
||
|
||
Auth is the same in every mode: the CLI's provider-native signing is
|
||
disabled via CLAUDE_CODE_SKIP_<PROVIDER>_AUTH, and the LiteLLM virtual
|
||
key travels as `Authorization: Bearer` (ANTHROPIC_AUTH_TOKEN), exactly
|
||
like the translation rows. The proxy holds the real provider
|
||
credentials.
|
||
|
||
The per-mode env vars and URL shapes above were captured from a real
|
||
`claude` CLI (2.1.210) run against a request-logging sink, not from
|
||
docs; if a CLI release changes them, the cells fail with the CLI's own
|
||
diagnostic rather than silently testing the wrong wire.
|
||
|
||
`run_models` and `env` are injection seams for
|
||
`_driver_unit_tests/test_passthrough.py`; production callers leave
|
||
them unset.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Callable, Dict, Mapping, Optional, Sequence
|
||
|
||
import pytest
|
||
|
||
from e2e_metadata import step
|
||
|
||
from claude_code._env import require_proxy
|
||
from claude_code.cli_driver import (
|
||
ClaudeCLIError,
|
||
failure_diagnostic,
|
||
run_claude_models_parallel,
|
||
)
|
||
|
||
ANTHROPIC_PASSTHROUGH_BASE_PATH = "/anthropic"
|
||
|
||
CLIENT_SIDE_AWS_REGION = "us-east-1"
|
||
"""Satisfies the CLI's embedded AWS SDK, which refuses to construct a
|
||
client without a region. The value never influences routing: the proxy
|
||
signs the upstream request with its own credentials and region."""
|
||
|
||
VERTEX_PLACEHOLDER_PROJECT = "proxy-resolved-project"
|
||
VERTEX_PLACEHOLDER_REGION = "us-east5"
|
||
"""The CLI refuses to build a Vertex URL without a project id and
|
||
region, but the proxy replaces both path segments with the resolved
|
||
deployment's `vertex_project` / `vertex_location` before forwarding,
|
||
so deliberately-fake values prove the resolution actually happened."""
|
||
|
||
|
||
def bedrock_extra_env(proxy_base_url: str) -> Dict[str, str]:
|
||
return {
|
||
"CLAUDE_CODE_USE_BEDROCK": "1",
|
||
"CLAUDE_CODE_SKIP_BEDROCK_AUTH": "1",
|
||
"ANTHROPIC_BEDROCK_BASE_URL": f"{proxy_base_url}/bedrock",
|
||
"AWS_REGION": CLIENT_SIDE_AWS_REGION,
|
||
}
|
||
|
||
|
||
def vertex_extra_env(proxy_base_url: str) -> Dict[str, str]:
|
||
"""Vertex-mode CLI env pointed at the proxy's /vertex_ai route.
|
||
|
||
The `/v1` suffix on ANTHROPIC_VERTEX_BASE_URL is load-bearing: the
|
||
CLI's Vertex SDK ships its API version inside its *default* base
|
||
URL (`https://{region}-aiplatform.googleapis.com/v1`), so
|
||
overriding the base drops the version from the request path unless
|
||
the override carries it. LiteLLM's /vertex_ai route reuses the
|
||
incoming path verbatim when it contains `/projects/.../locations/...`,
|
||
so a version-less path would reach Google as
|
||
`aiplatform.googleapis.com/projects/...` and 404.
|
||
"""
|
||
return {
|
||
"CLAUDE_CODE_USE_VERTEX": "1",
|
||
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
|
||
"ANTHROPIC_VERTEX_BASE_URL": f"{proxy_base_url}/vertex_ai/v1",
|
||
"ANTHROPIC_VERTEX_PROJECT_ID": VERTEX_PLACEHOLDER_PROJECT,
|
||
"CLOUD_ML_REGION": VERTEX_PLACEHOLDER_REGION,
|
||
}
|
||
|
||
|
||
def foundry_extra_env(proxy_base_url: str) -> Dict[str, str]:
|
||
return {
|
||
"CLAUDE_CODE_USE_FOUNDRY": "1",
|
||
"CLAUDE_CODE_SKIP_FOUNDRY_AUTH": "1",
|
||
"ANTHROPIC_FOUNDRY_BASE_URL": f"{proxy_base_url}/azure",
|
||
}
|
||
|
||
|
||
@step("run the claude CLI via a passthrough route")
|
||
def run_passthrough_cell(
|
||
*,
|
||
compat_result,
|
||
models: Sequence[str],
|
||
prompt: str,
|
||
passthrough_base_path: str = "",
|
||
build_extra_env: Optional[Callable[[str], Mapping[str, str]]] = None,
|
||
run_models: Callable[..., Mapping[str, Any]] = run_claude_models_parallel,
|
||
env: Optional[Mapping[str, str]] = None,
|
||
) -> None:
|
||
"""Run the shared `passthrough` × <provider> cell body.
|
||
|
||
`passthrough_base_path` is appended to the proxy base URL and
|
||
becomes the CLI's ANTHROPIC_BASE_URL (only the anthropic column
|
||
uses it; the cloud columns ignore ANTHROPIC_BASE_URL entirely once
|
||
their CLAUDE_CODE_USE_* flag is set). `build_extra_env` receives
|
||
the trailing-slash-normalized proxy base URL and returns the
|
||
provider-mode env for the CLI subprocess.
|
||
"""
|
||
proxy = require_proxy(compat_result, env=env)
|
||
proxy_base = proxy.base_url.rstrip("/")
|
||
extra_env = dict(build_extra_env(proxy_base)) if build_extra_env else None
|
||
|
||
outcomes = run_models(
|
||
models=models,
|
||
prompt=prompt,
|
||
base_url=proxy_base + passthrough_base_path,
|
||
api_key=proxy.api_key,
|
||
extra_env=extra_env,
|
||
)
|
||
|
||
failures = []
|
||
for model in models:
|
||
outcome = outcomes[model]
|
||
if isinstance(outcome, ClaudeCLIError):
|
||
error = f"[{model}] {outcome}"
|
||
compat_result.add({"status": "fail", "error": error})
|
||
failures.append(error)
|
||
continue
|
||
|
||
if outcome.exit_code != 0:
|
||
error = f"[{model}] claude CLI failed: {failure_diagnostic(outcome)}"
|
||
compat_result.add({"status": "fail", "error": error})
|
||
failures.append(error)
|
||
continue
|
||
|
||
if not outcome.text.strip():
|
||
error = f"[{model}] claude returned empty assistant text"
|
||
compat_result.add({"status": "fail", "error": error})
|
||
failures.append(error)
|
||
continue
|
||
|
||
compat_result.add({"status": "pass"})
|
||
|
||
if failures:
|
||
pytest.fail("; ".join(failures), pytrace=False)
|