mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-19 00:01:29 +00:00
fix(tests): resolve the integration support package without run.py's PYTHONPATH
tests/integration/conftest.py imported the bare `integration` package. Because tests/__init__.py and tests/integration/__init__.py both exist, pytest's default prepend import mode puts only the repo root on sys.path, so that name resolved only under the PYTHONPATH that tests/integration/run.py injects. Every other invocation died at conftest import with ModuleNotFoundError: No module named 'integration' and exit 4, including the command test_oci_integration.py documents in its own docstring. The imports now use the tests.integration._support path that pytest actually resolves, matching the 120 other `from tests.` imports in the suite. run.py's PYTHONPATH still works because it already puts the repo root on the path. tests/code_coverage_tests/test_integration_suite_imports.py collects every file under tests/integration with PYTHONPATH scrubbed and asserts a non-zero collection count, so an unresolvable import fails the code-quality job instead of only the developers who run these files by hand. CI runs the three pre-existing files through the allowlist rather than executing them, which is why nothing caught this.
This commit is contained in:
parent
174c1ac4ed
commit
3b0fbc426d
11 changed files with 75 additions and 18 deletions
3
.github/workflows/test-code-quality.yml
vendored
3
.github/workflows/test-code-quality.yml
vendored
|
|
@ -83,6 +83,9 @@ jobs:
|
|||
- name: test_e2e_changed_gate
|
||||
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_e2e_changed_gate.py tests/code_coverage_tests/test_e2e_idp_stack.py
|
||||
|
||||
- name: test_integration_suite_imports
|
||||
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_integration_suite_imports.py
|
||||
|
||||
- name: router_code_coverage
|
||||
run: uv run --no-sync python ./tests/code_coverage_tests/router_code_coverage.py
|
||||
|
||||
|
|
|
|||
54
tests/code_coverage_tests/test_integration_suite_imports.py
Normal file
54
tests/code_coverage_tests/test_integration_suite_imports.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT: Final = Path(__file__).resolve().parents[2]
|
||||
INTEGRATION_ROOT: Final = REPO_ROOT / "tests" / "integration"
|
||||
COLLECTED_COUNT: Final = re.compile(r"^(\d+) tests? collected", re.MULTILINE)
|
||||
|
||||
|
||||
def _integration_test_files() -> tuple[Path, ...]:
|
||||
return tuple(sorted(INTEGRATION_ROOT.rglob("test_*.py")))
|
||||
|
||||
|
||||
def _collect_without_injected_pythonpath(target: str) -> subprocess.CompletedProcess[str]:
|
||||
env: Final = {key: value for key, value in os.environ.items() if key != "PYTHONPATH"}
|
||||
return subprocess.run(
|
||||
(sys.executable, "-m", "pytest", target, "--collect-only", "-q", "-p", "no:cacheprovider"),
|
||||
cwd=REPO_ROOT,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _assert_collected(result: subprocess.CompletedProcess[str], target: str) -> None:
|
||||
assert result.returncode == 0, f"{target} exited {result.returncode}\n{result.stdout}\n{result.stderr}"
|
||||
match: Final = COLLECTED_COUNT.search(result.stdout)
|
||||
assert match is not None, f"{target} reported no collection summary\n{result.stdout}"
|
||||
assert int(match.group(1)) > 0, f"{target} collected nothing, so nothing was verified\n{result.stdout}"
|
||||
|
||||
|
||||
def test_the_integration_suite_still_has_files_to_guard() -> None:
|
||||
assert _integration_test_files()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"target",
|
||||
[str(path.relative_to(REPO_ROOT)) for path in _integration_test_files()],
|
||||
)
|
||||
def test_each_integration_file_collects_the_way_its_docs_document_it(target: str) -> None:
|
||||
_assert_collected(_collect_without_injected_pythonpath(target), target)
|
||||
|
||||
|
||||
def test_the_whole_integration_directory_collects_without_an_injected_pythonpath() -> None:
|
||||
target: Final = "tests/integration"
|
||||
_assert_collected(_collect_without_injected_pythonpath(target), target)
|
||||
|
|
@ -12,7 +12,7 @@ from typing import Final, TypeVar
|
|||
import httpx
|
||||
from pydantic import JsonValue, TypeAdapter
|
||||
|
||||
from integration._support.database import read_rows
|
||||
from tests.integration._support.database import read_rows
|
||||
|
||||
JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||
T = TypeVar("T")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from contextlib import contextmanager
|
|||
import httpx
|
||||
from hypothesis import Phase, settings
|
||||
|
||||
from integration._support.client import Gateway
|
||||
from tests.integration._support.client import Gateway
|
||||
|
||||
LIFECYCLE_SETTINGS: Final = settings(
|
||||
max_examples=20,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import pytest
|
|||
from hypothesis import strategies as st
|
||||
from hypothesis.stateful import RuleBasedStateMachine, invariant, rule, run_state_machine_as_test
|
||||
|
||||
from integration._support.client import Gateway, eventually, object_value
|
||||
from integration._support.database import read_rows
|
||||
from integration._support.generation import LIFECYCLE_SETTINGS, bounded_http_requests
|
||||
from tests.integration._support.client import Gateway, eventually, object_value
|
||||
from tests.integration._support.database import read_rows
|
||||
from tests.integration._support.generation import LIFECYCLE_SETTINGS, bounded_http_requests
|
||||
|
||||
|
||||
def assert_serving(gateway: Gateway, model: str, key: str, status: int, error_type: str = "auth_error") -> None:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from typing import Final
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
from integration._support.client import Gateway, object_value, string_value
|
||||
from integration._support.database import read_rows
|
||||
from tests.integration._support.client import Gateway, object_value, string_value
|
||||
from tests.integration._support.database import read_rows
|
||||
|
||||
|
||||
def model_identity(gateway: Gateway, alias: str) -> str:
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import pytest
|
|||
import httpx
|
||||
from redis import Redis
|
||||
|
||||
from integration._support.client import Gateway, eventually, gateway_from_environment
|
||||
from integration._support.manifest import OWNED_DIRECTORIES, contracts
|
||||
from integration._support.generation import LIFECYCLE_SETTINGS
|
||||
from tests.integration._support.client import Gateway, eventually, gateway_from_environment
|
||||
from tests.integration._support.manifest import OWNED_DIRECTORIES, contracts
|
||||
from tests.integration._support.generation import LIFECYCLE_SETTINGS
|
||||
|
||||
COLLECTED: Final = pytest.StashKey[tuple[str, ...]]()
|
||||
REPORTS: Final = pytest.StashKey[list[pytest.TestReport]]()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from hashlib import sha256
|
|||
|
||||
import pytest
|
||||
|
||||
from integration._support.client import Gateway, object_value
|
||||
from integration._support.database import read_rows
|
||||
from tests.integration._support.client import Gateway, object_value
|
||||
from tests.integration._support.database import read_rows
|
||||
|
||||
|
||||
@pytest.mark.covers("mgmt.key.update.preserves_independent_fields")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ from hypothesis import strategies as st
|
|||
from hypothesis.stateful import RuleBasedStateMachine, invariant, rule, run_state_machine_as_test
|
||||
from pydantic import JsonValue
|
||||
|
||||
from integration._support.client import Gateway, object_value
|
||||
from integration._support.database import read_rows
|
||||
from integration._support.generation import LIFECYCLE_SETTINGS, bounded_http_requests
|
||||
from tests.integration._support.client import Gateway, object_value
|
||||
from tests.integration._support.database import read_rows
|
||||
from tests.integration._support.generation import LIFECYCLE_SETTINGS, bounded_http_requests
|
||||
|
||||
|
||||
@pytest.mark.covers("mgmt.key.update.generated_sequences_preserve_state")
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import uuid
|
|||
import pytest
|
||||
import yaml
|
||||
|
||||
from integration._support.client import Gateway, eventually, object_value, string_value
|
||||
from integration._support.database import read_rows
|
||||
from tests.integration._support.client import Gateway, eventually, object_value, string_value
|
||||
from tests.integration._support.database import read_rows
|
||||
|
||||
|
||||
@pytest.mark.covers("quota_management.spend_tracking.custom_price.matches_input_rates")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Final
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
from integration._support.client import Gateway, JSON_OBJECT, object_value
|
||||
from tests.integration._support.client import Gateway, JSON_OBJECT, object_value
|
||||
|
||||
|
||||
@pytest.mark.covers("other.provider_wire.internal_parameters_filtered")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue