gabriel / muse public
test_name_rev_supercharge.py python
360 lines 13.3 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 135 days ago
1 """Supercharge tests for ``muse name-rev``.
2
3 Coverage tiers
4 --------------
5 - JSON envelope: exit_code and duration_ms present on all resolution outcomes
6 - Error payload: errors go to stdout as JSON in --json mode, no dual stderr prose
7 - Prefix resolution: bare hex short-prefix matches sha256:-prefixed keys in name_map
8 - TypedDicts: _NameRevJson and _NameRevErrorJson with required annotations
9 - Docstring: module docstring covers exit_code and duration_ms
10 - No-prose pollution: JSON stdout is valid on all non-error paths
11 - Stress: 100-commit chain, all entries have exit_code and duration_ms
12 """
13 from __future__ import annotations
14 from collections.abc import Mapping
15
16 import datetime
17 import json
18 import pathlib
19 from typing import get_type_hints
20
21 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
22 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
23 from tests.cli_test_helper import CliRunner
24
25 runner = CliRunner()
26
27 _DT = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
28
29
30 # ---------------------------------------------------------------------------
31 # Helpers
32 # ---------------------------------------------------------------------------
33
34
35 def _init_repo(tmp_path: pathlib.Path) -> pathlib.Path:
36 muse = tmp_path / ".muse"
37 for d in ("commits", "snapshots", "objects", "refs/heads"):
38 (muse / d).mkdir(parents=True, exist_ok=True)
39 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
40 (muse / "repo.json").write_text(
41 json.dumps({"repo_id": "nr-supercharge", "domain": "midi"}), encoding="utf-8"
42 )
43 return tmp_path
44
45
46 def _env(root: pathlib.Path) -> Mapping[str, str]:
47 return {"MUSE_REPO_ROOT": str(root)}
48
49
50 def _commit(
51 root: pathlib.Path,
52 msg: str,
53 branch: str = "main",
54 parent: str | None = None,
55 ) -> str:
56 sid = compute_snapshot_id({})
57 write_snapshot(root, SnapshotRecord(snapshot_id=sid, manifest={}, created_at=_DT))
58 parent_ids = [parent] if parent else []
59 cid = compute_commit_id(
60 repo_id="nr-supercharge",
61 parent_ids=parent_ids,
62 snapshot_id=sid,
63 message=msg,
64 committed_at_iso=_DT.isoformat(),
65 )
66 write_commit(root, CommitRecord(
67 commit_id=cid, repo_id="nr-supercharge", created_on_branch=branch,
68 snapshot_id=sid, message=msg, committed_at=_DT,
69 parent_commit_id=parent,
70 ))
71 ref = root / ".muse" / "refs" / "heads" / branch
72 ref.parent.mkdir(parents=True, exist_ok=True)
73 ref.write_text(cid, encoding="utf-8")
74 return cid
75
76
77 def _nr(root: pathlib.Path, *args: str, stdin: str | None = None):
78 from muse.cli.app import main as cli
79 extra = [] if "--json" in args or "-j" in args else ["--json"]
80 return runner.invoke(cli, ["name-rev", *extra, *args], env=_env(root), input=stdin)
81
82
83 def _hex_prefix(cid: str, n: int = 8) -> str:
84 """Extract n hex chars from a sha256:-prefixed commit ID."""
85 return cid[len("sha256:"):len("sha256:") + n]
86
87
88 # ---------------------------------------------------------------------------
89 # JSON envelope — exit_code
90 # ---------------------------------------------------------------------------
91
92
93 class TestJsonEnvelopeExitCode:
94 def test_found_has_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
95 root = _init_repo(tmp_path)
96 cid = _commit(root, "c1")
97 r = _nr(root, cid)
98 assert r.exit_code == 0
99 d = json.loads(r.output)
100 assert "exit_code" in d, "exit_code missing from found envelope"
101 assert d["exit_code"] == 0
102
103 def test_undefined_result_still_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
104 """undefined entries are a valid outcome — exit_code must still be 0."""
105 root = _init_repo(tmp_path)
106 _commit(root, "c1")
107 fake = "a" * 64
108 r = _nr(root, fake)
109 assert r.exit_code == 0
110 d = json.loads(r.output)
111 assert "exit_code" in d
112 assert d["exit_code"] == 0
113
114 def test_mixed_found_and_undefined_exit_code_zero(self, tmp_path: pathlib.Path) -> None:
115 root = _init_repo(tmp_path)
116 cid = _commit(root, "c1")
117 fake = "b" * 64
118 r = _nr(root, cid, fake)
119 assert r.exit_code == 0
120 d = json.loads(r.output)
121 assert d["exit_code"] == 0
122
123
124 # ---------------------------------------------------------------------------
125 # JSON envelope — duration_ms
126 # ---------------------------------------------------------------------------
127
128
129 class TestJsonEnvelopeDurationMs:
130 def test_found_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
131 root = _init_repo(tmp_path)
132 cid = _commit(root, "c1")
133 r = _nr(root, cid)
134 d = json.loads(r.output)
135 assert "duration_ms" in d, "duration_ms missing from found envelope"
136 assert isinstance(d["duration_ms"], float)
137 assert d["duration_ms"] >= 0.0
138
139 def test_undefined_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
140 root = _init_repo(tmp_path)
141 _commit(root, "c1")
142 r = _nr(root, "a" * 64)
143 d = json.loads(r.output)
144 assert "duration_ms" in d
145
146 def test_branches_filter_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
147 root = _init_repo(tmp_path)
148 cid = _commit(root, "c1", "main")
149 r = _nr(root, cid, "--branches", "main")
150 d = json.loads(r.output)
151 assert "duration_ms" in d
152 assert isinstance(d["duration_ms"], float)
153
154
155 # ---------------------------------------------------------------------------
156 # Error payload — errors route to stdout as JSON in --json mode
157 # ---------------------------------------------------------------------------
158
159
160 class TestErrorPayload:
161 def test_no_inputs_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
162 root = _init_repo(tmp_path)
163 r = _nr(root, "--json")
164 assert r.exit_code != 0
165 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
166 d = json.loads(r.output)
167 assert d["status"] == "error"
168
169 def test_invalid_hex_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
170 root = _init_repo(tmp_path)
171 r = _nr(root, "--json", "not-hex!")
172 assert r.exit_code != 0
173 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
174 d = json.loads(r.output)
175 assert d["status"] == "error"
176
177 def test_bad_max_walk_error_goes_to_stdout(self, tmp_path: pathlib.Path) -> None:
178 root = _init_repo(tmp_path)
179 cid = _commit(root, "c1")
180 r = _nr(root, "--json", cid, "--max-walk", "0")
181 assert r.exit_code != 0
182 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
183 d = json.loads(r.output)
184 assert d["status"] == "error"
185
186 def test_error_payload_has_status_error(self, tmp_path: pathlib.Path) -> None:
187 root = _init_repo(tmp_path)
188 r = _nr(root, "--json", "not-valid!")
189 d = json.loads(r.output)
190 assert d["status"] == "error"
191
192 def test_error_payload_has_exit_code(self, tmp_path: pathlib.Path) -> None:
193 root = _init_repo(tmp_path)
194 r = _nr(root, "--json", "not-valid!")
195 d = json.loads(r.output)
196 assert "exit_code" in d
197 assert d["exit_code"] != 0
198
199 def test_error_payload_has_error_field(self, tmp_path: pathlib.Path) -> None:
200 root = _init_repo(tmp_path)
201 r = _nr(root, "--json")
202 d = json.loads(r.output)
203 assert "error" in d
204 assert d["error"]
205
206 def test_no_emoji_on_stderr_in_json_mode(self, tmp_path: pathlib.Path) -> None:
207 root = _init_repo(tmp_path)
208 r = _nr(root, "--json", "not-valid!")
209 assert "❌" not in r.stderr
210
211
212 # ---------------------------------------------------------------------------
213 # Prefix resolution — bare hex prefix against sha256:-prefixed keys
214 # ---------------------------------------------------------------------------
215
216
217 class TestPrefixResolutionSha256:
218 def test_bare_hex_8char_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
219 """Bare 8-char hex prefix must resolve against sha256:-prefixed name_map keys."""
220 root = _init_repo(tmp_path)
221 cid = _commit(root, "c1")
222 prefix = _hex_prefix(cid, 8) # first 8 hex chars, no sha256: prefix
223 r = _nr(root, prefix)
224 assert r.exit_code == 0
225 entry = json.loads(r.output)["results"][0]
226 assert entry["commit_id"] == cid
227 assert entry["undefined"] is False
228
229 def test_bare_hex_4char_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
230 """4-char bare hex prefix must resolve when unambiguous."""
231 root = _init_repo(tmp_path)
232 cid = _commit(root, "unique-c1-msg")
233 prefix = _hex_prefix(cid, 4)
234 r = _nr(root, prefix)
235 assert r.exit_code == 0
236 entry = json.loads(r.output)["results"][0]
237 # Either resolves to cid or is ambiguous — must not crash or error
238 assert entry["input"] == prefix
239 assert r.exit_code == 0
240
241 def test_sha256_prefixed_short_id_resolves(self, tmp_path: pathlib.Path) -> None:
242 """sha256:-prefixed short IDs (e.g. sha256:abcd1234) must also resolve."""
243 root = _init_repo(tmp_path)
244 cid = _commit(root, "c1")
245 short = cid[:len("sha256:") + 8] # keep sha256: + 8 hex chars
246 r = _nr(root, short)
247 assert r.exit_code == 0
248 entry = json.loads(r.output)["results"][0]
249 assert entry["commit_id"] == cid
250
251 def test_input_field_preserves_bare_hex_prefix(self, tmp_path: pathlib.Path) -> None:
252 """input field echoes the caller's original value, not the resolved full ID."""
253 root = _init_repo(tmp_path)
254 cid = _commit(root, "c1")
255 prefix = _hex_prefix(cid, 8)
256 r = _nr(root, prefix)
257 entry = json.loads(r.output)["results"][0]
258 assert entry["input"] == prefix
259
260
261 # ---------------------------------------------------------------------------
262 # No-prose pollution
263 # ---------------------------------------------------------------------------
264
265
266 class TestNoProsePollution:
267 def test_found_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
268 root = _init_repo(tmp_path)
269 cid = _commit(root, "c1")
270 r = _nr(root, cid)
271 json.loads(r.output) # must not raise
272
273 def test_undefined_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
274 root = _init_repo(tmp_path)
275 _commit(root, "c1")
276 json.loads(_nr(root, "a" * 64).output)
277
278 def test_no_emoji_in_success_json(self, tmp_path: pathlib.Path) -> None:
279 root = _init_repo(tmp_path)
280 cid = _commit(root, "c1")
281 r = _nr(root, cid)
282 assert "✅" not in r.output
283 assert "❌" not in r.output
284
285
286 # ---------------------------------------------------------------------------
287 # TypedDicts
288 # ---------------------------------------------------------------------------
289
290
291 class TestTypedDicts:
292 def test_name_rev_json_typeddict_exists(self) -> None:
293 from muse.cli.commands.name_rev import _NameRevJson
294 assert _NameRevJson is not None
295
296 def test_name_rev_error_json_typeddict_exists(self) -> None:
297 from muse.cli.commands.name_rev import _NameRevErrorJson
298 assert _NameRevErrorJson is not None
299
300 def test_name_rev_json_has_exit_code_annotation(self) -> None:
301 from muse.cli.commands.name_rev import _NameRevJson
302 hints = get_type_hints(_NameRevJson)
303 assert "exit_code" in hints
304
305 def test_name_rev_json_has_duration_ms_annotation(self) -> None:
306 from muse.cli.commands.name_rev import _NameRevJson
307 hints = get_type_hints(_NameRevJson)
308 assert "duration_ms" in hints
309
310 def test_name_rev_error_json_has_required_fields(self) -> None:
311 from muse.cli.commands.name_rev import _NameRevErrorJson
312 hints = get_type_hints(_NameRevErrorJson)
313 for field in ("status", "error", "exit_code"):
314 assert field in hints, f"Missing annotation: {field!r}"
315
316
317 # ---------------------------------------------------------------------------
318 # Docstring coverage
319 # ---------------------------------------------------------------------------
320
321
322 class TestDocstring:
323 def _doc(self) -> str:
324 import muse.cli.commands.name_rev as mod
325 return mod.__doc__ or ""
326
327 def test_docstring_documents_exit_code(self) -> None:
328 assert "exit_code" in self._doc()
329
330 def test_docstring_documents_duration_ms(self) -> None:
331 assert "duration_ms" in self._doc()
332
333
334 # ---------------------------------------------------------------------------
335 # Stress
336 # ---------------------------------------------------------------------------
337
338
339 class TestStress:
340 def test_100_commit_chain_all_have_envelope_fields(self, tmp_path: pathlib.Path) -> None:
341 root = _init_repo(tmp_path)
342 parent: str | None = None
343 commits: list[str] = []
344 for i in range(100):
345 cid = _commit(root, f"c{i:03d}", parent=parent)
346 commits.append(cid)
347 parent = cid
348
349 r = _nr(root, commits[0], commits[49], commits[-1])
350 assert r.exit_code == 0
351 d = json.loads(r.output)
352 assert "exit_code" in d
353 assert "duration_ms" in d
354 assert d["exit_code"] == 0
355 assert isinstance(d["duration_ms"], float)
356 # Tip at distance 0, midpoint at 50, root at 99
357 by_id = {e["commit_id"]: e for e in d["results"]}
358 assert by_id[commits[-1]]["distance"] == 0
359 assert by_id[commits[49]]["distance"] == 50
360 assert by_id[commits[0]]["distance"] == 99
File History 2 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 135 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 141 days ago