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