gabriel / muse public

test_cache_status.py file-level

at sha256:c · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:c feat(code-test): audit cache internals, add cache_hit/--cache-status, c… · gabriel · Oct 22, 2026
1 """TDD tests for muse#49 Phase 4 — ``get_cache_status`` / ``--cache-status``.
2
3 Covers CT_11–CT_14: an honest, content-addressed-aware status schema (not
4 the ticket's originally-imagined snapshot-ID-keyed one — see
5 ``docs/code-test-internals.md`` "Decision" section for why), graceful
6 behavior on a fresh repo with no commits and no cache files, and the
7 stale/cache_hit flags tracking real per-file cache warmth.
8 """
9
10 from __future__ import annotations
11
12 import pathlib
13
14 import pytest
15
16 from muse.core.callgraph_cache import CallGraphCache
17 from muse.core.commits import get_head_snapshot_id
18 from muse.core.snapshots import read_snapshot
19 from muse.core.symbol_cache import SymbolCache
20 from muse.core.test_selection import ChangedSymbol, get_cache_status, select_tests
21 from tests.cli_test_helper import CliRunner
22
23
24 @pytest.fixture
25 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
26 monkeypatch.chdir(tmp_path)
27 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
28 runner = CliRunner()
29 result = runner.invoke(None, ["init", "--domain", "code"])
30 assert result.exit_code == 0, result.output
31 return tmp_path
32
33
34 class TestFreshRepoGraceful:
35 def test_no_commits_no_exception_zero_counts(self, repo: pathlib.Path) -> None:
36 """CT_14: a freshly-initialised repo with no commits and no cache
37 files must never raise — just report trivially empty status."""
38 status = get_cache_status(repo)
39
40 assert status["head_snapshot_id"] is None
41 assert status["semantic_files_total"] == 0
42 assert status["files_warm"] == 0
43 assert status["files_cold"] == 0
44 assert status["cache_hit"] is True
45 assert status["stale"] is False
46 assert status["symbol_cache_entries"] == 0
47 assert status["callgraph_cache_entries"] == 0
48 assert status["symbol_cache_age_seconds"] is None
49 assert status["callgraph_cache_age_seconds"] is None
50
51 def test_no_muse_repo_at_all_no_exception(self, tmp_path: pathlib.Path) -> None:
52 """Not even a ``.muse/`` directory — must still degrade gracefully."""
53 status = get_cache_status(tmp_path)
54 assert status["cache_hit"] is True
55 assert status["head_snapshot_id"] is None
56
57
58 class TestColdAfterCommitBeforeAnyRun:
59 def test_committed_file_reported_cold(self, repo: pathlib.Path) -> None:
60 """CT_11/CT_12: after a real commit, before any cache-populating
61 ``select_tests`` call, the file must be reported stale/cold."""
62 (repo / "prod.py").write_text("def compute(x: int) -> int:\n return x * 2\n")
63 runner = CliRunner()
64 assert runner.invoke(None, ["code", "add", "."]).exit_code == 0
65 assert runner.invoke(None, ["commit", "-m", "v1"]).exit_code == 0
66
67 status = get_cache_status(repo)
68
69 assert status["head_snapshot_id"] is not None
70 assert status["semantic_files_total"] == 1
71 assert status["files_warm"] == 0
72 assert status["files_cold"] == 1
73 assert status["cache_hit"] is False
74 assert status["stale"] is True
75
76
77 class TestWarmAfterRealRun:
78 def test_cache_status_reflects_warm_run(self, repo: pathlib.Path) -> None:
79 """CT_13: after a real select_tests run warms both caches, status
80 must flip to cache_hit=True/stale=False and report nonzero entries
81 and a defined (non-negative) age — proving the status function reads
82 the *actual* on-disk cache state, not a hardcoded value."""
83 (repo / "prod.py").write_text("def compute(x: int) -> int:\n return x * 2\n")
84 runner = CliRunner()
85 assert runner.invoke(None, ["code", "add", "."]).exit_code == 0
86 assert runner.invoke(None, ["commit", "-m", "v1"]).exit_code == 0
87
88 sid = get_head_snapshot_id(repo, "main")
89 snapshot = read_snapshot(repo, sid)
90 assert snapshot is not None
91
92 sym_cache = SymbolCache.from_root(repo)
93 cg_cache = CallGraphCache.from_root(repo)
94 changed = [ChangedSymbol(address="prod.py::compute", change_kind="modified")]
95 select_tests(repo, changed, snapshot.manifest, cache=sym_cache, callgraph_cache=cg_cache)
96 sym_cache.save()
97 cg_cache.save()
98
99 status = get_cache_status(repo)
100
101 assert status["cache_hit"] is True
102 assert status["stale"] is False
103 assert status["files_cold"] == 0
104 assert status["symbol_cache_entries"] > 0
105 assert status["callgraph_cache_entries"] > 0
106 assert status["symbol_cache_age_seconds"] is not None
107 assert status["symbol_cache_age_seconds"] >= 0.0
108 assert status["callgraph_cache_age_seconds"] is not None
109 assert status["callgraph_cache_age_seconds"] >= 0.0
110
111 def test_status_never_triggers_a_parse(
112 self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch
113 ) -> None:
114 """get_cache_status must be pure dict-lookup + os.stat — it must
115 never call read_object or parse_symbols, even when warm."""
116 from unittest.mock import patch
117
118 (repo / "prod.py").write_text("def compute(x: int) -> int:\n return x * 2\n")
119 runner = CliRunner()
120 assert runner.invoke(None, ["code", "add", "."]).exit_code == 0
121 assert runner.invoke(None, ["commit", "-m", "v1"]).exit_code == 0
122
123 with (
124 patch("muse.plugins.code._query.read_object") as mock_query_read,
125 patch("muse.plugins.code._callgraph.read_object") as mock_cg_read,
126 patch("muse.plugins.code.ast_parser.parse_symbols") as mock_parse,
127 ):
128 get_cache_status(repo)
129
130 mock_query_read.assert_not_called()
131 mock_cg_read.assert_not_called()
132 mock_parse.assert_not_called()