litellm/tests/unit/test_conftest_isolation.py
yuneng-jiang 9b53ee878f
perf(ci): isolate and shard integration suites (#44146)
* 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
2026-10-10 11:15:19 -07:00

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