litellm/tests/unified_google_tests/conftest.py
ryan-crabbe-berri e9d40a8f73 test: enforce F811 so a duplicate definition cannot silently replace the first
A name bound twice keeps only the second binding. In `tests/` that is nearly
always a repeated import, harmless but misleading, and the same rule is what
catches the cases that are not harmless: a local that shadows an import the
module still calls, and a second `def test_x` that quietly replaces the first.

311 of the 344 sites were repeated imports and came out with ruff's own fix.
The remaining 33 needed a decision. Four modules imported a name they never
used because a local definition below already shadowed it. Two comprehensions
bound `call` over `unittest.mock.call`, which those modules import and use.
One test rebound the two module handles its nested reload closure had captured.
One class attribute shadowed an unused `status` import.

The load-test fixtures move to a conftest, which is how pytest is meant to share
them, so the test module no longer imports three fixture names it never calls.
The nine `prisma_client` parameters keep a narrow `noqa`: pytest resolves that
fixture by name before the body runs, so the parameter never shadows anything.
2026-08-21 12:06:19 -07:00

226 lines
6.5 KiB
Python

# conftest.py
import asyncio
import importlib
import os
import socket
import sys
import threading
import time
from pathlib import Path
from typing import Iterator, Tuple
import pytest
import uvicorn
from dotenv import load_dotenv
load_dotenv()
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
import litellm # noqa: E402,F401
from tests._vcr_conftest_common import ( # noqa: E402,F401
VerboseReporterState,
_pin_multipart_boundary,
apply_vcr_auto_marker_to_items,
emit_cassette_cache_session_banner,
emit_vcr_classification_summary,
emit_vcr_diagnostic_log,
install_live_call_probe,
record_vcr_outcome,
register_persister_if_enabled,
reset_vcr_diag_dir,
vcr_config_dict,
)
_verbose_state = VerboseReporterState()
PROXY_CONFIG_PATH = Path(__file__).parent / "google_genai_proxy_test_config.yaml"
PROXY_MASTER_KEY = "sk-1234"
PROXY_START_TIMEOUT_S = 30.0
def _start_proxy_server(
config_path: str,
) -> Tuple[str, uvicorn.Server, threading.Thread, socket.socket]:
from litellm.proxy.proxy_server import (
app as proxy_app,
cleanup_router_config_variables,
initialize,
)
cleanup_router_config_variables()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 0))
host, port = sock.getsockname()
config = uvicorn.Config(proxy_app, host=host, port=port, log_level="warning")
server = uvicorn.Server(config)
def _run() -> None:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(initialize(config=config_path, debug=True))
loop.run_until_complete(server.serve(sockets=[sock]))
thread = threading.Thread(target=_run, daemon=True)
thread.start()
start_time = time.time()
while not server.started:
if not thread.is_alive():
raise RuntimeError("LiteLLM proxy failed to start")
if time.time() - start_time > PROXY_START_TIMEOUT_S:
raise TimeoutError("LiteLLM proxy did not start in time")
time.sleep(0.05)
return f"http://{host}:{port}", server, thread, sock
@pytest.fixture(scope="session")
def google_genai_proxy_url() -> Iterator[str]:
from base_google_genai_proxy_sdk_test import has_vertex_credentials
from base_google_test import load_vertex_ai_credentials
saved_env = {
key: os.environ.get(key)
for key in (
"DATABASE_URL",
"DIRECT_URL",
"LITELLM_MASTER_KEY",
"STORE_MODEL_IN_DB",
"GOOGLE_APPLICATION_CREDENTIALS",
)
}
temp_credentials_path: str | None = None
os.environ.pop("DATABASE_URL", None)
os.environ.pop("DIRECT_URL", None)
os.environ["LITELLM_MASTER_KEY"] = PROXY_MASTER_KEY
os.environ["STORE_MODEL_IN_DB"] = "False"
if has_vertex_credentials():
credentials_file = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "")
if not (credentials_file and os.path.isfile(credentials_file)):
vertex_credentials_path = load_vertex_ai_credentials(
model="vertex_ai/gemini-2.5-flash-lite"
)
if vertex_credentials_path:
temp_credentials_path = vertex_credentials_path
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = vertex_credentials_path
server_url, server, thread, sock = _start_proxy_server(str(PROXY_CONFIG_PATH))
try:
yield server_url
finally:
server.should_exit = True
thread.join(timeout=10)
sock.close()
if temp_credentials_path:
try:
os.unlink(temp_credentials_path)
except OSError:
pass
for key, value in saved_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
@pytest.fixture(scope="session")
def event_loop():
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="function", autouse=True)
def setup_and_teardown(request):
"""
This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained.
"""
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the project directory to the system path
if "google_genai_proxy_url" not in request.fixturenames:
importlib.reload(litellm)
loop = asyncio.get_event_loop_policy().new_event_loop()
asyncio.set_event_loop(loop)
print(litellm)
yield
# Teardown code (executes after the yield point)
loop.close() # Close the loop created earlier
asyncio.set_event_loop(None) # Remove the reference to the loop
@pytest.fixture(scope="module")
def vcr_config():
return vcr_config_dict()
def pytest_recording_configure(config, vcr):
register_persister_if_enabled(vcr)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
setattr(item, f"rep_{rep.when}", rep)
@pytest.fixture(autouse=True)
def _vcr_outcome_gate(request, vcr):
install_live_call_probe(request, vcr)
yield
record_vcr_outcome(request, vcr)
def pytest_configure(config):
_verbose_state.remember_pluginmanager(config)
reset_vcr_diag_dir()
def pytest_runtest_logreport(report):
_verbose_state.maybe_emit_verdict(report)
def pytest_collection_modifyitems(config, items):
apply_vcr_auto_marker_to_items(
items,
skip_nodeid_suffixes=(
"test_proxy_genai_sdk_non_streaming",
"test_proxy_genai_sdk_streaming_completes_without_errors",
"test_proxy_genai_sdk_streaming_dict_style",
),
)
# Separate tests in 'test_amazing_proxy_custom_logger.py' and other tests
custom_logger_tests = [
item for item in items if "custom_logger" in item.parent.name
]
other_tests = [item for item in items if "custom_logger" not in item.parent.name]
# Sort tests based on their names
custom_logger_tests.sort(key=lambda x: x.name)
other_tests.sort(key=lambda x: x.name)
# Reorder the items list
items[:] = custom_logger_tests + other_tests
def pytest_terminal_summary(terminalreporter, exitstatus, config):
emit_cassette_cache_session_banner(terminalreporter)
emit_vcr_classification_summary(terminalreporter)
emit_vcr_diagnostic_log(terminalreporter)