mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-27 01:22:18 +00:00
* ci: run the unit_selection.sh shard files on every event instead of only fork pull requests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * ci: rename fork-flag to unit-flag now that it applies on every event * test: move tests/test_litellm root and small trees into tests/unit Pure renames, no content changes. Follow-up commits in this PR fix references, merge the three files that already existed in tests/unit, keep live-provider tests in tests/test_litellm and wire CI. * test: carry tests/test_litellm conftest isolation into tests/unit Callback lists, routing fallbacks, cached HTTP clients, logger state, AWS, proxy-URL and keychain env, and session-end client cleanup now reset for unit tests too. The environment isolation owns its MonkeyPatch so a test's own monkeypatch is undone before the model-cost teardown runs. * test: merge, split and prune the moved root and small-tree tests Merge batches/test_batch_utils.py and the chat_completions and messages dispatch tests into the files that already existed in tests/unit. Keep the live Gemini interactions tests, the async image-fetch format test and the OpenAI embedding scorer test in tests/test_litellm since they need real network or keys. Put test_router.py under tests/unit/test_router so the existing package no longer shadows it. Delete eight tests the audit found superseded by stronger ones kept in this move. * ci: run the moved root and small-tree tests under their legacy flags Add the misc and responses-caching-types flags to unit_selection.sh and CircleCI, extend enterprise-routing and mcp-integration, and point the legacy GHA shards, Makefile, redis-compat workflow, merge smoke manifest and change classifier at the new paths. * test: make the new tests/unit directories packages tests/unit/test_package_layout.py requires every directory to carry an __init__.py, and without one the moved and retained test_litellm_responses_bridge.py modules collide on import. * test: scope the unit socket block to tests/unit in shared sessions The GHA shards collect the legacy test-path and the unit selection in one pytest session. The unit conftest's loopback-only block leaked into legacy modules that reach the network at import. The legacy conftest now lifts the restriction at collect and setup time, and the unit conftest re-applies it when collecting its own modules. * test: give the shard-script tests their own GITHUB_OUTPUT They only passed where the runner set it. The CircleCI unit job's env allowlist drops it, so the script's redirect failed there. * test: point the router and module-deletion checks at tests/unit router_code_coverage and code_qa_check_tests only searched tests/test_litellm, so the moved router tests no longer counted. The two silent-experiment tests the audit deleted were the only direct callers of those methods; they are replaced with tests that assert the forwarded shadow request and the recursion guard. --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
130 lines
4.7 KiB
Python
130 lines
4.7 KiB
Python
"""
|
|
Test for LITELLM_DISABLE_LAZY_LOADING environment variable.
|
|
|
|
This test verifies that when LITELLM_DISABLE_LAZY_LOADING is set,
|
|
encoding is loaded at import time (pre-#18070 behavior) instead of lazy loading.
|
|
|
|
This addresses issue #18659: VCR cassette creation broken by lazy loading.
|
|
For now, this only affects encoding as it was the only reported issue.
|
|
|
|
Tests that need to clear sys.modules and re-import litellm run in subprocesses
|
|
to avoid contaminating the test process's module graph (which breaks mock.patch
|
|
for all subsequent tests on the same xdist worker).
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
|
|
def _run_python(
|
|
script: str, env_override: dict | None = None
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run a Python script in a subprocess and return the result."""
|
|
import os
|
|
|
|
env = os.environ.copy()
|
|
# Remove the var so each test controls it explicitly
|
|
env.pop("LITELLM_DISABLE_LAZY_LOADING", None)
|
|
env.pop("TIKTOKEN_CACHE_DIR", None)
|
|
if env_override:
|
|
env.update(env_override)
|
|
return subprocess.run(
|
|
[sys.executable, "-c", textwrap.dedent(script)],
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
# Importing litellm can cold-load tiktoken/tokenizer assets and is
|
|
# occasionally slow on CI runners; these tests validate behavior, not speed.
|
|
timeout=180,
|
|
)
|
|
|
|
|
|
def test_eager_loading_enabled():
|
|
"""Test that encoding is loaded at import time when env var is set"""
|
|
result = _run_python(
|
|
"""
|
|
import litellm
|
|
assert hasattr(litellm, "encoding"), "Encoding should be available when eager loading is enabled"
|
|
encoding = litellm.encoding
|
|
assert encoding is not None, "Encoding should not be None"
|
|
tokens = encoding.encode("Hello, world!")
|
|
assert len(tokens) > 0, "Encoding should work"
|
|
""",
|
|
env_override={"LITELLM_DISABLE_LAZY_LOADING": "1"},
|
|
)
|
|
assert (
|
|
result.returncode == 0
|
|
), f"Subprocess failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
|
|
|
|
def test_eager_loading_env_var_values():
|
|
"""Test that various truthy env var values all enable eager loading.
|
|
|
|
All values are tested inside a single subprocess to avoid spawning one
|
|
cold ``import litellm`` process per value (~78 s each on CI). The
|
|
subprocess re-imports litellm in isolated ``importlib`` reloads so each
|
|
value gets a fresh module, but we only pay the process-start cost once.
|
|
"""
|
|
result = _run_python(
|
|
"""
|
|
import importlib, sys, os
|
|
|
|
values = ["1", "true", "True", "TRUE", "yes", "Yes", "YES", "on", "On", "ON"]
|
|
for value in values:
|
|
# Set the env var for this iteration
|
|
os.environ["LITELLM_DISABLE_LAZY_LOADING"] = value
|
|
# Remove cached litellm modules so re-import picks up the new env
|
|
mods_to_remove = [k for k in sys.modules if k == "litellm" or k.startswith("litellm.")]
|
|
for m in mods_to_remove:
|
|
del sys.modules[m]
|
|
import litellm
|
|
assert hasattr(litellm, "encoding"), f"Encoding missing for {value!r}"
|
|
tokens = litellm.encoding.encode("test")
|
|
assert len(tokens) > 0, f"Encoding broken for {value!r}"
|
|
""",
|
|
env_override={"LITELLM_DISABLE_LAZY_LOADING": "1"},
|
|
)
|
|
assert (
|
|
result.returncode == 0
|
|
), f"Failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
|
|
|
|
def test_lazy_loading_default():
|
|
"""Test that encoding is lazy loaded by default (when env var is not set)"""
|
|
result = _run_python(
|
|
"""
|
|
import litellm
|
|
# Encoding should be accessible via __getattr__ (lazy loading)
|
|
encoding = litellm.encoding
|
|
tokens = encoding.encode("Hello, world!")
|
|
assert len(tokens) > 0, "Encoding should work"
|
|
""",
|
|
)
|
|
assert (
|
|
result.returncode == 0
|
|
), f"Subprocess failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
|
|
|
|
def test_tiktoken_cache_dir_set_on_lazy_load():
|
|
"""Test that TIKTOKEN_CACHE_DIR is set when encoding is lazy loaded.
|
|
|
|
This ensures the local tiktoken cache is used instead of downloading
|
|
from the internet. Regression test for issue #19768.
|
|
"""
|
|
result = _run_python(
|
|
"""
|
|
import os
|
|
import litellm
|
|
# Access encoding (triggers lazy load)
|
|
_ = litellm.encoding
|
|
assert "TIKTOKEN_CACHE_DIR" in os.environ, "TIKTOKEN_CACHE_DIR should be set after lazy loading encoding"
|
|
cache_dir = os.environ["TIKTOKEN_CACHE_DIR"]
|
|
assert "tokenizers" in cache_dir, f"TIKTOKEN_CACHE_DIR should point to tokenizers directory, got: {cache_dir}"
|
|
""",
|
|
)
|
|
assert (
|
|
result.returncode == 0
|
|
), f"Subprocess failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
|