Skip to content
Closed
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
4 changes: 4 additions & 0 deletions hindsight-api-slim/hindsight_api/api/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def FieldWithDefault(default_factory: Callable, **kwargs) -> Any:
get_metrics_collector,
initialize_metrics,
normalize_http_endpoint,
reset_metrics_collector,
)
from hindsight_api.models import RequestContext

Expand Down Expand Up @@ -3970,6 +3971,9 @@ async def lifespan(app: FastAPI):

shutdown_tracing()

# Prevent in-process app instances from leaking a live collector across tests (#3780).
reset_metrics_collector()

from hindsight_api import __version__
from hindsight_api.config import get_config

Expand Down
8 changes: 8 additions & 0 deletions hindsight-api-slim/hindsight_api/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,3 +1120,11 @@ def create_metrics_collector() -> MetricsCollector:
global _metrics_collector
_metrics_collector = MetricsCollector()
return _metrics_collector


def reset_metrics_collector() -> None:
"""
Reset the global metrics collector back to NoOpMetricsCollector.
"""
global _metrics_collector
_metrics_collector = NoOpMetricsCollector()
20 changes: 20 additions & 0 deletions hindsight-api-slim/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@ def _cleanup_leaked_span_recorders():
recorders.remove(recorder)


@pytest.fixture(autouse=True)
def _cleanup_leaked_metrics_collector():
"""Reset the process-global metrics collector to NoOpMetricsCollector around each test (#3780).

``create_metrics_collector()`` (called e.g. during FastAPI app lifespan startup)
permanently sets ``_metrics_collector`` to a real ``MetricsCollector``. Without
per-test teardown, this leaks across tests in the same pytest-xdist worker.
Subsequent provider tests with MagicMock-based usage objects fail with
TypeError when ``MetricsCollector.record_llm_call`` performs integer comparisons.
"""
import hindsight_api.metrics as metrics_module

original_collector = metrics_module._metrics_collector
metrics_module._metrics_collector = metrics_module.NoOpMetricsCollector()
try:
yield
finally:
metrics_module._metrics_collector = original_collector


# Default pg0 instance configuration for tests
DEFAULT_PG0_INSTANCE_NAME = "hindsight-test"
DEFAULT_PG0_PORT = int(os.environ.get("HINDSIGHT_TEST_PG_PORT", "5556"))
Expand Down
23 changes: 13 additions & 10 deletions hindsight-api-slim/tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
create_metrics_collector,
initialize_metrics,
normalize_http_endpoint,
reset_metrics_collector,
)


Expand Down Expand Up @@ -266,21 +267,23 @@ def test_record_operation_includes_bank_id_when_enabled(self):


class TestGetMetricsCollector:
"""Tests for the get_metrics_collector function."""
"""Tests for the get_metrics_collector and reset_metrics_collector functions."""

def test_returns_noop_by_default(self):
"""Test that get_metrics_collector returns NoOpMetricsCollector by default."""
# Reset global state
import hindsight_api.metrics as metrics_module
collector = get_metrics_collector()
assert isinstance(collector, NoOpMetricsCollector)

original_collector = metrics_module._metrics_collector
def test_create_and_reset_metrics_collector(self):
"""Test creating and resetting the global metrics collector (#3780)."""
with patch("hindsight_api.metrics.get_meter") as mock_get_meter:
mock_get_meter.return_value = MagicMock()
collector = create_metrics_collector()
assert isinstance(collector, MetricsCollector)
assert get_metrics_collector() is collector

try:
metrics_module._metrics_collector = NoOpMetricsCollector()
collector = get_metrics_collector()
assert isinstance(collector, NoOpMetricsCollector)
finally:
metrics_module._metrics_collector = original_collector
reset_metrics_collector()
assert isinstance(get_metrics_collector(), NoOpMetricsCollector)


class TestMetricsCollectorBase:
Expand Down
Loading