Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 13 additions & 22 deletions tests/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"""

import functools
import sys
import os
from collections.abc import Iterator
from pathlib import Path

import pytest
import requests

from e2e_config import CONTROL_PLANE_BASE_URL, PROXY_BASE_URL
from e2e_db import RESET_OPT_IN_ENV, reset_spend_logs, run_spend_log_cleanup
from junit_properties import attach_result_properties
from lifecycle import ProxyClientProvider, ResourceManager
from proxy_client import ProxyClient, build_proxy_client
Expand Down Expand Up @@ -107,26 +107,17 @@ def pytest_runtest_call(item: pytest.Item) -> None:


def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
"""Once the whole e2e session is done (all suites), truncate the spend logs so
the DB doesn't accumulate test rows. Sessions where no e2e test body ran leave
the DB alone so a `DATABASE_URL` pointing at a shared instance is never wiped
without an e2e run. Best-effort: a cleanup failure (no DB reachable) must not
fail the run. The spend_tracking dir goes on sys.path only for this import and
is removed after, so a broader `pytest tests/` run is not left with a mutated
path."""
if not session.stash.get(_E2E_TEST_RAN, False):
return
spend_dir = str(Path(__file__).parent / "quota_management" / "spend_tracking")
sys.path.insert(0, spend_dir)
try:
from spend_e2e_client import reset_spend_logs # pyright: ignore

reset_spend_logs()
except Exception as exc: # noqa: BLE001 - cleanup is best-effort
print(f"spend-log cleanup best-effort failed: {exc}")
finally:
if spend_dir in sys.path:
sys.path.remove(spend_dir)
"""Once the whole e2e session is done (all suites), optionally truncate the
spend logs so the DB doesn't accumulate test rows. The truncate is destructive
and irreversible, so it runs only when the operator explicitly opts in
(`E2E_RESET_SPEND_LOGS=1`) and an e2e test body actually ran; otherwise a
`DATABASE_URL` pointing at a shared or staging instance is left untouched.
Best-effort: a cleanup failure (no DB reachable) must not fail the run."""
run_spend_log_cleanup(
opt_in=os.environ.get(RESET_OPT_IN_ENV),
e2e_test_ran=session.stash.get(_E2E_TEST_RAN, False),
truncate=reset_spend_logs,
)


@pytest.fixture(scope="session")
Expand Down
56 changes: 56 additions & 0 deletions tests/e2e/e2e_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Shared, destructive DB helpers for the e2e harness.

Kept at the top level next to e2e_config and lifecycle so every suite imports it
by name (`from e2e_db import ...`); no suite reaches into another's directory by
mutating sys.path.

reset_spend_logs truncates LiteLLM_SpendLogs and cannot be undone, so the
session-finish cleanup routes through run_spend_log_cleanup, which fires the
truncate only on an explicit operator opt-in. "An e2e test ran" is necessary but
never sufficient: a DATABASE_URL pointing at a shared or staging instance must
not be wiped by a routine local run that merely exercised a test.
"""

import os
from collections.abc import Callable

RESET_OPT_IN_ENV = "E2E_RESET_SPEND_LOGS"


def run_spend_log_cleanup(
*, opt_in: str | None, e2e_test_ran: bool, truncate: Callable[[], None]
) -> bool:
"""Invoke `truncate` iff the destructive spend-log reset is both opted into
and warranted, returning whether the truncate was attempted.

The truncate fires only when the opt-in value is exactly "1" AND an e2e test
body actually ran. Any other opt-in value (unset, "0", "true", "") leaves the
DB untouched, so the destructive path is never armed by the env var's mere
presence or by a test run on its own. Best-effort: a truncate failure is
swallowed so cleanup never fails the session, so the returned bool reports
that the reset was attempted, not that the DB call succeeded.
"""
if opt_in != "1" or not e2e_test_ran:
return False
try:
truncate()
except Exception as exc: # noqa: BLE001 - cleanup is best-effort
print(f"spend-log cleanup best-effort failed: {exc}")
return True
Comment thread
greptile-apps[bot] marked this conversation as resolved.


def reset_spend_logs() -> None:
"""Truncate LiteLLM_SpendLogs for a clean slate. No proxy endpoint deletes
spend logs (/global/spend/reset keeps them), so go to the DB directly. Uses
DATABASE_URL (default: the local docker postgres on its mapped host port; the
in-container `@db` host isn't resolvable from the host, so default to
localhost).
"""
import psycopg

url = os.environ.get(
"DATABASE_URL",
"postgresql://llmproxy:dbpassword9090@localhost:5432/litellm",
)
with psycopg.connect(url) as conn:
_ = conn.execute('TRUNCATE TABLE "LiteLLM_SpendLogs"')
19 changes: 0 additions & 19 deletions tests/e2e/quota_management/spend_tracking/spend_e2e_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from __future__ import annotations

import os
import time
from collections.abc import Callable
from dataclasses import dataclass
Expand Down Expand Up @@ -50,7 +49,6 @@
__all__ = [
"SpendClient",
"build_client",
"reset_spend_logs",
"unique_marker",
"unwrap",
"is_ok",
Expand All @@ -59,23 +57,6 @@
]


def reset_spend_logs() -> None:
"""Truncate LiteLLM_SpendLogs for a clean slate. No proxy endpoint deletes
spend logs (/global/spend/reset keeps them), so go to the DB directly. Uses
DATABASE_URL (default: the local docker postgres on its mapped host port; note
the in-container `@db` host isn't resolvable from the host, so default to
localhost).
"""
import psycopg

url = os.environ.get(
"DATABASE_URL",
"postgresql://llmproxy:dbpassword9090@localhost:5432/litellm",
)
with psycopg.connect(url) as conn:
_ = conn.execute('TRUNCATE TABLE "LiteLLM_SpendLogs"')


def _chat_body(
model: str,
content: str,
Expand Down
Loading