Files
2026-07-13 13:32:05 +08:00

212 lines
6.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
try:
import sys
import pysqlite3 as sqlite3 # type: ignore
sys.modules["sqlite3"] = sqlite3
sys.modules["sqlite3.dbapi2"] = sqlite3.dbapi2
except Exception:
pass
import os
import pytest
import tenacity
from typing import TYPE_CHECKING
from pathlib import Path
from deepeval.tracing.tracing import trace_manager
from deepeval.config.settings import get_settings, reset_settings, Settings
if TYPE_CHECKING:
from _pytest.fixtures import FixtureRequest
PRESERVE_FROM_BASELINE = {
name
for name in Settings.model_fields.keys()
if name.endswith("_API_KEY") and not name.startswith("CONFIDENT")
}
@pytest.fixture(autouse=True)
def _ensure_hidden_store_dir(tmp_path: Path):
d = tmp_path / ".deepeval"
d.mkdir(exist_ok=True)
# some code expects the file to be there after a run,
# but at minimum the directory must exist to avoid FileNotFoundError
yield
@pytest.fixture
def hidden_store_dir(tmp_path: Path) -> Path:
d = tmp_path / ".deepeval"
d.mkdir(parents=True, exist_ok=True)
return d
# Silence telemetry for all tests so we don't have to deal with the noise
@pytest.fixture(autouse=True)
def _telemetry_opt_out(monkeypatch):
monkeypatch.setenv("DEEPEVAL_TELEMETRY_OPT_OUT", "1")
yield
# Run every test in its own temp CWD so .deepeval/.deepeval is sandboxed
@pytest.fixture(autouse=True)
def _isolate_cwd(tmp_path: Path, monkeypatch):
monkeypatch.chdir(tmp_path)
yield
# Default dotenv path most tests can reuse; override in tests as needed
@pytest.fixture
def env_path(monkeypatch, tmp_path: Path) -> Path:
monkeypatch.setenv("ENV_DIR_PATH", str(tmp_path))
return tmp_path / ".env.local"
@pytest.fixture
def env_dir(monkeypatch, tmp_path: Path) -> Path:
monkeypatch.setenv("ENV_DIR_PATH", str(tmp_path))
return tmp_path
@pytest.fixture(autouse=True)
def no_sleep(monkeypatch):
monkeypatch.setattr(tenacity.nap, "sleep", lambda _: None, raising=True)
@pytest.fixture()
def settings():
settings = get_settings()
yield settings
@pytest.fixture(scope="session")
def _session_env_baseline():
# capture the environment as it existed when pytest started
return os.environ.copy()
def _restore_env_to(baseline: dict[str, str]) -> None:
# remove any keys not in the baseline
for k in list(os.environ.keys()):
if k not in baseline:
os.environ.pop(k, None)
# update differing values to match the baseline
for k, v in baseline.items():
if os.environ.get(k) != v:
os.environ[k] = v
@pytest.fixture(autouse=True)
def _env_sandbox(_session_env_baseline, request, monkeypatch):
# Start from the session baseline (CI secrets included)
_restore_env_to(_session_env_baseline)
# Save whitelisted secrets from the session baseline
preserved = {
k: v
for k, v in _session_env_baseline.items()
if k in PRESERVE_FROM_BASELINE and isinstance(v, str) and v.strip()
}
# Clear ALL Settings keys to avoid leaking config (file system mode, default save, etc.)
for setting_key in list(Settings.model_fields.keys()):
monkeypatch.delenv(setting_key, raising=False)
# Re-inject only the secrets we explicitly want to preserve
for k, v in preserved.items():
monkeypatch.setenv(k, v)
# Never open the Confident AI browser UI during tests
monkeypatch.setenv("CONFIDENT_OPEN_BROWSER", "0")
# Disable dotenv by default unless the test opts in via @pytest.mark.enable_dotenv
if not request.node.get_closest_marker("enable_dotenv"):
monkeypatch.setenv("DEEPEVAL_DISABLE_DOTENV", "1")
else:
monkeypatch.delenv("DEEPEVAL_DISABLE_DOTENV", raising=False)
# Fresh Settings for this test
reset_settings(reload_dotenv=False)
yield
# Restore to the session baseline after the test
_restore_env_to(_session_env_baseline)
reset_settings(reload_dotenv=False)
@pytest.fixture(autouse=True)
def _core_mode_no_confident(
_env_sandbox, monkeypatch, request: "FixtureRequest"
):
# Ensure no Confident keys come from the process env in this test
for key in ("CONFIDENT_API_KEY", "CONFIDENTAI_API_KEY"):
monkeypatch.delenv(key, raising=False)
# Prevent dotenv from re-injecting keys from files during the test
# core tests shouldnt depend on local .env anyway
if not request.node.get_closest_marker("enable_dotenv"):
monkeypatch.setenv("DEEPEVAL_DISABLE_DOTENV", "1")
# Rebuild the Settings singleton from the now-clean process env
reset_settings(reload_dotenv=False)
# Clear the in-memory Settings fields (no persistence)
s = get_settings()
with s.edit(persist=False) as ctx:
ctx.s.CONFIDENT_API_KEY = None
# Yield control to the test
yield
@pytest.fixture()
def enable_dotenv(monkeypatch):
monkeypatch.setenv("DEEPEVAL_DISABLE_DOTENV", "0")
# rebuild Settings after changing the env
reset_settings(reload_dotenv=False)
@pytest.fixture(autouse=False)
def unpatch_openai_after():
from deepeval.openai.patch import unpatch_openai_classes
yield
unpatch_openai_classes()
@pytest.fixture(autouse=True)
def _reset_tracing_state():
from deepeval.tracing.types import EvalSession
trace_manager.clear_traces()
# Atomic reset: dropping the session clears mode + every per-run
# collection (pending_traces, traces_to_evaluate, trace_uuid_to_golden,
# test_case_metrics) in one go, so a test cannot leak eval state to its
# neighbors via a forgotten field.
trace_manager.eval_session = EvalSession()
try:
trace_manager.task_bindings.clear()
except Exception:
pass
yield
@pytest.fixture
def completed_traces(monkeypatch):
"""Capture completed traces before they are evicted from trace_manager.traces."""
captured = []
_original = trace_manager.end_trace
def _capturing(trace_uuid):
if trace_uuid in trace_manager.active_traces:
captured.append(trace_manager.active_traces[trace_uuid])
_original(trace_uuid)
monkeypatch.setattr(trace_manager, "end_trace", _capturing)
return captured