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