gabriel / muse public
test_merge_base_supercharge.py python
381 lines 14.2 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 139 days ago
1 """Supercharge tests for ``muse merge-base``.
2
3 Coverage tiers
4 --------------
5 - JSON envelope: exit_code and duration_ms present on found and not-found outcomes
6 - Error payload: errors go to stdout as JSON in --json mode, no dual stderr prose
7 - Data integrity: symmetry (A,B)==(B,A); merge-commit as input; deep DAG
8 - TypedDicts: _MergeBaseFoundJson and _MergeBaseErrorJson with required annotations
9 - Docstring: module docstring covers exit_code and duration_ms
10 - No-prose pollution: no emoji / prose leaks into JSON stdout
11 - Stress: 100-commit linear chain resolves correctly
12 """
13 from __future__ import annotations
14
15 import datetime
16 import json
17 import pathlib
18 from typing import get_type_hints
19
20 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
21 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
22 from tests.cli_test_helper import CliRunner
23
24 runner = CliRunner()
25
26 _DT = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
27
28
29 # ---------------------------------------------------------------------------
30 # Helpers
31 # ---------------------------------------------------------------------------
32
33
34 def _init_repo(tmp_path: pathlib.Path) -> pathlib.Path:
35 muse = tmp_path / ".muse"
36 for sub in ("objects", "commits", "snapshots", "refs/heads"):
37 (muse / sub).mkdir(parents=True)
38 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
39 (muse / "repo.json").write_text(
40 json.dumps({"repo_id": "supercharge-test", "domain": "code"}),
41 encoding="utf-8",
42 )
43 return tmp_path
44
45
46 def _env(root: pathlib.Path) -> dict[str, str]:
47 return {"MUSE_REPO_ROOT": str(root)}
48
49
50 def _snap(root: pathlib.Path) -> str:
51 sid = compute_snapshot_id({})
52 write_snapshot(root, SnapshotRecord(snapshot_id=sid, manifest={}, created_at=_DT))
53 return sid
54
55
56 def _commit(
57 root: pathlib.Path,
58 msg: str,
59 *,
60 parent: str | None = None,
61 parent2: str | None = None,
62 branch: str = "main",
63 ) -> str:
64 sid = _snap(root)
65 parent_ids = [p for p in [parent, parent2] if p is not None]
66 cid = compute_commit_id(parent_ids, sid, msg, _DT.isoformat())
67 write_commit(root, CommitRecord(
68 commit_id=cid,
69 repo_id="supercharge-test",
70 branch=branch,
71 snapshot_id=sid,
72 message=msg,
73 committed_at=_DT,
74 parent_commit_id=parent,
75 parent2_commit_id=parent2,
76 ))
77 return cid
78
79
80 def _set_branch(root: pathlib.Path, branch: str, cid: str) -> None:
81 ref = root / ".muse" / "refs" / "heads" / branch
82 ref.parent.mkdir(parents=True, exist_ok=True)
83 ref.write_text(cid, encoding="utf-8")
84 (root / ".muse" / "HEAD").write_text(f"ref: refs/heads/{branch}", encoding="utf-8")
85
86
87 def _mb(root: pathlib.Path, *args: str):
88 from muse.cli.app import main as cli
89 return runner.invoke(cli, ["merge-base", *args], env=_env(root))
90
91
92 def _diverged_repo(root: pathlib.Path) -> tuple[str, str, str]:
93 """Create base → left and base → right. Returns (base, left, right)."""
94 base = _commit(root, "base")
95 left = _commit(root, "left", parent=base, branch="left")
96 right = _commit(root, "right", parent=base, branch="right")
97 _set_branch(root, "left", left)
98 _set_branch(root, "right", right)
99 return base, left, right
100
101
102 def _unrelated_repo(root: pathlib.Path) -> tuple[str, str]:
103 """Two commits with no shared history."""
104 c1 = _commit(root, "unrelated-c1")
105 c2 = _commit(root, "unrelated-c2")
106 return c1, c2
107
108
109 # ---------------------------------------------------------------------------
110 # JSON envelope — exit_code
111 # ---------------------------------------------------------------------------
112
113
114 class TestJsonEnvelopeExitCode:
115 """exit_code is present and correct on all merge-base outcomes."""
116
117 def test_found_has_exit_code(self, tmp_path: pathlib.Path) -> None:
118 root = _init_repo(tmp_path)
119 base, left, right = _diverged_repo(root)
120 r = _mb(root, left, right)
121 assert r.exit_code == 0
122 d = json.loads(r.output)
123 assert "exit_code" in d, "exit_code missing when merge base is found"
124 assert d["exit_code"] == 0
125
126 def test_not_found_has_exit_code(self, tmp_path: pathlib.Path) -> None:
127 root = _init_repo(tmp_path)
128 c1, c2 = _unrelated_repo(root)
129 r = _mb(root, c1, c2)
130 assert r.exit_code == 0
131 d = json.loads(r.output)
132 assert "exit_code" in d, "exit_code missing when no common ancestor"
133 assert d["exit_code"] == 0
134
135 def test_same_commit_has_exit_code(self, tmp_path: pathlib.Path) -> None:
136 root = _init_repo(tmp_path)
137 cid = _commit(root, "solo")
138 r = _mb(root, cid, cid)
139 d = json.loads(r.output)
140 assert "exit_code" in d
141 assert d["exit_code"] == 0
142
143
144 # ---------------------------------------------------------------------------
145 # JSON envelope — duration_ms
146 # ---------------------------------------------------------------------------
147
148
149 class TestJsonEnvelopeDurationMs:
150 """duration_ms is present and non-negative on all merge-base outcomes."""
151
152 def test_found_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
153 root = _init_repo(tmp_path)
154 base, left, right = _diverged_repo(root)
155 r = _mb(root, left, right)
156 d = json.loads(r.output)
157 assert "duration_ms" in d, "duration_ms missing when merge base is found"
158 assert isinstance(d["duration_ms"], float)
159 assert d["duration_ms"] >= 0.0
160
161 def test_not_found_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
162 root = _init_repo(tmp_path)
163 c1, c2 = _unrelated_repo(root)
164 r = _mb(root, c1, c2)
165 d = json.loads(r.output)
166 assert "duration_ms" in d, "duration_ms missing when no common ancestor"
167 assert isinstance(d["duration_ms"], float)
168
169 def test_same_commit_has_duration_ms(self, tmp_path: pathlib.Path) -> None:
170 root = _init_repo(tmp_path)
171 cid = _commit(root, "dur-solo")
172 r = _mb(root, cid, cid)
173 d = json.loads(r.output)
174 assert "duration_ms" in d
175 assert isinstance(d["duration_ms"], float)
176
177
178 # ---------------------------------------------------------------------------
179 # Error payload — errors route to stdout as JSON in --json mode
180 # ---------------------------------------------------------------------------
181
182
183 class TestErrorPayload:
184 """In --json mode, all errors appear on stdout as JSON — no stderr prose."""
185
186 def test_bad_ref_error_is_json_on_stdout(self, tmp_path: pathlib.Path) -> None:
187 root = _init_repo(tmp_path)
188 r = _mb(root, "no-such-branch", "also-missing")
189 assert r.exit_code != 0
190 # Error JSON must go to stdout: stderr should be empty.
191 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
192 d = json.loads(r.output)
193 assert d["status"] == "error"
194
195 def test_bad_ref_error_has_status_error(self, tmp_path: pathlib.Path) -> None:
196 root = _init_repo(tmp_path)
197 r = _mb(root, "ghost", "phantom")
198 d = json.loads(r.output)
199 assert d["status"] == "error"
200
201 def test_bad_ref_error_has_exit_code(self, tmp_path: pathlib.Path) -> None:
202 root = _init_repo(tmp_path)
203 r = _mb(root, "ghost", "phantom")
204 d = json.loads(r.output)
205 assert "exit_code" in d
206 assert d["exit_code"] != 0
207
208 def test_bad_ref_error_has_error_field(self, tmp_path: pathlib.Path) -> None:
209 root = _init_repo(tmp_path)
210 r = _mb(root, "ghost", "phantom")
211 d = json.loads(r.output)
212 assert "error" in d
213 assert d["error"] # non-empty message
214
215 def test_no_duplicate_stderr_prose(self, tmp_path: pathlib.Path) -> None:
216 """In --json mode, errors must not also print ❌ prose to stderr."""
217 root = _init_repo(tmp_path)
218 r = _mb(root, "no-such", "branch")
219 assert "❌" not in r.stderr
220
221 def test_second_bad_ref_error_is_json_on_stdout(self, tmp_path: pathlib.Path) -> None:
222 """Error on second ref (commit_b) also goes to stdout, not stderr."""
223 root = _init_repo(tmp_path)
224 cid = _commit(root, "real-commit")
225 r = _mb(root, cid, "nonexistent")
226 assert r.exit_code != 0
227 assert not r.stderr.strip(), f"unexpected stderr: {r.stderr!r}"
228 d = json.loads(r.output)
229 assert d["status"] == "error"
230
231
232 # ---------------------------------------------------------------------------
233 # Data integrity
234 # ---------------------------------------------------------------------------
235
236
237 class TestDataIntegrity:
238 """Correctness properties that must hold across all inputs."""
239
240 def test_symmetry(self, tmp_path: pathlib.Path) -> None:
241 """merge-base(A, B) == merge-base(B, A)."""
242 root = _init_repo(tmp_path)
243 base, left, right = _diverged_repo(root)
244 r_ab = json.loads(_mb(root, left, right).output)
245 r_ba = json.loads(_mb(root, right, left).output)
246 assert r_ab["merge_base"] == r_ba["merge_base"]
247
248 def test_merge_commit_as_input(self, tmp_path: pathlib.Path) -> None:
249 """A merge commit (two parents) is handled correctly as input."""
250 root = _init_repo(tmp_path)
251 base, left, right = _diverged_repo(root)
252 # Simulate a merge commit that has both left and right as parents
253 merge_commit = _commit(root, "merge", parent=left, parent2=right, branch="main")
254 _set_branch(root, "main", merge_commit)
255 # merge-base of the merge commit with right should be right (right is ancestor of merge)
256 r = _mb(root, merge_commit, right)
257 assert r.exit_code == 0
258 d = json.loads(r.output)
259 assert d["merge_base"] == right
260
261 def test_found_merge_base_is_correct(self, tmp_path: pathlib.Path) -> None:
262 """merge_base field equals the actual LCA commit ID."""
263 root = _init_repo(tmp_path)
264 base, left, right = _diverged_repo(root)
265 d = json.loads(_mb(root, left, right).output)
266 assert d["merge_base"] == base
267
268 def test_commit_a_and_b_echoed_correctly(self, tmp_path: pathlib.Path) -> None:
269 """commit_a and commit_b in the response match what was requested."""
270 root = _init_repo(tmp_path)
271 base, left, right = _diverged_repo(root)
272 d = json.loads(_mb(root, left, right).output)
273 assert d["commit_a"] == left
274 assert d["commit_b"] == right
275
276 def test_not_found_merge_base_is_null(self, tmp_path: pathlib.Path) -> None:
277 """merge_base is null (not absent) when no common ancestor."""
278 root = _init_repo(tmp_path)
279 c1, c2 = _unrelated_repo(root)
280 d = json.loads(_mb(root, c1, c2).output)
281 assert "merge_base" in d
282 assert d["merge_base"] is None
283
284
285 # ---------------------------------------------------------------------------
286 # No-prose pollution
287 # ---------------------------------------------------------------------------
288
289
290 class TestNoProsePollution:
291 """JSON stdout must be valid, parseable JSON on all non-error paths."""
292
293 def test_found_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
294 root = _init_repo(tmp_path)
295 base, left, right = _diverged_repo(root)
296 r = _mb(root, left, right)
297 json.loads(r.output) # must not raise
298
299 def test_not_found_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None:
300 root = _init_repo(tmp_path)
301 c1, c2 = _unrelated_repo(root)
302 json.loads(_mb(root, c1, c2).output) # must not raise
303
304 def test_no_emoji_in_found_json(self, tmp_path: pathlib.Path) -> None:
305 root = _init_repo(tmp_path)
306 base, left, right = _diverged_repo(root)
307 r = _mb(root, left, right)
308 assert "✅" not in r.output
309 assert "❌" not in r.output
310
311
312 # ---------------------------------------------------------------------------
313 # TypedDicts
314 # ---------------------------------------------------------------------------
315
316
317 class TestTypedDicts:
318 def test_merge_base_found_json_typeddict_exists(self) -> None:
319 from muse.cli.commands.merge_base import _MergeBaseFoundJson
320 assert _MergeBaseFoundJson is not None
321
322 def test_merge_base_error_json_typeddict_exists(self) -> None:
323 from muse.cli.commands.merge_base import _MergeBaseErrorJson
324 assert _MergeBaseErrorJson is not None
325
326 def test_merge_base_found_json_has_exit_code_annotation(self) -> None:
327 from muse.cli.commands.merge_base import _MergeBaseFoundJson
328 hints = get_type_hints(_MergeBaseFoundJson)
329 assert "exit_code" in hints
330
331 def test_merge_base_found_json_has_duration_ms_annotation(self) -> None:
332 from muse.cli.commands.merge_base import _MergeBaseFoundJson
333 hints = get_type_hints(_MergeBaseFoundJson)
334 assert "duration_ms" in hints
335
336 def test_merge_base_error_json_has_required_fields(self) -> None:
337 from muse.cli.commands.merge_base import _MergeBaseErrorJson
338 hints = get_type_hints(_MergeBaseErrorJson)
339 for field in ("status", "error", "exit_code"):
340 assert field in hints, f"Missing annotation: {field!r}"
341
342
343 # ---------------------------------------------------------------------------
344 # Docstring coverage
345 # ---------------------------------------------------------------------------
346
347
348 class TestDocstring:
349 def _doc(self) -> str:
350 import muse.cli.commands.merge_base as mod
351 return mod.__doc__ or ""
352
353 def test_docstring_documents_exit_code(self) -> None:
354 assert "exit_code" in self._doc()
355
356 def test_docstring_documents_duration_ms(self) -> None:
357 assert "duration_ms" in self._doc()
358
359
360 # ---------------------------------------------------------------------------
361 # Stress
362 # ---------------------------------------------------------------------------
363
364
365 class TestStress:
366 def test_100_commit_linear_chain(self, tmp_path: pathlib.Path) -> None:
367 """100-commit chain: merge-base of tip and midpoint is the midpoint."""
368 root = _init_repo(tmp_path)
369 commits: list[str] = []
370 for i in range(100):
371 parent = commits[-1] if commits else None
372 cid = _commit(root, f"chain-{i:03d}", parent=parent)
373 commits.append(cid)
374 mid = commits[49]
375 tip = commits[-1]
376 r = _mb(root, tip, mid)
377 assert r.exit_code == 0
378 d = json.loads(r.output)
379 assert d["merge_base"] == mid
380 assert "duration_ms" in d
381 assert "exit_code" in d
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 139 days ago