Files
litellm/tests/batches_tests/conftest.py
T
Mateo Wang 6a9f542f81 test: stabilize batch VCR coverage and stop live upload/network leaks (#29477)
* test: stabilize batch VCR coverage

* test: replay bedrock batch s3 uploads

* test: stop batch tests leaking live uploads

* test: keep bedrock batch workflow off live s3

* test: mock bedrock batch workflow network

* test: accept realtime guardrail refusal wording

* test: update gemini thought signature model

* test: quiet logging worker atexit flush

* test: address Greptile review on batch VCR fixes

Handle content= bodies in the bedrock batch post stub so payload
extraction does not raise a TypeError when a request omits json and
data. Restore litellm list state faithfully by preserving None instead
of coercing it to an empty list, so callbacks that start as None are not
turned into [] after a test. Set logging.raiseExceptions inside the try
block in the atexit flush so the finally always restores the previous
value.

* test: scope atexit logging suppression to the drain loop

Wrap only the queue drain loop in LoggingWorker._flush_on_exit with the
logging.raiseExceptions toggle so the process-wide global is suppressed for
the smallest possible window, keeping other threads' logging error reporting
intact outside the loop.

* test: cover atexit flush error-swallow branch in LoggingWorker

The _flush_on_exit drain loop was wrapped in a try/finally to scope the
logging.raiseExceptions toggle, which reindented the existing edge-case
branches into the diff and dropped patch coverage below target. Add a
regression test that enqueues a coroutine which raises during the atexit
flush and asserts the failure is swallowed while later queued events are
still drained, exercising the silent-failure path directly.
2026-06-02 16:11:52 -07:00

158 lines
3.9 KiB
Python

import asyncio
import os
import sys
import pytest
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()
_CALLBACK_ATTRS = (
"callbacks",
"success_callback",
"failure_callback",
"_async_success_callback",
"_async_failure_callback",
)
_SCALAR_ATTRS = (
"num_retries",
"set_verbose",
"cache",
"allowed_fails",
"disable_aiohttp_transport",
"force_ipv4",
"drop_params",
"modify_params",
"api_base",
"api_key",
"cohere_key",
)
@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_terminal_summary(terminalreporter, exitstatus, config):
emit_cassette_cache_session_banner(terminalreporter)
emit_vcr_classification_summary(terminalreporter)
emit_vcr_diagnostic_log(terminalreporter)
@pytest.fixture(scope="session")
def event_loop():
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
yield loop
loop.close()
def _copy_litellm_state():
state = {}
for attr in _CALLBACK_ATTRS:
if hasattr(litellm, attr):
value = getattr(litellm, attr)
state[attr] = value.copy() if isinstance(value, list) else value
for attr in _SCALAR_ATTRS:
if hasattr(litellm, attr):
state[attr] = getattr(litellm, attr)
return state
def _restore_litellm_state(state) -> None:
for attr, value in state.items():
if hasattr(litellm, attr):
setattr(litellm, attr, value)
def _reset_litellm_callbacks() -> None:
for attr in _CALLBACK_ATTRS:
if hasattr(litellm, attr):
setattr(litellm, attr, [])
manager = getattr(litellm, "logging_callback_manager", None)
reset = getattr(manager, "_reset_all_callbacks", None)
if callable(reset):
reset()
def _clear_logging_queue(loop=None) -> None:
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
if loop is not None and not loop.is_closed() and not loop.is_running():
loop.run_until_complete(GLOBAL_LOGGING_WORKER.clear_queue())
return
asyncio.run(GLOBAL_LOGGING_WORKER.clear_queue())
@pytest.fixture(scope="function", autouse=True)
def setup_and_teardown(event_loop):
original_state = _copy_litellm_state()
_clear_logging_queue(event_loop)
_reset_litellm_callbacks()
asyncio.set_event_loop(event_loop)
yield
_clear_logging_queue(event_loop)
_reset_litellm_callbacks()
_restore_litellm_state(original_state)
pending = asyncio.all_tasks(event_loop)
for task in pending:
task.cancel()
if pending:
event_loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
def pytest_collection_modifyitems(config, items):
apply_vcr_auto_marker_to_items(items)