gabriel / muse public
test_cmd_commit_graph_enhancements.py python
224 lines 7.6 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
1 """Tests for the new commit-graph flags: --count, --first-parent, --ancestry-path."""
2
3 from __future__ import annotations
4
5 import datetime
6 import json
7 import pathlib
8
9 import pytest
10 from tests.cli_test_helper import CliRunner
11
12 cli = None # argparse migration — CliRunner ignores this arg
13 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
14 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
15 from muse.core._types import Manifest
16
17 runner = CliRunner()
18
19 _DT = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
20
21
22
23 def _init_repo(path: pathlib.Path) -> pathlib.Path:
24 muse = path / ".muse"
25 (muse / "commits").mkdir(parents=True)
26 (muse / "snapshots").mkdir(parents=True)
27 (muse / "objects").mkdir(parents=True)
28 (muse / "refs" / "heads").mkdir(parents=True)
29 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
30 (muse / "repo.json").write_text(
31 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
32 )
33 return path
34
35
36 def _env(repo: pathlib.Path) -> Manifest:
37 return {"MUSE_REPO_ROOT": str(repo)}
38
39
40 def _snap(repo: pathlib.Path, tag: str) -> str:
41 """Write an empty-manifest snapshot; return its content-addressed ID."""
42 sid = compute_snapshot_id({})
43 write_snapshot(
44 repo,
45 SnapshotRecord(
46 snapshot_id=sid,
47 manifest={},
48 created_at=_DT,
49 ),
50 )
51 return sid
52
53
54 def _commit(
55 repo: pathlib.Path,
56 tag: str,
57 branch: str = "main",
58 parent: str | None = None,
59 parent2: str | None = None,
60 ) -> str:
61 """Write a commit using *tag* as message (ensures uniqueness); return real commit ID."""
62 sid = _snap(repo, tag)
63 parent_ids = [p for p in [parent, parent2] if p is not None]
64 cid = compute_commit_id(
65 repo_id="test-repo",
66 parent_ids=parent_ids,
67 snapshot_id=sid,
68 message=tag,
69 committed_at_iso=_DT.isoformat(),
70 author="tester",
71 )
72 write_commit(
73 repo,
74 CommitRecord(
75 commit_id=cid,
76 repo_id="test-repo",
77 created_on_branch=branch,
78 snapshot_id=sid,
79 message=tag,
80 committed_at=_DT,
81 author="tester",
82 parent_commit_id=parent,
83 parent2_commit_id=parent2,
84 ),
85 )
86 ref_path = repo / ".muse" / "refs" / "heads" / branch
87 ref_path.parent.mkdir(parents=True, exist_ok=True)
88 ref_path.write_text(cid, encoding="utf-8")
89 return cid
90
91
92 class TestCommitGraphCount:
93 def test_count_returns_integer(self, tmp_path: pathlib.Path) -> None:
94 _init_repo(tmp_path)
95 c1 = _commit(tmp_path, "a", parent=None)
96 _commit(tmp_path, "b", parent=c1)
97 result = runner.invoke(cli, ["commit-graph", "--count"], env=_env(tmp_path))
98 assert result.exit_code == 0
99 data = json.loads(result.output)
100 assert "count" in data
101 assert data["count"] == 2
102 assert "commits" not in data # full node list suppressed
103
104 def test_count_no_commits_returns_error(self, tmp_path: pathlib.Path) -> None:
105 _init_repo(tmp_path)
106 result = runner.invoke(cli, ["commit-graph", "--count"], env=_env(tmp_path))
107 assert result.exit_code != 0
108
109 def test_count_with_stop_at(self, tmp_path: pathlib.Path) -> None:
110 _init_repo(tmp_path)
111 base = _commit(tmp_path, "base", parent=None)
112 _commit(tmp_path, "feature", parent=base)
113 result = runner.invoke(
114 cli, ["commit-graph", "--stop-at", base, "--count"], env=_env(tmp_path)
115 )
116 assert result.exit_code == 0
117 data = json.loads(result.output)
118 assert data["count"] == 1 # only "feature", base excluded
119
120 def test_count_short_flag(self, tmp_path: pathlib.Path) -> None:
121 _init_repo(tmp_path)
122 _commit(tmp_path, "a")
123 result = runner.invoke(cli, ["commit-graph", "-c"], env=_env(tmp_path))
124 assert result.exit_code == 0
125 data = json.loads(result.output)
126 assert "count" in data
127
128
129 class TestCommitGraphFirstParent:
130 def test_first_parent_only(self, tmp_path: pathlib.Path) -> None:
131 _init_repo(tmp_path)
132 c1 = _commit(tmp_path, "c1", parent=None)
133 c2 = _commit(tmp_path, "c2", parent=c1)
134 result = runner.invoke(
135 cli, ["commit-graph", "--first-parent", "--count"], env=_env(tmp_path)
136 )
137 assert result.exit_code == 0
138 data = json.loads(result.output)
139 assert data["count"] == 2
140
141 def test_first_parent_excludes_merge_parent(self, tmp_path: pathlib.Path) -> None:
142 """With --first-parent, second parents of merges are not followed."""
143 _init_repo(tmp_path)
144 c1 = _commit(tmp_path, "c1", parent=None)
145 c2 = _commit(tmp_path, "branch_tip", "feat", parent=c1)
146 # merge commit with c1 as first parent, c2 as second parent
147 _commit(tmp_path, "merge", parent=c1, parent2=c2)
148 result = runner.invoke(
149 cli, ["commit-graph", "--first-parent", "--count"], env=_env(tmp_path)
150 )
151 assert result.exit_code == 0
152 data = json.loads(result.output)
153 # Should NOT follow c2 branch; only main chain: merge → c1
154 assert data["count"] == 2 # merge + c1
155
156 def test_first_parent_short_flag(self, tmp_path: pathlib.Path) -> None:
157 _init_repo(tmp_path)
158 _commit(tmp_path, "c1")
159 result = runner.invoke(
160 cli, ["commit-graph", "-1", "--count"], env=_env(tmp_path)
161 )
162 assert result.exit_code == 0
163
164
165 class TestCommitGraphAncestryPath:
166 def test_ancestry_path_requires_stop_at(self, tmp_path: pathlib.Path) -> None:
167 _init_repo(tmp_path)
168 _commit(tmp_path, "c1")
169 result = runner.invoke(
170 cli, ["commit-graph", "--ancestry-path"], env=_env(tmp_path)
171 )
172 assert result.exit_code != 0
173 data = json.loads(result.output)
174 assert "error" in data
175
176 def test_ancestry_path_with_stop_at_runs(self, tmp_path: pathlib.Path) -> None:
177 _init_repo(tmp_path)
178 base = _commit(tmp_path, "base", parent=None)
179 _commit(tmp_path, "feature", parent=base)
180 result = runner.invoke(
181 cli,
182 ["commit-graph", "--json", "--stop-at", base, "--ancestry-path"],
183 env=_env(tmp_path),
184 )
185 assert result.exit_code == 0
186 data = json.loads(result.output)
187 assert "commits" in data
188
189 def test_ancestry_path_short_flag(self, tmp_path: pathlib.Path) -> None:
190 _init_repo(tmp_path)
191 base = _commit(tmp_path, "base", parent=None)
192 _commit(tmp_path, "next", parent=base)
193 result = runner.invoke(
194 cli,
195 ["commit-graph", "--stop-at", base, "-a"],
196 env=_env(tmp_path),
197 )
198 assert result.exit_code == 0
199
200
201 class TestCommitGraphCombined:
202 def test_first_parent_and_count(self, tmp_path: pathlib.Path) -> None:
203 _init_repo(tmp_path)
204 c1 = _commit(tmp_path, "c1", parent=None)
205 _commit(tmp_path, "c2", parent=c1)
206 result = runner.invoke(
207 cli, ["commit-graph", "--first-parent", "--count"], env=_env(tmp_path)
208 )
209 assert result.exit_code == 0
210 data = json.loads(result.output)
211 assert data["count"] == 2
212
213 def test_count_always_emits_json(self, tmp_path: pathlib.Path) -> None:
214 """--count always emits JSON."""
215 _init_repo(tmp_path)
216 _commit(tmp_path, "c1")
217 result = runner.invoke(
218 cli,
219 ["commit-graph", "--count"],
220 env=_env(tmp_path),
221 )
222 assert result.exit_code == 0
223 data = json.loads(result.output)
224 assert "count" in data
File History 3 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 131 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 137 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 140 days ago