mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
* perf(ci): distribute unit cases and reuse security sweep connections * revert(ci): reject first speed trial after new failures * perf(ci): avoid repeated test discovery and parsing * perf(ci): isolate parallel security workers * revert(ci): restore measured security execution isolation * perf(tests): reuse source scans and deduplicate cache cleanup * perf(ci): restore parallel security with safe log batching * test(ci): isolate fixture state and schema comparisons * test(ci): make schema and scheduler fixtures deterministic * test(ci): drain queued logging before fixture teardown * test(ci): preserve logging delivery without idle worker restarts * test(ci): keep guardrail sinks available during parallel bursts * test(ci): isolate full proxy lifecycle from gateway imports * test(ci): preserve first failure responses and sweep evidence * test(ci): restore documentation URL settings between cases * test(ci): isolate open MCP tools in private gateways * test(ci): group private MCP cases and handle exited workers * test(ci): retain proxy exits and MCP failure details * test(ci): repair merge interactions with main Key the schema shard's full clone on artifact-name, since main removed the unit-flag input. Give each owned Redis its own log file again. Hand the realtime transcription tests main added a logging object whose dispatch handlers are awaitable, so the per-test logging drain no longer waits on a MagicMock task forever * perf(ci): shard owned integration cases and close lifecycle leaks * fix(ci): qualify shard execution and harden interrupted cleanup * fix(ci): bound worker supervision and own fault traffic * perf(ci): reuse unparametrized fixtures across bounded case groups * fix(ci): preserve boot fault allowances and complete exit records * test(ci): tolerate startup retries and retain readiness details * perf(ci): balance integration shards by recorded duration * fix(ci): load migration database helpers only for workers
169 lines
6.5 KiB
Python
169 lines
6.5 KiB
Python
import asyncio
|
|
import sys
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass
|
|
from threading import Thread
|
|
from typing import Final, Literal
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm import Router
|
|
from litellm import router as litellm_router_module
|
|
from litellm import utils as litellm_utils_module
|
|
from litellm.caching.caching import DualCache
|
|
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
|
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
|
|
from tests.unit import conftest as unit_harness
|
|
|
|
CANARY_MODEL = "conftest-isolation-canary-model"
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class _LoggingOwner:
|
|
current: str = "original-test"
|
|
delivered: tuple[str, ...] = ()
|
|
|
|
async def deliver(self) -> None:
|
|
self.delivered = (*self.delivered, self.current)
|
|
|
|
|
|
@pytest.mark.parametrize("loop_state", ("stopped", "running", "closed"))
|
|
def test_queued_logging_finishes_under_its_original_owner(loop_state: Literal["stopped", "running", "closed"]) -> None:
|
|
owner: Final = _LoggingOwner()
|
|
loop: Final = asyncio.new_event_loop()
|
|
|
|
async def enqueue() -> None:
|
|
GLOBAL_LOGGING_WORKER._ensure_queue()
|
|
GLOBAL_LOGGING_WORKER.enqueue(owner.deliver())
|
|
|
|
thread: Final = Thread(target=loop.run_forever) if loop_state == "running" else None
|
|
if thread is not None:
|
|
thread.start()
|
|
asyncio.run_coroutine_threadsafe(enqueue(), loop).result()
|
|
else:
|
|
loop.run_until_complete(enqueue())
|
|
if loop_state == "closed":
|
|
loop.close()
|
|
try:
|
|
unit_harness._flush_completed_test_logging()
|
|
owner.current = "next-test"
|
|
assert owner.delivered == ("original-test",)
|
|
finally:
|
|
if thread is not None:
|
|
asyncio.run_coroutine_threadsafe(GLOBAL_LOGGING_WORKER.stop(), loop).result()
|
|
loop.call_soon_threadsafe(loop.stop)
|
|
thread.join()
|
|
elif not loop.is_closed():
|
|
loop.run_until_complete(GLOBAL_LOGGING_WORKER.stop())
|
|
if not loop.is_closed():
|
|
loop.close()
|
|
|
|
|
|
def test_dequeued_logging_finishes_under_its_original_owner() -> None:
|
|
owner: Final = _LoggingOwner()
|
|
|
|
async def enqueue() -> None:
|
|
GLOBAL_LOGGING_WORKER.ensure_initialized_and_enqueue(owner.deliver())
|
|
|
|
asyncio.run(enqueue())
|
|
assert GLOBAL_LOGGING_WORKER._queue is not None
|
|
assert GLOBAL_LOGGING_WORKER._queue.empty()
|
|
assert owner.delivered == ()
|
|
unit_harness._flush_completed_test_logging()
|
|
owner.current = "next-test"
|
|
assert owner.delivered == ("original-test",)
|
|
|
|
|
|
class _CanaryRouterHolder:
|
|
router: Router | None = None
|
|
|
|
|
|
def test_register_model_ledger_entry_is_scoped_to_this_test():
|
|
litellm.register_model({CANARY_MODEL: {"litellm_provider": "openai", "input_cost_per_token": 0.001}})
|
|
assert CANARY_MODEL in litellm_utils_module._runtime_registered_model_cost
|
|
|
|
|
|
def test_register_model_ledger_entry_was_rolled_back():
|
|
assert CANARY_MODEL not in litellm_utils_module._runtime_registered_model_cost
|
|
|
|
|
|
def test_live_router_membership_is_scoped_to_this_test():
|
|
_CanaryRouterHolder.router = Router(
|
|
model_list=[
|
|
{
|
|
"model_name": "conftest-isolation-canary-router",
|
|
"litellm_params": {"model": "openai/conftest-isolation-canary-backend", "api_key": "sk-canary"},
|
|
}
|
|
]
|
|
)
|
|
assert _CanaryRouterHolder.router in litellm_router_module._live_routers
|
|
|
|
|
|
def test_live_router_membership_was_rolled_back():
|
|
assert _CanaryRouterHolder.router is not None
|
|
assert _CanaryRouterHolder.router not in litellm_router_module._live_routers
|
|
|
|
|
|
def test_aws_cache_reset_sees_new_handlers_and_replaced_caches(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
module: Final = unit_harness.importlib.import_module("litellm.files.main")
|
|
handler: Final = BaseAWSLLM()
|
|
first_cache: Final = DualCache()
|
|
handler.iam_cache = first_cache
|
|
monkeypatch.setattr(module, "unit_reset_canary", handler, raising=False)
|
|
first_cache.set_cache("unit-reset-key", "first-secret")
|
|
unit_harness._reset_aws_auth_caches()
|
|
assert first_cache.get_cache("unit-reset-key") is None
|
|
second_cache: Final = DualCache()
|
|
handler.iam_cache = second_cache
|
|
second_cache.set_cache("unit-reset-key", "second-secret")
|
|
unit_harness._reset_aws_auth_caches()
|
|
assert second_cache.get_cache("unit-reset-key") is None
|
|
|
|
|
|
def test_aws_cache_reset_sees_a_replaced_module(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
name: Final = "litellm.files.main"
|
|
original_module: Final = unit_harness.importlib.import_module(name)
|
|
package: Final = unit_harness.importlib.import_module("litellm.files")
|
|
monkeypatch.delitem(sys.modules, name)
|
|
monkeypatch.setattr(package, "main", original_module)
|
|
module: Final = unit_harness.importlib.import_module(name)
|
|
handler: Final = BaseAWSLLM()
|
|
cache: Final = DualCache()
|
|
handler.iam_cache = cache
|
|
monkeypatch.setattr(module, "unit_reset_canary", handler, raising=False)
|
|
cache.set_cache("unit-reset-key", "module-secret")
|
|
unit_harness._reset_aws_auth_caches()
|
|
assert cache.get_cache("unit-reset-key") is None
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def registered_collection_models() -> Iterator[None]:
|
|
original_registrations: Final = dict(litellm_utils_module._runtime_registered_model_cost)
|
|
original_routers: Final = frozenset(litellm_router_module._live_routers)
|
|
litellm.register_model(
|
|
{"collection-isolation-ledger": {"litellm_provider": "openai", "input_cost_per_token": 0.001}}
|
|
)
|
|
collection_router: Final = Router(
|
|
model_list=[
|
|
{
|
|
"model_name": "collection-isolation-router",
|
|
"litellm_params": {"model": "openai/collection-isolation-backend", "api_key": "sk-canary"},
|
|
}
|
|
]
|
|
)
|
|
yield
|
|
litellm_utils_module._runtime_registered_model_cost.clear()
|
|
litellm_utils_module._runtime_registered_model_cost.update(original_registrations)
|
|
litellm_router_module._live_routers.discard(collection_router)
|
|
litellm_router_module._live_routers.update(original_routers)
|
|
|
|
|
|
def test_collection_model_metadata_is_not_replayed_into_a_test_catalog() -> None:
|
|
from litellm.litellm_core_utils.get_model_cost_map import adopt_model_cost_map
|
|
|
|
catalog: Final = {"unit-test-catalog-model": {"litellm_provider": "openai", "input_cost_per_token": 0.001}}
|
|
adopt_model_cost_map(catalog)
|
|
assert "collection-isolation-ledger" not in litellm.model_cost
|
|
assert "collection-isolation-router" not in litellm.model_cost
|
|
assert "unit-test-catalog-model" in litellm.model_cost
|