Skip to content

Commit c4665d7

Browse files
committed
conftest(feat): Add fixture cache hit/miss tracking
why: Need visibility into whether repo fixtures are using cached master copies or performing slow initialization. what: - Add FixtureMetrics dataclass for structured metrics collection - Track cache hits/misses via from_cache attribute on RepoFixtureResult - Display cache statistics in terminal summary with hit rate percentages
1 parent e468f02 commit c4665d7

1 file changed

Lines changed: 45 additions & 2 deletions

File tree

conftest.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from __future__ import annotations
1212

13+
import dataclasses
1314
import time
1415
import typing as t
1516
from collections import defaultdict
@@ -24,9 +25,21 @@
2425

2526
pytest_plugins = ["pytester"]
2627

28+
29+
@dataclasses.dataclass
30+
class FixtureMetrics:
31+
"""Metrics collected during fixture execution."""
32+
33+
fixture_name: str
34+
duration: float
35+
cache_hit: bool | None = None # None if not applicable (non-repo fixture)
36+
37+
2738
# Fixture profiling storage
2839
_fixture_timings: dict[str, list[float]] = defaultdict(list)
2940
_fixture_call_counts: dict[str, int] = defaultdict(int)
41+
_fixture_cache_hits: dict[str, int] = defaultdict(int)
42+
_fixture_cache_misses: dict[str, int] = defaultdict(int)
3043

3144

3245
def pytest_addoption(parser: pytest.Parser) -> None:
@@ -76,10 +89,17 @@ def pytest_fixture_setup(
7689
fixturedef: FixtureDef[t.Any],
7790
request: SubRequest,
7891
) -> t.Generator[None, t.Any, t.Any]:
79-
"""Wrap fixture setup to measure timing."""
92+
"""Wrap fixture setup to measure timing and track cache hits."""
8093
start = time.perf_counter()
8194
try:
8295
result = yield
96+
# Track cache hits for fixtures that support it (RepoFixtureResult)
97+
if hasattr(result, "from_cache"):
98+
fixture_name = fixturedef.argname
99+
if result.from_cache:
100+
_fixture_cache_hits[fixture_name] += 1
101+
else:
102+
_fixture_cache_misses[fixture_name] += 1
83103
return result
84104
finally:
85105
duration = time.perf_counter() - start
@@ -93,7 +113,7 @@ def pytest_terminal_summary(
93113
exitstatus: int,
94114
config: pytest.Config,
95115
) -> None:
96-
"""Display fixture timing summary."""
116+
"""Display fixture timing and cache statistics summary."""
97117
durations_count = config.option.fixture_durations
98118
durations_min = config.option.fixture_durations_min
99119

@@ -134,6 +154,29 @@ def pytest_terminal_summary(
134154
f"{name:<40} {total:>9.3f}s {calls:>8} {avg:>9.3f}s",
135155
)
136156

157+
# Display cache statistics if any repo fixtures were used
158+
if _fixture_cache_hits or _fixture_cache_misses:
159+
terminalreporter.write_line("")
160+
terminalreporter.write_sep("=", "fixture cache statistics")
161+
terminalreporter.write_line("")
162+
terminalreporter.write_line(
163+
f"{'Fixture':<40} {'Hits':>8} {'Misses':>8} {'Hit Rate':>10}",
164+
)
165+
terminalreporter.write_line("-" * 70)
166+
167+
# Combine hits and misses for all fixtures that have cache tracking
168+
all_cache_fixtures = set(_fixture_cache_hits.keys()) | set(
169+
_fixture_cache_misses.keys()
170+
)
171+
for name in sorted(all_cache_fixtures):
172+
hits = _fixture_cache_hits.get(name, 0)
173+
misses = _fixture_cache_misses.get(name, 0)
174+
total = hits + misses
175+
hit_rate = (hits / total * 100) if total > 0 else 0
176+
terminalreporter.write_line(
177+
f"{name:<40} {hits:>8} {misses:>8} {hit_rate:>9.1f}%",
178+
)
179+
137180

138181
@pytest.fixture(autouse=True)
139182
def add_doctest_fixtures(

0 commit comments

Comments
 (0)