gabriel / muse public
test_cmd_shortlog.py python
228 lines 7.7 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 147 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(parent_ids, snap_id, f"commit by {author} #{_counter}", committed_at.isoformat())
79 write_commit(root, CommitRecord(
80 commit_id=commit_id,
81 repo_id=_REPO_ID,
82 branch=branch,
83 snapshot_id=snap_id,
84 message=f"commit by {author} #{_counter}",
85 committed_at=committed_at,
86 parent_commit_id=parent_id,
87 author=author,
88 ))
89 (root / ".muse" / "refs" / "heads" / branch).write_text(commit_id, encoding="utf-8")
90 _branch_heads[f"{str(root)}:{branch}"] = commit_id
91 return commit_id
92
93
94 # ---------------------------------------------------------------------------
95 # Unit: empty repo
96 # ---------------------------------------------------------------------------
97
98
99 def test_shortlog_empty_repo(tmp_path: pathlib.Path) -> None:
100 _init_repo(tmp_path)
101 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
102 assert result.exit_code == 0
103 assert "no commits" in result.output.lower()
104
105
106 def test_shortlog_help() -> None:
107 result = runner.invoke(cli, ["shortlog", "--help"])
108 assert result.exit_code == 0
109 assert "--numbered" in result.output or "-n" in result.output
110
111
112 # ---------------------------------------------------------------------------
113 # Unit: single author
114 # ---------------------------------------------------------------------------
115
116
117 def test_shortlog_single_author(tmp_path: pathlib.Path) -> None:
118 _init_repo(tmp_path)
119 _make_commit(tmp_path, author="Alice")
120 _make_commit(tmp_path, author="Alice")
121 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
122 assert result.exit_code == 0
123 assert "Alice" in result.output
124 assert "(2)" in result.output
125
126
127 # ---------------------------------------------------------------------------
128 # Unit: multiple authors
129 # ---------------------------------------------------------------------------
130
131
132 def test_shortlog_multiple_authors(tmp_path: pathlib.Path) -> None:
133 _init_repo(tmp_path)
134 _make_commit(tmp_path, author="Alice")
135 _make_commit(tmp_path, author="Bob")
136 _make_commit(tmp_path, author="Alice")
137 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
138 assert result.exit_code == 0
139 assert "Alice" in result.output
140 assert "Bob" in result.output
141
142
143 # ---------------------------------------------------------------------------
144 # Unit: --numbered sorts by count
145 # ---------------------------------------------------------------------------
146
147
148 def test_shortlog_numbered(tmp_path: pathlib.Path) -> None:
149 _init_repo(tmp_path)
150 _make_commit(tmp_path, author="Bob")
151 _make_commit(tmp_path, author="Alice")
152 _make_commit(tmp_path, author="Alice")
153 _make_commit(tmp_path, author="Alice")
154 result = runner.invoke(cli, ["shortlog", "--numbered"], env=_env(tmp_path))
155 assert result.exit_code == 0
156 alice_pos = result.output.index("Alice")
157 bob_pos = result.output.index("Bob")
158 assert alice_pos < bob_pos # Alice has more commits, should appear first
159
160
161 # ---------------------------------------------------------------------------
162 # Unit: --format json
163 # ---------------------------------------------------------------------------
164
165
166 def test_shortlog_json_output(tmp_path: pathlib.Path) -> None:
167 _init_repo(tmp_path)
168 _make_commit(tmp_path, author="Charlie")
169 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
170 assert result.exit_code == 0
171 data = json.loads(result.output)
172 assert isinstance(data, dict)
173 groups = data["groups"]
174 assert len(groups) >= 1
175 assert groups[0]["key"] == "Charlie"
176 assert groups[0]["count"] >= 1
177
178
179 # ---------------------------------------------------------------------------
180 # Unit: --limit
181 # ---------------------------------------------------------------------------
182
183
184 def test_shortlog_limit(tmp_path: pathlib.Path) -> None:
185 _init_repo(tmp_path)
186 for _ in range(20):
187 _make_commit(tmp_path, author="Dave")
188 result = runner.invoke(cli, ["shortlog", "--limit", "5", "--json"], env=_env(tmp_path))
189 assert result.exit_code == 0
190 data = json.loads(result.output)
191 total_commits = sum(g["count"] for g in data["groups"])
192 assert total_commits <= 5
193
194
195 # ---------------------------------------------------------------------------
196 # Unit: short flags
197 # ---------------------------------------------------------------------------
198
199
200 def test_shortlog_short_flags(tmp_path: pathlib.Path) -> None:
201 _init_repo(tmp_path)
202 _make_commit(tmp_path, author="Eve")
203 result = runner.invoke(cli, ["shortlog", "-n", "--json"], env=_env(tmp_path))
204 assert result.exit_code == 0
205 data = json.loads(result.output)
206 assert len(data["groups"]) >= 1
207
208
209 # ---------------------------------------------------------------------------
210 # Stress: 200 commits across 3 authors
211 # ---------------------------------------------------------------------------
212
213
214 def test_shortlog_stress_200_commits(tmp_path: pathlib.Path) -> None:
215 _init_repo(tmp_path)
216 authors = ["Frank", "Grace", "Heidi"]
217 for i in range(200):
218 _make_commit(tmp_path, author=authors[i % 3])
219
220 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
221 assert result.exit_code == 0
222 data = json.loads(result.output)
223 total = sum(g["count"] for g in data["groups"])
224 assert total == 200
225 names = {g["key"] for g in data["groups"]}
226 assert "Frank" in names
227 assert "Grace" in names
228 assert "Heidi" in names
File History 2 commits
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 147 days ago
sha256:a09b1b4f6838754495547f200aa0ce88e2f56ffc5b20b900f6f0cff2c3cdede9 fix(cursorignore): remove git-ism (.git/worktrees) Human minor 150 days ago