gabriel / muse public
test_cmd_shortlog.py python
229 lines 7.7 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 27 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 hashlib
12 import json
13 import pathlib
14
15 import pytest
16 from tests.cli_test_helper import CliRunner
17
18 cli = None # argparse migration — CliRunner ignores this arg
19 from muse.core.object_store import write_object
20 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
21 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
22 from muse.core._types import Manifest
23
24 runner = CliRunner()
25
26 _REPO_ID = "shortlog-test"
27
28
29 # ---------------------------------------------------------------------------
30 # Helpers
31 # ---------------------------------------------------------------------------
32
33
34 def _sha(data: bytes) -> str:
35 return hashlib.sha256(data).hexdigest()
36
37
38 def _init_repo(path: pathlib.Path) -> pathlib.Path:
39 muse = path / ".muse"
40 for d in ("commits", "snapshots", "objects", "refs/heads"):
41 (muse / d).mkdir(parents=True, exist_ok=True)
42 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
43 (muse / "repo.json").write_text(
44 json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8"
45 )
46 return path
47
48
49 def _env(repo: pathlib.Path) -> Manifest:
50 return {"MUSE_REPO_ROOT": str(repo)}
51
52
53 _counter = 0
54
55 # Per-branch tracking of the latest commit so tests can chain automatically.
56 _branch_heads: Manifest = {}
57
58
59 def _make_commit(
60 root: pathlib.Path,
61 author: str = "Alice",
62 parent_id: str | None = None,
63 branch: str = "main",
64 ) -> str:
65 """Create a commit, automatically chaining to the previous commit on the branch."""
66 global _counter
67 _counter += 1
68 # Auto-chain: if no explicit parent, use the last commit on this branch.
69 if parent_id is None:
70 parent_id = _branch_heads.get(f"{str(root)}:{branch}")
71 content = f"content-{_counter}".encode()
72 obj_id = _sha(content)
73 write_object(root, obj_id, content)
74 manifest = {f"file_{_counter}.txt": obj_id}
75 snap_id = compute_snapshot_id(manifest)
76 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
77 committed_at = datetime.datetime.now(datetime.timezone.utc)
78 parent_ids = [parent_id] if parent_id else []
79 commit_id = compute_commit_id(parent_ids, snap_id, f"commit by {author} #{_counter}", committed_at.isoformat())
80 write_commit(root, CommitRecord(
81 commit_id=commit_id,
82 repo_id=_REPO_ID,
83 branch=branch,
84 snapshot_id=snap_id,
85 message=f"commit by {author} #{_counter}",
86 committed_at=committed_at,
87 parent_commit_id=parent_id,
88 author=author,
89 ))
90 (root / ".muse" / "refs" / "heads" / branch).write_text(commit_id, encoding="utf-8")
91 _branch_heads[f"{str(root)}:{branch}"] = commit_id
92 return commit_id
93
94
95 # ---------------------------------------------------------------------------
96 # Unit: empty repo
97 # ---------------------------------------------------------------------------
98
99
100 def test_shortlog_empty_repo(tmp_path: pathlib.Path) -> None:
101 _init_repo(tmp_path)
102 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
103 assert result.exit_code == 0
104 assert "no commits" in result.output.lower()
105
106
107 def test_shortlog_help() -> None:
108 result = runner.invoke(cli, ["shortlog", "--help"])
109 assert result.exit_code == 0
110 assert "--numbered" in result.output or "-n" in result.output
111
112
113 # ---------------------------------------------------------------------------
114 # Unit: single author
115 # ---------------------------------------------------------------------------
116
117
118 def test_shortlog_single_author(tmp_path: pathlib.Path) -> None:
119 _init_repo(tmp_path)
120 _make_commit(tmp_path, author="Alice")
121 _make_commit(tmp_path, author="Alice")
122 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
123 assert result.exit_code == 0
124 assert "Alice" in result.output
125 assert "(2)" in result.output
126
127
128 # ---------------------------------------------------------------------------
129 # Unit: multiple authors
130 # ---------------------------------------------------------------------------
131
132
133 def test_shortlog_multiple_authors(tmp_path: pathlib.Path) -> None:
134 _init_repo(tmp_path)
135 _make_commit(tmp_path, author="Alice")
136 _make_commit(tmp_path, author="Bob")
137 _make_commit(tmp_path, author="Alice")
138 result = runner.invoke(cli, ["shortlog"], env=_env(tmp_path))
139 assert result.exit_code == 0
140 assert "Alice" in result.output
141 assert "Bob" in result.output
142
143
144 # ---------------------------------------------------------------------------
145 # Unit: --numbered sorts by count
146 # ---------------------------------------------------------------------------
147
148
149 def test_shortlog_numbered(tmp_path: pathlib.Path) -> None:
150 _init_repo(tmp_path)
151 _make_commit(tmp_path, author="Bob")
152 _make_commit(tmp_path, author="Alice")
153 _make_commit(tmp_path, author="Alice")
154 _make_commit(tmp_path, author="Alice")
155 result = runner.invoke(cli, ["shortlog", "--numbered"], env=_env(tmp_path))
156 assert result.exit_code == 0
157 alice_pos = result.output.index("Alice")
158 bob_pos = result.output.index("Bob")
159 assert alice_pos < bob_pos # Alice has more commits, should appear first
160
161
162 # ---------------------------------------------------------------------------
163 # Unit: --format json
164 # ---------------------------------------------------------------------------
165
166
167 def test_shortlog_json_output(tmp_path: pathlib.Path) -> None:
168 _init_repo(tmp_path)
169 _make_commit(tmp_path, author="Charlie")
170 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
171 assert result.exit_code == 0
172 data = json.loads(result.output)
173 assert isinstance(data, dict)
174 groups = data["groups"]
175 assert len(groups) >= 1
176 assert groups[0]["key"] == "Charlie"
177 assert groups[0]["count"] >= 1
178
179
180 # ---------------------------------------------------------------------------
181 # Unit: --limit
182 # ---------------------------------------------------------------------------
183
184
185 def test_shortlog_limit(tmp_path: pathlib.Path) -> None:
186 _init_repo(tmp_path)
187 for _ in range(20):
188 _make_commit(tmp_path, author="Dave")
189 result = runner.invoke(cli, ["shortlog", "--limit", "5", "--json"], env=_env(tmp_path))
190 assert result.exit_code == 0
191 data = json.loads(result.output)
192 total_commits = sum(g["count"] for g in data["groups"])
193 assert total_commits <= 5
194
195
196 # ---------------------------------------------------------------------------
197 # Unit: short flags
198 # ---------------------------------------------------------------------------
199
200
201 def test_shortlog_short_flags(tmp_path: pathlib.Path) -> None:
202 _init_repo(tmp_path)
203 _make_commit(tmp_path, author="Eve")
204 result = runner.invoke(cli, ["shortlog", "-n", "--json"], env=_env(tmp_path))
205 assert result.exit_code == 0
206 data = json.loads(result.output)
207 assert len(data["groups"]) >= 1
208
209
210 # ---------------------------------------------------------------------------
211 # Stress: 200 commits across 3 authors
212 # ---------------------------------------------------------------------------
213
214
215 def test_shortlog_stress_200_commits(tmp_path: pathlib.Path) -> None:
216 _init_repo(tmp_path)
217 authors = ["Frank", "Grace", "Heidi"]
218 for i in range(200):
219 _make_commit(tmp_path, author=authors[i % 3])
220
221 result = runner.invoke(cli, ["shortlog", "--json"], env=_env(tmp_path))
222 assert result.exit_code == 0
223 data = json.loads(result.output)
224 total = sum(g["count"] for g in data["groups"])
225 assert total == 200
226 names = {g["key"] for g in data["groups"]}
227 assert "Frank" in names
228 assert "Grace" in names
229 assert "Heidi" in names
File History 5 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 27 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 27 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e Merge branch 'dev' into main Human 30 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 49 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 101 days ago