gabriel / muse public
test_plumbing_name_rev.py python
686 lines 25.3 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
1 """Tests for muse plumbing name-rev.
2
3 Coverage tiers
4 --------------
5 Unit — _build_name_map (flat, hierarchical, symlink skip, bad ref ID,
6 branch_pattern filter, max_walk ceiling),
7 _resolve_prefix (exact, short prefix, ambiguous, no-match),
8 _NameRevEntry schema
9 Integration — tip commit, parent chain, multiple IDs, undefined commit,
10 text output, --name-only, --undefined string,
11 --branches filter, --stdin, short-prefix resolution,
12 --max-walk, --json shorthand, ambiguous prefix
13 Security — ANSI in branch name sanitized, non-hex input rejected,
14 error output to stderr (format, no-args, max-walk, non-hex),
15 no traceback, symlinks skipped, --undefined value sanitized
16 Stress — 50-commit chain, 10-branch repo, 200 sequential calls,
17 stdin with 50 commit IDs
18 """
19
20 from __future__ import annotations
21
22 type _CommitInfoMap = dict[str, tuple[str, int]]
23
24 import datetime
25 import hashlib
26 import io
27 import json
28 import pathlib
29
30 from tests.cli_test_helper import CliRunner, InvokeResult
31
32 from muse.cli.commands.plumbing.name_rev import (
33 _NameRevEntry,
34 _build_name_map,
35 _resolve_prefix,
36 )
37 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
38 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
39 from muse.core._types import Manifest
40
41 cli = None # argparse-based CLI; CliRunner ignores this arg
42 runner = CliRunner()
43
44
45 # ---------------------------------------------------------------------------
46 # Helpers
47 # ---------------------------------------------------------------------------
48
49
50 def _sha(tag: str) -> str:
51 return hashlib.sha256(tag.encode()).hexdigest()
52
53
54 def _init_repo(path: pathlib.Path) -> pathlib.Path:
55 muse = path / ".muse"
56 (muse / "commits").mkdir(parents=True)
57 (muse / "snapshots").mkdir(parents=True)
58 (muse / "objects").mkdir(parents=True)
59 (muse / "refs" / "heads").mkdir(parents=True)
60 (muse / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
61 (muse / "repo.json").write_text(
62 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
63 )
64 return path
65
66
67 def _env(repo: pathlib.Path) -> Manifest:
68 return {"MUSE_REPO_ROOT": str(repo)}
69
70
71 def _snap(repo: pathlib.Path, tag: str) -> str:
72 sid = compute_snapshot_id({})
73 write_snapshot(
74 repo,
75 SnapshotRecord(
76 snapshot_id=sid,
77 manifest={},
78 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
79 ),
80 )
81 return sid
82
83
84 def _commit(
85 repo: pathlib.Path,
86 tag: str,
87 branch: str = "main",
88 parent: str | None = None,
89 ) -> str:
90 sid = _snap(repo, tag)
91 committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
92 parent_ids: list[str] = [parent] if parent else []
93 cid = compute_commit_id(parent_ids, sid, tag, committed_at.isoformat())
94 write_commit(
95 repo,
96 CommitRecord(
97 commit_id=cid,
98 repo_id="test-repo",
99 branch=branch,
100 snapshot_id=sid,
101 message=tag,
102 committed_at=committed_at,
103 author="tester",
104 parent_commit_id=parent,
105 parent2_commit_id=None,
106 ),
107 )
108 ref_path = repo / ".muse" / "refs" / "heads" / branch
109 ref_path.parent.mkdir(parents=True, exist_ok=True)
110 ref_path.write_text(cid, encoding="utf-8")
111 return cid
112
113
114 # Two messages whose content-addressed commit IDs share the 4-char prefix "9f7c".
115 # Verified: compute_commit_id([], compute_snapshot_id({}), msg, "2026-01-01T00:00:00+00:00")
116 _AMBIG_MSG_1 = "commit-search-165" # -> 9f7cc16c...
117 _AMBIG_MSG_2 = "commit-search-106" # -> 9f7c932b...
118 _AMBIG_PREFIX = "9f7c"
119
120
121 def _nr(repo: pathlib.Path, *args: str, stdin: str | None = None) -> InvokeResult:
122 return runner.invoke(
123 cli,
124 ["name-rev", *args],
125 env=_env(repo),
126 input=stdin,
127 )
128
129
130 # ---------------------------------------------------------------------------
131 # Unit — _NameRevEntry schema
132 # ---------------------------------------------------------------------------
133
134
135 class TestNameRevEntrySchema:
136 def test_required_fields(self) -> None:
137 keys = _NameRevEntry.__annotations__
138 for f in ("commit_id", "input", "name", "branch", "distance", "undefined", "ambiguous"):
139 assert f in keys
140
141 def test_input_field_present(self) -> None:
142 """New 'input' field tracks the original caller-supplied value."""
143 assert "input" in _NameRevEntry.__annotations__
144
145 def test_ambiguous_field_present(self) -> None:
146 assert "ambiguous" in _NameRevEntry.__annotations__
147
148
149 # ---------------------------------------------------------------------------
150 # Unit — _resolve_prefix
151 # ---------------------------------------------------------------------------
152
153
154 class TestResolvePrefix:
155 def _map(self) -> _CommitInfoMap:
156 return {
157 "abcd1234" + "0" * 56: ("main", 0),
158 "abcd5678" + "0" * 56: ("dev", 1),
159 "ffff0000" + "0" * 56: ("feat", 2),
160 }
161
162 def test_exact_match(self) -> None:
163 m = self._map()
164 k = "abcd1234" + "0" * 56
165 full_id, ambiguous = _resolve_prefix(k, m)
166 assert full_id == k
167 assert not ambiguous
168
169 def test_short_prefix_unique(self) -> None:
170 m = self._map()
171 full_id, ambiguous = _resolve_prefix("abcd1234", m)
172 assert full_id == "abcd1234" + "0" * 56
173 assert not ambiguous
174
175 def test_short_prefix_ambiguous(self) -> None:
176 m = self._map()
177 # "abcd" matches both "abcd1234..." and "abcd5678..."
178 full_id, ambiguous = _resolve_prefix("abcd", m)
179 assert full_id is None
180 assert ambiguous
181
182 def test_no_match_returns_none(self) -> None:
183 m = self._map()
184 full_id, ambiguous = _resolve_prefix("deadbeef", m)
185 assert full_id is None
186 assert not ambiguous
187
188 def test_empty_map(self) -> None:
189 full_id, ambiguous = _resolve_prefix("abc123", {})
190 assert full_id is None
191 assert not ambiguous
192
193
194 # ---------------------------------------------------------------------------
195 # Unit — _build_name_map
196 # ---------------------------------------------------------------------------
197
198
199 class TestBuildNameMap:
200 def test_empty_heads_dir(self, tmp_path: pathlib.Path) -> None:
201 _init_repo(tmp_path)
202 assert _build_name_map(tmp_path, set()) == {}
203
204 def test_tip_commit_distance_zero(self, tmp_path: pathlib.Path) -> None:
205 _init_repo(tmp_path)
206 cid = _commit(tmp_path, "c1")
207 m = _build_name_map(tmp_path, {cid})
208 assert cid in m
209 assert m[cid] == ("main", 0)
210
211 def test_parent_commit_distance_one(self, tmp_path: pathlib.Path) -> None:
212 _init_repo(tmp_path)
213 c1 = _commit(tmp_path, "c1")
214 c2 = _commit(tmp_path, "c2", parent=c1)
215 m = _build_name_map(tmp_path, {c1, c2})
216 assert m[c1][1] == 1 # distance from tip (c2)
217 assert m[c2][1] == 0 # tip itself
218
219 def test_hierarchical_branch_discovered(self, tmp_path: pathlib.Path) -> None:
220 _init_repo(tmp_path)
221 _commit(tmp_path, "main-c", "main")
222 cid = _commit(tmp_path, "feat-c", "feat/my-thing")
223 m = _build_name_map(tmp_path, {cid})
224 assert cid in m
225 assert m[cid][0] == "feat/my-thing"
226
227 def test_symlink_ref_skipped(self, tmp_path: pathlib.Path) -> None:
228 _init_repo(tmp_path)
229 cid = _commit(tmp_path, "c", "main")
230 real = tmp_path / ".muse" / "refs" / "heads" / "main"
231 link = tmp_path / ".muse" / "refs" / "heads" / "sym"
232 link.symlink_to(real)
233 m = _build_name_map(tmp_path, {cid})
234 # The commit should be reachable via main, not via the symlink.
235 assert cid in m
236 assert m[cid][0] == "main" # not "sym"
237
238 def test_invalid_ref_id_skipped(self, tmp_path: pathlib.Path) -> None:
239 _init_repo(tmp_path)
240 cid = _commit(tmp_path, "c", "main")
241 bad = tmp_path / ".muse" / "refs" / "heads" / "bad"
242 bad.write_text("not-a-sha\n", encoding="utf-8")
243 m = _build_name_map(tmp_path, {cid})
244 assert cid in m # main still seeded
245
246 def test_branch_pattern_filter(self, tmp_path: pathlib.Path) -> None:
247 _init_repo(tmp_path)
248 c_main = _commit(tmp_path, "c-main", "main")
249 c_feat = _commit(tmp_path, "c-feat", "feat/x")
250 m = _build_name_map(tmp_path, {c_main, c_feat}, branch_pattern="main")
251 # feat/x commit should be unreachable (only main seeded)
252 assert c_main in m
253 assert c_feat not in m
254
255 def test_max_walk_ceiling(self, tmp_path: pathlib.Path) -> None:
256 """BFS stops after max_walk steps; deep commits may remain unmapped."""
257 _init_repo(tmp_path)
258 parent: str | None = None
259 first: str | None = None
260 for i in range(10):
261 cid = _commit(tmp_path, f"c{i}", parent=parent)
262 if first is None:
263 first = cid
264 parent = cid
265 assert first is not None
266 # max_walk=1 means only the tip is visited; grandparent must be unmapped
267 m = _build_name_map(tmp_path, {first}, max_walk=1)
268 assert first not in m # too deep to reach
269
270 def test_early_exit_when_all_targets_found(self, tmp_path: pathlib.Path) -> None:
271 """When both targets are at tips, BFS should stop without full traversal."""
272 _init_repo(tmp_path)
273 c1 = _commit(tmp_path, "c1", "main")
274 c2 = _commit(tmp_path, "c2", "dev")
275 m = _build_name_map(tmp_path, {c1, c2})
276 assert c1 in m
277 assert c2 in m
278
279
280 # ---------------------------------------------------------------------------
281 # Integration — basic resolution
282 # ---------------------------------------------------------------------------
283
284
285 class TestResolution:
286 def test_tip_commit_distance_zero(self, tmp_path: pathlib.Path) -> None:
287 _init_repo(tmp_path)
288 cid = _commit(tmp_path, "c1")
289 r = _nr(tmp_path, cid)
290 assert r.exit_code == 0
291 entry = json.loads(r.output)["results"][0]
292 assert entry["commit_id"] == cid
293 assert entry["distance"] == 0
294 assert entry["undefined"] is False
295 assert entry["ambiguous"] is False
296 assert entry["name"] == "main"
297
298 def test_parent_commit_named_tilde_one(self, tmp_path: pathlib.Path) -> None:
299 _init_repo(tmp_path)
300 c1 = _commit(tmp_path, "c1")
301 _commit(tmp_path, "c2", parent=c1)
302 r = _nr(tmp_path, c1)
303 entry = json.loads(r.output)["results"][0]
304 assert entry["distance"] == 1
305 assert entry["name"] == "main~1"
306
307 def test_grandparent_named_tilde_two(self, tmp_path: pathlib.Path) -> None:
308 _init_repo(tmp_path)
309 c1 = _commit(tmp_path, "grandparent")
310 c2 = _commit(tmp_path, "parent", parent=c1)
311 _commit(tmp_path, "tip", parent=c2)
312 r = _nr(tmp_path, c1)
313 entry = json.loads(r.output)["results"][0]
314 assert entry["distance"] == 2
315 assert "~2" in entry["name"]
316
317 def test_undefined_commit(self, tmp_path: pathlib.Path) -> None:
318 _init_repo(tmp_path)
319 _commit(tmp_path, "c1")
320 fake_id = "a" * 64
321 r = _nr(tmp_path, fake_id)
322 entry = json.loads(r.output)["results"][0]
323 assert entry["undefined"] is True
324 assert entry["name"] is None
325 assert entry["commit_id"] is None
326
327 def test_multiple_commit_ids(self, tmp_path: pathlib.Path) -> None:
328 _init_repo(tmp_path)
329 c1 = _commit(tmp_path, "c1")
330 c2 = _commit(tmp_path, "c2", parent=c1)
331 r = _nr(tmp_path, c1, c2)
332 data = json.loads(r.output)
333 assert len(data["results"]) == 2
334
335 def test_input_field_preserves_original(self, tmp_path: pathlib.Path) -> None:
336 _init_repo(tmp_path)
337 cid = _commit(tmp_path, "c1")
338 short = cid[:10]
339 r = _nr(tmp_path, short)
340 entry = json.loads(r.output)["results"][0]
341 assert entry["input"] == short
342 assert entry["commit_id"] == cid # resolved to full
343
344
345 # ---------------------------------------------------------------------------
346 # Integration — short prefix resolution
347 # ---------------------------------------------------------------------------
348
349
350 class TestPrefixResolution:
351 def test_short_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
352 _init_repo(tmp_path)
353 cid = _commit(tmp_path, "c1")
354 r = _nr(tmp_path, cid[:8])
355 assert r.exit_code == 0
356 entry = json.loads(r.output)["results"][0]
357 assert entry["commit_id"] == cid
358 assert entry["undefined"] is False
359
360 def test_four_char_prefix_resolves(self, tmp_path: pathlib.Path) -> None:
361 _init_repo(tmp_path)
362 cid = _commit(tmp_path, "c1")
363 r = _nr(tmp_path, cid[:4])
364 assert r.exit_code == 0
365 entry = json.loads(r.output)["results"][0]
366 # Might be undefined if prefix is too ambiguous in the BFS map
367 # but the input field is always preserved
368 assert entry["input"] == cid[:4]
369
370 def test_ambiguous_prefix_marked(self, tmp_path: pathlib.Path) -> None:
371 """Two commits sharing a 4-char prefix → ambiguous result, not undefined."""
372 _init_repo(tmp_path)
373 # _AMBIG_MSG_1 and _AMBIG_MSG_2 produce real commit IDs sharing _AMBIG_PREFIX.
374 cid1 = _commit(tmp_path, _AMBIG_MSG_1, "main")
375 cid2 = _commit(tmp_path, _AMBIG_MSG_2, "dev")
376 assert cid1[:4] == cid2[:4] == _AMBIG_PREFIX, "pre-computed prefix mismatch"
377 r = _nr(tmp_path, _AMBIG_PREFIX)
378 assert r.exit_code == 0
379 entry = json.loads(r.output)["results"][0]
380 assert entry["ambiguous"] is True
381
382 def test_non_hex_input_rejected(self, tmp_path: pathlib.Path) -> None:
383 _init_repo(tmp_path)
384 r = _nr(tmp_path, "not-hex-at-all!")
385 assert r.exit_code != 0
386 assert r.stdout_bytes == b""
387 assert "error" in r.stderr.lower()
388
389
390 # ---------------------------------------------------------------------------
391 # Integration — --branches filter
392 # ---------------------------------------------------------------------------
393
394
395 class TestBranchesFilter:
396 def test_branches_filter_restricts_bfs(self, tmp_path: pathlib.Path) -> None:
397 _init_repo(tmp_path)
398 c_main = _commit(tmp_path, "c-main", "main")
399 c_dev = _commit(tmp_path, "c-dev", "dev")
400 # With --branches main, c_dev should be undefined
401 r = _nr(tmp_path, c_dev, "--branches", "main")
402 assert r.exit_code == 0
403 entry = json.loads(r.output)["results"][0]
404 assert entry["undefined"] is True
405
406 def test_branches_filter_finds_matching(self, tmp_path: pathlib.Path) -> None:
407 _init_repo(tmp_path)
408 c_main = _commit(tmp_path, "c-main", "main")
409 c_dev = _commit(tmp_path, "c-dev", "dev")
410 r = _nr(tmp_path, c_main, "--branches", "main")
411 assert r.exit_code == 0
412 entry = json.loads(r.output)["results"][0]
413 assert entry["undefined"] is False
414 assert entry["branch"] == "main"
415
416 def test_branches_glob_pattern(self, tmp_path: pathlib.Path) -> None:
417 _init_repo(tmp_path)
418 c_feat1 = _commit(tmp_path, "c-feat1", "feat/one")
419 c_feat2 = _commit(tmp_path, "c-feat2", "feat/two")
420 c_main = _commit(tmp_path, "c-main", "main")
421 # Only feat/* seeded; main commit should be undefined
422 r = _nr(tmp_path, c_main, "--branches", "feat/*")
423 entry = json.loads(r.output)["results"][0]
424 assert entry["undefined"] is True
425
426 def test_branches_filter_hierarchical(self, tmp_path: pathlib.Path) -> None:
427 _init_repo(tmp_path)
428 cid = _commit(tmp_path, "c-feat", "feat/my-thing")
429 r = _nr(tmp_path, cid, "--branches", "feat/*")
430 entry = json.loads(r.output)["results"][0]
431 assert entry["undefined"] is False
432 assert entry["branch"] == "feat/my-thing"
433
434
435 # ---------------------------------------------------------------------------
436 # Integration — --stdin
437 # ---------------------------------------------------------------------------
438
439
440 class TestStdinMode:
441 def test_stdin_reads_commit_ids(self, tmp_path: pathlib.Path) -> None:
442 _init_repo(tmp_path)
443 cid = _commit(tmp_path, "c1")
444 r = _nr(tmp_path, "--stdin", stdin=f"{cid}\n")
445 assert r.exit_code == 0
446 data = json.loads(r.output)
447 assert len(data["results"]) == 1
448 assert data["results"][0]["commit_id"] == cid
449
450 def test_stdin_skips_blank_lines(self, tmp_path: pathlib.Path) -> None:
451 _init_repo(tmp_path)
452 cid = _commit(tmp_path, "c1")
453 r = _nr(tmp_path, "--stdin", stdin=f"\n{cid}\n\n")
454 assert r.exit_code == 0
455 assert len(json.loads(r.output)["results"]) == 1
456
457 def test_stdin_skips_comments(self, tmp_path: pathlib.Path) -> None:
458 _init_repo(tmp_path)
459 cid = _commit(tmp_path, "c1")
460 r = _nr(tmp_path, "--stdin", stdin=f"# this is a comment\n{cid}\n")
461 assert r.exit_code == 0
462 assert len(json.loads(r.output)["results"]) == 1
463
464 def test_stdin_combined_with_positional(self, tmp_path: pathlib.Path) -> None:
465 _init_repo(tmp_path)
466 c1 = _commit(tmp_path, "c1", "main")
467 c2 = _commit(tmp_path, "c2", "dev")
468 r = _nr(tmp_path, c1, "--stdin", stdin=f"{c2}\n")
469 assert r.exit_code == 0
470 data = json.loads(r.output)
471 assert len(data["results"]) == 2
472
473 def test_stdin_empty_with_no_positional_errors(self, tmp_path: pathlib.Path) -> None:
474 _init_repo(tmp_path)
475 r = _nr(tmp_path, "--stdin", stdin="")
476 assert r.exit_code != 0
477 assert r.stdout_bytes == b""
478
479
480 # ---------------------------------------------------------------------------
481 # Integration — text output, --name-only, --undefined
482 # ---------------------------------------------------------------------------
483
484
485 class TestTextOutput:
486 def test_text_format_contains_cid(self, tmp_path: pathlib.Path) -> None:
487 _init_repo(tmp_path)
488 cid = _commit(tmp_path, "c1")
489 r = _nr(tmp_path, "--format", "text", cid)
490 assert r.exit_code == 0
491 assert cid in r.output
492
493 def test_name_only_omits_cid(self, tmp_path: pathlib.Path) -> None:
494 _init_repo(tmp_path)
495 cid = _commit(tmp_path, "c1")
496 r = _nr(tmp_path, "--format", "text", "--name-only", cid)
497 assert r.exit_code == 0
498 assert cid not in r.output
499 assert r.output.strip()
500
501 def test_custom_undefined_string(self, tmp_path: pathlib.Path) -> None:
502 _init_repo(tmp_path)
503 _commit(tmp_path, "c1")
504 fake = "b" * 64
505 r = _nr(tmp_path, "--format", "text", "--undefined", "UNKNOWN", fake)
506 assert r.exit_code == 0
507 assert "UNKNOWN" in r.output
508
509 def test_ambiguous_shown_in_text(self, tmp_path: pathlib.Path) -> None:
510 """Ambiguous prefix emits '(ambiguous)' in text output mode."""
511 _init_repo(tmp_path)
512 cid1 = _commit(tmp_path, _AMBIG_MSG_1, "main")
513 cid2 = _commit(tmp_path, _AMBIG_MSG_2, "dev")
514 assert cid1[:4] == cid2[:4] == _AMBIG_PREFIX, "pre-computed prefix mismatch"
515 r = _nr(tmp_path, "--format", "text", _AMBIG_PREFIX)
516 assert r.exit_code == 0
517 assert "ambiguous" in r.output.lower()
518
519
520 # ---------------------------------------------------------------------------
521 # Integration — --max-walk
522 # ---------------------------------------------------------------------------
523
524
525 class TestMaxWalk:
526 def test_max_walk_limits_bfs(self, tmp_path: pathlib.Path) -> None:
527 _init_repo(tmp_path)
528 parent: str | None = None
529 first: str | None = None
530 for i in range(8):
531 cid = _commit(tmp_path, f"c{i}", parent=parent)
532 if first is None:
533 first = cid
534 parent = cid
535 assert first is not None
536 r = _nr(tmp_path, first, "--max-walk", "1")
537 assert r.exit_code == 0
538 entry = json.loads(r.output)["results"][0]
539 # Deep commit unreachable with max_walk=1
540 assert entry["undefined"] is True
541
542 def test_max_walk_zero_errors(self, tmp_path: pathlib.Path) -> None:
543 _init_repo(tmp_path)
544 cid = _commit(tmp_path, "c1")
545 r = _nr(tmp_path, cid, "--max-walk", "0")
546 assert r.exit_code != 0
547 assert r.stdout_bytes == b""
548
549 def test_json_shorthand(self, tmp_path: pathlib.Path) -> None:
550 _init_repo(tmp_path)
551 cid = _commit(tmp_path, "c1")
552 r = _nr(tmp_path, "--json", cid)
553 assert r.exit_code == 0
554 data = json.loads(r.output)
555 assert "results" in data
556
557
558 # ---------------------------------------------------------------------------
559 # Security
560 # ---------------------------------------------------------------------------
561
562
563 class TestSecurity:
564 def test_non_hex_input_rejected(self, tmp_path: pathlib.Path) -> None:
565 _init_repo(tmp_path)
566 r = _nr(tmp_path, "not-hex!")
567 assert r.exit_code != 0
568 assert r.stdout_bytes == b""
569 assert "error" in r.stderr.lower()
570
571 def test_ansi_in_branch_name_sanitized_text(self, tmp_path: pathlib.Path) -> None:
572 """Branch name from ref → sanitize_display applied before printing."""
573 _init_repo(tmp_path)
574 cid = _commit(tmp_path, "c1")
575 # Inject ANSI into the commit message (branch name from BFS result)
576 # We can't easily inject into the branch name; test via undefined output
577 # with an ANSI-containing --undefined value being sanitized
578 r = _nr(tmp_path, "--format", "text", "--undefined", "\x1b[31mred\x1b[0m", "a" * 64)
579 assert r.exit_code == 0
580 assert "\x1b" not in r.output
581
582 def test_format_error_goes_to_stderr(self, tmp_path: pathlib.Path) -> None:
583 _init_repo(tmp_path)
584 cid = _commit(tmp_path, "c1")
585 r = _nr(tmp_path, "--format", "xml", cid)
586 assert r.exit_code != 0
587 assert r.stdout_bytes == b""
588 assert "error" in r.stderr.lower()
589
590 def test_no_args_error_goes_to_stderr(self, tmp_path: pathlib.Path) -> None:
591 _init_repo(tmp_path)
592 r = _nr(tmp_path)
593 assert r.exit_code != 0
594 assert r.stdout_bytes == b""
595
596 def test_no_traceback_on_bad_format(self, tmp_path: pathlib.Path) -> None:
597 _init_repo(tmp_path)
598 cid = _commit(tmp_path, "c1")
599 r = _nr(tmp_path, "--format", "bad", cid)
600 assert "Traceback" not in r.output
601 assert "Traceback" not in r.stderr
602
603 def test_no_traceback_on_non_hex(self, tmp_path: pathlib.Path) -> None:
604 _init_repo(tmp_path)
605 r = _nr(tmp_path, "xyz!!!")
606 assert "Traceback" not in r.output
607 assert "Traceback" not in r.stderr
608
609 def test_symlink_ref_skipped(self, tmp_path: pathlib.Path) -> None:
610 _init_repo(tmp_path)
611 cid = _commit(tmp_path, "c", "main")
612 real = tmp_path / ".muse" / "refs" / "heads" / "main"
613 link = tmp_path / ".muse" / "refs" / "heads" / "sym"
614 link.symlink_to(real)
615 r = _nr(tmp_path, cid)
616 assert r.exit_code == 0
617 entry = json.loads(r.output)["results"][0]
618 assert entry["branch"] == "main" # not "sym"
619
620 def test_no_repo_exits_cleanly(self, tmp_path: pathlib.Path) -> None:
621 r = runner.invoke(
622 cli,
623 ["name-rev", "a" * 64],
624 env={"MUSE_REPO_ROOT": str(tmp_path / "norepo")},
625 )
626 assert r.exit_code != 0
627 assert "Traceback" not in r.output
628 assert "Traceback" not in r.stderr
629
630
631 # ---------------------------------------------------------------------------
632 # Stress
633 # ---------------------------------------------------------------------------
634
635
636 class TestStress:
637 def test_50_commit_chain(self, tmp_path: pathlib.Path) -> None:
638 _init_repo(tmp_path)
639 parent: str | None = None
640 commits: list[str] = []
641 for i in range(50):
642 cid = _commit(tmp_path, f"c{i}", parent=parent)
643 commits.append(cid)
644 parent = cid
645 # Tip should be at distance 0; first at distance 49.
646 r = _nr(tmp_path, commits[-1], commits[0])
647 assert r.exit_code == 0
648 results = json.loads(r.output)["results"]
649 by_id = {e["commit_id"]: e for e in results}
650 assert by_id[commits[-1]]["distance"] == 0
651 assert by_id[commits[0]]["distance"] == 49
652
653 def test_10_branch_repo(self, tmp_path: pathlib.Path) -> None:
654 _init_repo(tmp_path)
655 tip_ids = []
656 for i in range(10):
657 cid = _commit(tmp_path, f"c-branch{i}", f"branch-{i:02d}")
658 tip_ids.append(cid)
659 r = _nr(tmp_path, *tip_ids)
660 assert r.exit_code == 0
661 data = json.loads(r.output)
662 assert len(data["results"]) == 10
663 for entry in data["results"]:
664 assert entry["distance"] == 0
665
666 def test_200_sequential_calls(self, tmp_path: pathlib.Path) -> None:
667 _init_repo(tmp_path)
668 cid = _commit(tmp_path, "c1")
669 for _ in range(200):
670 r = _nr(tmp_path, cid)
671 assert r.exit_code == 0
672 assert json.loads(r.output)["results"][0]["distance"] == 0
673
674 def test_stdin_50_commit_ids(self, tmp_path: pathlib.Path) -> None:
675 _init_repo(tmp_path)
676 parent: str | None = None
677 commits = []
678 for i in range(50):
679 cid = _commit(tmp_path, f"cs{i}", parent=parent)
680 commits.append(cid)
681 parent = cid
682 stdin_input = "\n".join(commits) + "\n"
683 r = _nr(tmp_path, "--stdin", stdin=stdin_input)
684 assert r.exit_code == 0
685 data = json.loads(r.output)
686 assert len(data["results"]) == 50
File History 3 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 26 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 100 days ago