gabriel / muse public
test_blame_symlog_fast_path.py python
188 lines 7.7 KB
Raw
sha256:a5f8a020b53ddab68f231cf27489fe865525f85ee924133034262d39b332076e fix(clone,pull): never claim success when a snapshot hash m… Sonnet 5 patch 10 minutes ago
1 """muse#53 item 2 -- `muse code blame` prefers the symlog journal (O(1) per
2 symbol) over the full commit-history BFS scan when a journal exists.
3
4 Correctness parity with the pre-existing BFS path is what matters here
5 (the existing test_cmd_blame*/test_blame_supercharge suites already
6 exercise that path's output and continue to pass unchanged, confirming
7 the symlog fast path produces a structurally compatible result) --
8 this file targets the NEW decision logic: when the fast path is used,
9 when it falls back, and rename-chain correctness via the journal.
10 """
11
12 from __future__ import annotations
13 from collections.abc import Mapping
14
15 import json
16 import pathlib
17 import textwrap
18
19 import pytest
20
21 from tests.cli_test_helper import CliRunner
22
23 runner = CliRunner()
24
25
26 def _env(root: pathlib.Path) -> Mapping[str, str]:
27 return {"MUSE_REPO_ROOT": str(root)}
28
29
30 def _run(root: pathlib.Path, *args: str):
31 return runner.invoke(None, list(args), env=_env(root))
32
33
34 @pytest.fixture()
35 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
36 monkeypatch.chdir(tmp_path)
37 r = _run(tmp_path, "init", "--domain", "code")
38 assert r.exit_code == 0, r.output
39 return tmp_path
40
41
42 def _write(repo: pathlib.Path, rel: str, content: str) -> None:
43 (repo / rel).write_text(textwrap.dedent(content))
44
45
46 def _commit(repo: pathlib.Path, msg: str) -> None:
47 r1 = _run(repo, "code", "add", ".")
48 assert r1.exit_code == 0, r1.output
49 r2 = _run(repo, "commit", "-m", msg)
50 assert r2.exit_code == 0, r2.output
51
52
53 class TestSymlogFastPathIsActuallyUsed:
54 """Proves the symlog journal is the mechanism, not just that the final
55 result happens to be correct (the pre-existing BFS path already produces
56 correct results on its own, so output-only assertions can't distinguish
57 the two paths -- these tests break the BFS path and confirm blame still
58 works, or confirm the BFS entry point is never even called)."""
59
60 def test_blame_still_works_when_bfs_walk_is_broken(
61 self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch,
62 ) -> None:
63 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
64 _commit(repo, "add compute_total")
65 _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n")
66 _commit(repo, "double it")
67
68 import muse.cli.commands.blame as blame_mod
69
70 def _broken_bfs(*args: object, **kwargs: object) -> None:
71 raise AssertionError("walk_commits_bfs must not be called on the symlog fast path")
72
73 monkeypatch.setattr(blame_mod, "walk_commits_bfs", _broken_bfs)
74
75 result = _run(repo, "code", "blame", "billing.py::compute_total", "--json")
76 assert result.exit_code == 0, result.output
77 data = json.loads(result.output)
78 kinds = [e["event"] for e in data["events"]]
79 assert "created" in kinds
80 assert "modified" in kinds
81
82 def test_from_ref_falls_back_to_bfs_walk(
83 self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch,
84 ) -> None:
85 """--from forces the full BFS scan -- the journal has no concept of
86 'as reachable from an older ref' and must not be used here."""
87 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
88 _commit(repo, "add compute_total")
89 _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n")
90 _commit(repo, "double it")
91
92 import muse.cli.commands.blame as blame_mod
93
94 calls: list[bool] = []
95 real_bfs = blame_mod.walk_commits_bfs
96
97 def _spy_bfs(*args: object, **kwargs: object):
98 calls.append(True)
99 return real_bfs(*args, **kwargs)
100
101 monkeypatch.setattr(blame_mod, "walk_commits_bfs", _spy_bfs)
102
103 result = _run(repo, "code", "blame", "billing.py::compute_total", "--from", "HEAD~1", "--json")
104 assert result.exit_code == 0, result.output
105 assert calls, "walk_commits_bfs should have been called for --from"
106
107 def test_kind_filter_for_unsupported_kind_falls_back_to_bfs(
108 self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch,
109 ) -> None:
110 """A --kind moved/signature query can never be answered from the
111 journal (it doesn't track those categories) -- must fall back."""
112 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
113 _commit(repo, "add compute_total")
114
115 import muse.cli.commands.blame as blame_mod
116
117 calls: list[bool] = []
118 real_bfs = blame_mod.walk_commits_bfs
119
120 def _spy_bfs(*args: object, **kwargs: object):
121 calls.append(True)
122 return real_bfs(*args, **kwargs)
123
124 monkeypatch.setattr(blame_mod, "walk_commits_bfs", _spy_bfs)
125
126 result = _run(repo, "code", "blame", "billing.py::compute_total", "--kind", "signature", "--json")
127 assert result.exit_code == 0, result.output
128 assert calls, "walk_commits_bfs should have been called for --kind signature"
129
130
131 class TestEventsFromSymlogDirect:
132 """Direct unit tests against _events_from_symlog() -- exercises the new
133 function in isolation rather than through blame's path-selection logic."""
134
135 def test_returns_none_when_no_journal_exists(self, repo: pathlib.Path) -> None:
136 from muse.cli.commands.blame import _events_from_symlog
137
138 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
139 _commit(repo, "add compute_total")
140
141 result = _events_from_symlog(repo, "billing.py::nonexistent", 500)
142 assert result is None
143
144 def test_builds_created_and_modified_events(self, repo: pathlib.Path) -> None:
145 from muse.cli.commands.blame import _events_from_symlog
146
147 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
148 _commit(repo, "add compute_total")
149 _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n")
150 _commit(repo, "double it")
151
152 events = _events_from_symlog(repo, "billing.py::compute_total", 500)
153 assert events is not None
154 kinds = [e.kind for e in events]
155 assert kinds == ["modified", "created"] # newest-first
156 assert events[0].commit.message == "double it"
157
158 def test_deleted_symbol(self, repo: pathlib.Path) -> None:
159 from muse.cli.commands.blame import _events_from_symlog
160
161 _write(repo, "billing.py", (
162 "def compute_total(items):\n return sum(items)\n\n"
163 "def apply_discount(total, pct):\n return total * (1 - pct)\n"
164 ))
165 _commit(repo, "add two functions")
166 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
167 _commit(repo, "remove apply_discount")
168
169 events = _events_from_symlog(repo, "billing.py::apply_discount", 500)
170 assert events is not None
171 assert events[0].kind == "deleted"
172
173 def test_rename_chain_crosses_address_boundary(self, repo: pathlib.Path) -> None:
174 from muse.cli.commands.blame import _events_from_symlog
175
176 _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n")
177 _commit(repo, "add compute_total")
178 _write(repo, "billing.py", "def compute_invoice_total(items):\n return sum(items)\n")
179 _commit(repo, "rename to compute_invoice_total")
180
181 events = _events_from_symlog(repo, "billing.py::compute_invoice_total", 500)
182 assert events is not None
183 kinds = [e.kind for e in events]
184 assert "created" in kinds
185 # The creation event must be attributed to the OLD address, since
186 # that's what the symbol was called at the time it was created.
187 created_event = next(e for e in events if e.kind == "created")
188 assert created_event.address == "billing.py::compute_total"
File History 1 commit
sha256:a5f8a020b53ddab68f231cf27489fe865525f85ee924133034262d39b332076e fix(clone,pull): never claim success when a snapshot hash m… Sonnet 5 patch 10 minutes ago