fathom-code / tests /conftest.py
23f2002275
clean repo without secrets or data
071ba6b
Raw
History Blame Contribute Delete
2.84 kB
"""Shared pytest fixtures for FATHOM env tests."""
from __future__ import annotations
import json
import os
import signal
import socket
import subprocess
import sys
import time
from pathlib import Path
from typing import Iterator
import pytest
from fastapi.testclient import TestClient
@pytest.fixture()
def fresh_test_client() -> Iterator[TestClient]:
"""A TestClient bound to a FRESH FastAPI app — avoids single-_ENV state bleed between tests."""
import importlib
import env.server.app as app_mod
importlib.reload(app_mod)
yield TestClient(app_mod.app)
@pytest.fixture(scope="session")
def fixture_rows() -> list[dict]:
"""Parse env/data/phase0_fixture.jsonl once per test session."""
path = Path("env/data/phase0_fixture.jsonl")
rows = []
with path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
rows.append(json.loads(line))
assert len(rows) >= 3, f"Expected >=3 fixture rows, got {len(rows)}"
return rows
def _find_free_port() -> int:
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
@pytest.fixture()
def live_uvicorn_server() -> Iterator[str]:
"""Launch uvicorn in a subprocess; yield base_url; terminate on teardown.
Skipped when subprocess uvicorn fails to come up (e.g., flaky Windows CI).
"""
port = _find_free_port()
env = os.environ.copy()
env["PYTHONDONTWRITEBYTECODE"] = "1"
proc = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "env.server.app:app",
"--host", "127.0.0.1", "--port", str(port), "--log-level", "warning"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
)
base_url = f"http://127.0.0.1:{port}"
try:
deadline = time.monotonic() + 10.0
ready = False
import httpx
while time.monotonic() < deadline:
try:
r = httpx.get(base_url + "/healthz", timeout=0.5)
if r.status_code == 200:
ready = True
break
except Exception:
time.sleep(0.2)
if not ready:
out = proc.stdout.read().decode("utf-8", errors="replace")[:2000] if proc.stdout else ""
err = proc.stderr.read().decode("utf-8", errors="replace")[:2000] if proc.stderr else ""
pytest.skip(f"uvicorn failed to come up in 10s. stderr: {err}. stdout: {out}")
yield base_url
finally:
if proc.poll() is None:
try:
if sys.platform != "win32":
proc.send_signal(signal.SIGTERM)
else:
proc.terminate()
proc.wait(timeout=5)
except Exception:
proc.kill()