gabriel / muse public
test_cmd_shortlog.py python
262 lines 8.7 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 121 days ago
1 """Tests for ``muse shortlog``.
2
3 Covers: empty repo, single author, multiple authors, --numbered sort,
4 --email flag, --format json, --all branches, --limit, short flags,
5 stress: 200 commits across 3 authors.
6 """
7
8 from __future__ import annotations
9
10 import datetime
11 import json
12 import pathlib
13
14 import pytest
15 from tests.cli_test_helper import CliRunner
16
17 cli = None # argparse migration — CliRunner ignores this arg
18 from muse.core.object_store import write_object
19 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
20 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
21 from muse.core.types import Manifest, blob_id
22 from muse.core.paths import muse_dir, ref_path
23
24 runner = CliRunner()
25
26 _REPO_ID = "shortlog-test"
27
28
29 # ---------------------------------------------------------------------------
30 # Helpers
31 # ---------------------------------------------------------------------------
32
33
34
35
36 def _init_repo(path: pathlib.Path) -> pathlib.Path:
37 dot_muse = muse_dir(path)
38 for d in ("commits", "snapshots", "objects", "refs/heads"):
39 (dot_muse / d).mkdir(parents=True, exist_ok=True)
40 (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
41 (dot_muse / "repo.json").write_text(
42 json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8"
43 )
44 return path
45
46
47 def _env(repo: pathlib.Path) -> Manifest:
48 return {"MUSE_REPO_ROOT": str(repo)}
49
50
51 _counter = 0
52
53 # Per-branch tracking of the latest commit so tests can chain automatically.
54 _branch_heads: Manifest = {}
55
56
57 def _make_commit(
58 root: pathlib.Path,
59 author: str = "Alice",
60 parent_id: str | None = None,
61 branch: str = "main",
62 ) -> str:
63 """Create a commit, automatically chaining to the previous commit on the branch."""
64 global _counter
65 _counter += 1
66 # Auto-chain: if no explicit parent, use the last commit on this branch.
67 if parent_id is None:
68 parent_id = _branch_heads.get(f"{str(root)}:{branch}")
69 content = f"content-{_counter}".encode()
70 obj_id = blob_id(content)
71 write_object(root, obj_id, content)
72 manifest = {f"file_{_counter}.txt": obj_id}
73 snap_id = compute_snapshot_id(manifest)
74 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
75 committed_at = datetime.datetime.now(datetime.timezone.utc)
76 parent_ids = [parent_id] if parent_id else []
77 commit_id = compute_commit_id(
78 parent_ids=parent_ids,
79 snapshot_id=snap_id,
80 message=f"commit by {author} #{_counter}",
81 committed_at_iso=committed_at.isoformat(),
82 author=author,
83 )
84 write_commit(root, CommitRecord(
85 repo_id=_REPO_ID,
86 commit_id=commit_id,
87 branch=branch,
88 snapshot_id=snap_id,
89 message=f"commit by {author} #{_counter}",
90 committed_at=committed_at,
91 parent_commit_id=parent_id,
92 author=author,
93 ))
94 (ref_path(root, branch)).write_text(commit_id, encoding="utf-8")
95 _branch_heads[f"{str(root)}:{branch}"] = commit_id
96 return commit_id
97
98
99 # ---------------------------------------------------------------------------
100 # Unit: empty repo
101 # ---------------------------------------------------------------------------
102
103
104 def test_shortlog_empty_repo(tmp_path: pathlib.Path) -> None:
105 _init_repo(tmp_path)
106 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
107 assert result.exit_code == 0
108 assert "no commits" in result.output.lower()
109
110
111 def test_shortlog_help() -> None:
112 result = runner.invoke(cli, ["shortlog", "--help"])
113 assert result.exit_code == 0
114 assert "--numbered" in result.output or "-n" in result.output
115
116
117 # ---------------------------------------------------------------------------
118 # Unit: single author
119 # ---------------------------------------------------------------------------
120
121
122 def test_shortlog_single_author(tmp_path: pathlib.Path) -> None:
123 _init_repo(tmp_path)
124 _make_commit(tmp_path, author="Alice")
125 _make_commit(tmp_path, author="Alice")
126 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
127 assert result.exit_code == 0
128 assert "Alice" in result.output
129 assert "(2)" in result.output
130
131
132 # ---------------------------------------------------------------------------
133 # Unit: multiple authors
134 # ---------------------------------------------------------------------------
135
136
137 def test_shortlog_multiple_authors(tmp_path: pathlib.Path) -> None:
138 _init_repo(tmp_path)
139 _make_commit(tmp_path, author="Alice")
140 _make_commit(tmp_path, author="Bob")
141 _make_commit(tmp_path, author="Alice")
142 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
143 assert result.exit_code == 0
144 assert "Alice" in result.output
145 assert "Bob" in result.output
146
147
148 # ---------------------------------------------------------------------------
149 # Unit: --numbered sorts by count
150 # ---------------------------------------------------------------------------
151
152
153 def test_shortlog_numbered(tmp_path: pathlib.Path) -> None:
154 _init_repo(tmp_path)
155 _make_commit(tmp_path, author="Bob")
156 _make_commit(tmp_path, author="Alice")
157 _make_commit(tmp_path, author="Alice")
158 _make_commit(tmp_path, author="Alice")
159 result = runner.invoke(cli, ["shortlog", "--numbered"], env=_env(tmp_path))
160 assert result.exit_code == 0
161 alice_pos = result.output.index("Alice")
162 bob_pos = result.output.index("Bob")
163 assert alice_pos < bob_pos # Alice has more commits, should appear first
164
165
166 # ---------------------------------------------------------------------------
167 # Unit: --format json
168 # ---------------------------------------------------------------------------
169
170
171 def test_shortlog_json_output(tmp_path: pathlib.Path) -> None:
172 _init_repo(tmp_path)
173 _make_commit(tmp_path, author="Charlie")
174 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
175 assert result.exit_code == 0
176 data = json.loads(result.output)
177 assert isinstance(data, dict)
178 groups = data["groups"]
179 assert len(groups) >= 1
180 assert groups[0]["key"] == "Charlie"
181 assert groups[0]["count"] >= 1
182
183
184 # ---------------------------------------------------------------------------
185 # Unit: --limit
186 # ---------------------------------------------------------------------------
187
188
189 def test_shortlog_limit(tmp_path: pathlib.Path) -> None:
190 _init_repo(tmp_path)
191 for _ in range(20):
192 _make_commit(tmp_path, author="Dave")
193 result = runner.invoke(cli, ["shortlog", "--limit", "5", "--json"], env=_env(tmp_path))
194 assert result.exit_code == 0
195 data = json.loads(result.output)
196 total_commits = sum(g["count"] for g in data["groups"])
197 assert total_commits <= 5
198
199
200 # ---------------------------------------------------------------------------
201 # Unit: short flags
202 # ---------------------------------------------------------------------------
203
204
205 def test_shortlog_short_flags(tmp_path: pathlib.Path) -> None:
206 _init_repo(tmp_path)
207 _make_commit(tmp_path, author="Eve")
208 result = runner.invoke(cli, ["shortlog", "--numbered", "--json"], env=_env(tmp_path))
209 assert result.exit_code == 0
210 data = json.loads(result.output)
211 assert len(data["groups"]) >= 1
212
213
214 # ---------------------------------------------------------------------------
215 # Stress: 200 commits across 3 authors
216 # ---------------------------------------------------------------------------
217
218
219 def test_shortlog_stress_200_commits(tmp_path: pathlib.Path) -> None:
220 _init_repo(tmp_path)
221 authors = ["Frank", "Grace", "Heidi"]
222 for i in range(200):
223 _make_commit(tmp_path, author=authors[i % 3])
224
225 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
226 assert result.exit_code == 0
227 data = json.loads(result.output)
228 total = sum(g["count"] for g in data["groups"])
229 assert total == 200
230 names = {g["key"] for g in data["groups"]}
231 assert "Frank" in names
232 assert "Grace" in names
233 assert "Heidi" in names
234
235
236 class TestRegisterFlags:
237 def test_default_json_out_is_false(self) -> None:
238 import argparse
239 from muse.cli.commands.shortlog import register
240 p = argparse.ArgumentParser()
241 subs = p.add_subparsers()
242 register(subs)
243 args = p.parse_args(["shortlog"])
244 assert args.json_out is False
245
246 def test_json_flag_sets_json_out(self) -> None:
247 import argparse
248 from muse.cli.commands.shortlog import register
249 p = argparse.ArgumentParser()
250 subs = p.add_subparsers()
251 register(subs)
252 args = p.parse_args(["shortlog", "--json"])
253 assert args.json_out is True
254
255 def test_j_shorthand_sets_json_out(self) -> None:
256 import argparse
257 from muse.cli.commands.shortlog import register
258 p = argparse.ArgumentParser()
259 subs = p.add_subparsers()
260 register(subs)
261 args = p.parse_args(["shortlog", "-j"])
262 assert args.json_out is True
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 121 days ago