diff --git a/src/common/test_tools/plugin.py b/src/common/test_tools/plugin.py index 47e5ee2e..92459631 100644 --- a/src/common/test_tools/plugin.py +++ b/src/common/test_tools/plugin.py @@ -31,10 +31,17 @@ def assert_metric_impl() -> Generator[AssertMetricFixture, None, None]: registry = prometheus_client.REGISTRY collectors = [*registry._collector_to_names] - # Reset registry state + # Reset registry state. `MetricWrapperBase.clear()` is implemented for + # parent metrics (those declared with labels) — calling it on an + # unlabeled metric raises AttributeError because `_lock` is only set on + # parents. Reset unlabeled metrics' observable state via `_metric_init`. for collector in collectors: - if isinstance(collector, prometheus_client.metrics.MetricWrapperBase): + if not isinstance(collector, prometheus_client.metrics.MetricWrapperBase): + continue + if collector._is_parent(): # type: ignore[no-untyped-call] collector.clear() + else: + collector._metric_init() # type: ignore[no-untyped-call] def _assert_metric( *, diff --git a/tests/conftest.py b/tests/conftest.py index afe35873..ad91ead7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,3 +92,11 @@ def test_metric() -> prometheus_client.Counter: "Total number of tests run by pytest.", ["test_name"], ) + + +@pytest.fixture(scope="session") +def test_unlabeled_metric() -> prometheus_client.Counter: + return prometheus_client.Counter( + "pytest_unlabeled_total", + "Total number of pytest unlabeled-metric assertions.", + ) diff --git a/tests/unit/common/test_tools/test_plugin.py b/tests/unit/common/test_tools/test_plugin.py index 1ac164d4..14afa29b 100644 --- a/tests/unit/common/test_tools/test_plugin.py +++ b/tests/unit/common/test_tools/test_plugin.py @@ -25,6 +25,22 @@ def test_assert_metrics__metric_incremented__asserts_expected( ) +def test_assert_metrics__unlabeled_metric_incremented__asserts_expected( + assert_metric: AssertMetricFixture, + test_unlabeled_metric: prometheus_client.Counter, +) -> None: + # Given an unlabeled counter incremented during the test (the fixture + # has already reset the registry). The unlabeled-metric reset previously + # raised AttributeError because `MetricWrapperBase.clear()` only works + # on labeled parents; the fixture now uses `_metric_init` for unlabeled. + + # When the counter is incremented + test_unlabeled_metric.inc() + + # Then the assertion sees a fresh count of 1 + assert_metric(name="pytest_unlabeled_total", labels={}, value=1) + + def test_assert_metrics__after_registry_reset__raises_assertion( test_metric: prometheus_client.Counter, ) -> None: