gabriel / muse public

test_cmd_describe.py file-level

at sha256:6 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 chore(timeline): remove unused RationalRate import in entity.py · · Jul 10, 2026
1 """Tests for ``muse describe`` and ``muse/core/describe.py``.
2
3 Covers: no version tags → exit 1, tag at tip (distance 0), tag behind tip
4 (distance N), --pre flag, --first-parent, --json, core describe_commit,
5 stress: deep ancestry.
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.describe import describe_commit
19 from muse.core.ids import hash_commit, hash_snapshot
20 from muse.core.commits import CommitRecord, write_commit
21 from muse.core.snapshots import SnapshotRecord, write_snapshot
22 from muse.core.version_tags import VersionTagRecord, write_version_tag
23 from muse.core.semver import parse_semver
24 from muse.core.types import Manifest, content_hash
25
26 runner = CliRunner()
27
28 from muse.core.paths import muse_dir, ref_path
29 _REPO_ID = content_hash({"name": "describe-test"})
30
31
32 # ---------------------------------------------------------------------------
33 # Helpers
34 # ---------------------------------------------------------------------------
35
36
37 def _init_repo(path: pathlib.Path) -> pathlib.Path:
38 dot_muse = muse_dir(path)
39 for d in ("commits", "snapshots", "objects", "refs/heads"):
40 (dot_muse / d).mkdir(parents=True, exist_ok=True)
41 (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
42 (dot_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 def _make_commit(
53 root: pathlib.Path,
54 parent_id: str | None = None,
55 content: bytes = b"data",
56 branch: str = "main",
57 ) -> str:
58 manifest: Manifest = {}
59 snap_id = hash_snapshot(manifest)
60 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
61 committed_at = datetime.datetime.now(datetime.timezone.utc)
62 parent_ids = [parent_id] if parent_id else []
63 commit_id = hash_commit(
64 parent_ids=parent_ids,
65 snapshot_id=snap_id,
66 message=f"commit-{content[:8].hex()}",
67 committed_at_iso=committed_at.isoformat(),
68 )
69 write_commit(root, CommitRecord(
70 commit_id=commit_id,
71 branch=branch,
72 snapshot_id=snap_id,
73 message=f"commit-{content[:8].hex()}",
74 committed_at=committed_at,
75 parent_commit_id=parent_id,
76 ))
77 ref_path(root, branch).write_text(commit_id, encoding="utf-8")
78 return commit_id
79
80
81 def _make_vtag(root: pathlib.Path, tag: str, commit_id: str) -> None:
82 write_version_tag(root, VersionTagRecord(
83 tag_id=content_hash({"tag": tag, "commit_id": commit_id}),
84 repo_id=_REPO_ID,
85 tag=tag,
86 semver=parse_semver(tag),
87 commit_id=commit_id,
88 created_at=datetime.datetime.now(datetime.timezone.utc),
89 author="",
90 message="",
91 ))
92
93
94 # ---------------------------------------------------------------------------
95 # Unit: core describe_commit
96 # ---------------------------------------------------------------------------
97
98
99 def test_describe_no_tags_returns_none_fields(tmp_path: pathlib.Path) -> None:
100 _init_repo(tmp_path)
101 cid = _make_commit(tmp_path, content=b"alpha")
102 result = describe_commit(tmp_path, _REPO_ID, cid)
103 assert result["tag"] is None
104 assert result["distance"] is None
105 assert result["description"] is None
106 assert result["exact"] is False
107
108
109 def test_describe_tag_at_tip(tmp_path: pathlib.Path) -> None:
110 _init_repo(tmp_path)
111 cid = _make_commit(tmp_path, content=b"beta")
112 _make_vtag(tmp_path, "v1.0.0", cid)
113 result = describe_commit(tmp_path, _REPO_ID, cid)
114 assert result["tag"] == "v1.0.0"
115 assert result["distance"] == 0
116 assert result["description"] == "v1.0.0"
117 assert result["exact"] is True
118
119
120 def test_describe_tag_one_hop_behind(tmp_path: pathlib.Path) -> None:
121 _init_repo(tmp_path)
122 cid1 = _make_commit(tmp_path, content=b"first")
123 _make_vtag(tmp_path, "v0.9.0", cid1)
124 cid2 = _make_commit(tmp_path, parent_id=cid1, content=b"second")
125 result = describe_commit(tmp_path, _REPO_ID, cid2)
126 assert result["tag"] == "v0.9.0"
127 assert result["distance"] == 1
128 assert result["exact"] is False
129
130
131 def test_describe_excludes_pre_by_default(tmp_path: pathlib.Path) -> None:
132 _init_repo(tmp_path)
133 cid = _make_commit(tmp_path, content=b"pre")
134 _make_vtag(tmp_path, "v1.0.0-rc1", cid)
135 result = describe_commit(tmp_path, _REPO_ID, cid, include_pre=False)
136 assert result["tag"] is None
137
138
139 def test_describe_includes_pre_with_flag(tmp_path: pathlib.Path) -> None:
140 _init_repo(tmp_path)
141 cid = _make_commit(tmp_path, content=b"pre")
142 _make_vtag(tmp_path, "v1.0.0-rc1", cid)
143 result = describe_commit(tmp_path, _REPO_ID, cid, include_pre=True)
144 assert result["tag"] == "v1.0.0-rc1"
145 assert result["distance"] == 0
146
147
148 # ---------------------------------------------------------------------------
149 # CLI: muse describe
150 # ---------------------------------------------------------------------------
151
152
153 def test_describe_cli_no_commits(tmp_path: pathlib.Path) -> None:
154 _init_repo(tmp_path)
155 result = runner.invoke(cli, ["describe"], env=_env(tmp_path))
156 assert result.exit_code != 0
157
158
159 def test_describe_cli_no_tags_exits_1(tmp_path: pathlib.Path) -> None:
160 _init_repo(tmp_path)
161 _make_commit(tmp_path, content=b"no-tags")
162 result = runner.invoke(cli, ["describe", "--json"], env=_env(tmp_path))
163 assert result.exit_code == 1
164 data = json.loads(result.output)
165 assert data["tag"] is None
166 assert data["distance"] is None
167 assert data["description"] is None
168
169
170 def test_describe_cli_text_output(tmp_path: pathlib.Path) -> None:
171 _init_repo(tmp_path)
172 cid = _make_commit(tmp_path, content=b"cli-test")
173 _make_vtag(tmp_path, "v3.0.0", cid)
174 result = runner.invoke(cli, ["describe"], env=_env(tmp_path))
175 assert result.exit_code == 0
176 assert "v3.0.0" in result.output
177
178
179 def test_describe_cli_json_output(tmp_path: pathlib.Path) -> None:
180 _init_repo(tmp_path)
181 cid = _make_commit(tmp_path, content=b"json-test")
182 _make_vtag(tmp_path, "v4.0.0", cid)
183 result = runner.invoke(cli, ["describe", "--json"], env=_env(tmp_path))
184 assert result.exit_code == 0
185 data = json.loads(result.output)
186 assert data["tag"] == "v4.0.0"
187 assert data["distance"] == 0
188 assert data["description"] == "v4.0.0"
189 assert "commit_id" in data
190
191
192 def test_describe_cli_pre_flag(tmp_path: pathlib.Path) -> None:
193 _init_repo(tmp_path)
194 cid = _make_commit(tmp_path, content=b"rc")
195 _make_vtag(tmp_path, "v5.0.0-rc1", cid)
196 result = runner.invoke(cli, ["describe", "--pre", "--json"], env=_env(tmp_path))
197 assert result.exit_code == 0
198 data = json.loads(result.output)
199 assert data["tag"] == "v5.0.0-rc1"
200
201
202 # ---------------------------------------------------------------------------
203 # Stress: deep ancestry (100 commits, tag at root)
204 # ---------------------------------------------------------------------------
205
206
207 def test_describe_stress_deep_ancestry(tmp_path: pathlib.Path) -> None:
208 _init_repo(tmp_path)
209 prev: str | None = None
210 first_commit_id = ""
211 for i in range(100):
212 cid = _make_commit(tmp_path, parent_id=prev, content=f"step {i}".encode())
213 if i == 0:
214 first_commit_id = cid
215 prev = cid
216
217 _make_vtag(tmp_path, "v1.0.0", first_commit_id)
218 assert prev is not None
219 result = describe_commit(tmp_path, _REPO_ID, prev)
220 assert result["tag"] == "v1.0.0"
221 assert result["distance"] == 99
222
223
224 class TestRegisterFlags:
225 def test_default_json_out_is_false(self) -> None:
226 import argparse
227 from muse.cli.commands.describe import register
228 p = argparse.ArgumentParser()
229 subs = p.add_subparsers()
230 register(subs)
231 args = p.parse_args(["describe"])
232 assert args.json_out is False
233
234 def test_json_flag_sets_json_out(self) -> None:
235 import argparse
236 from muse.cli.commands.describe import register
237 p = argparse.ArgumentParser()
238 subs = p.add_subparsers()
239 register(subs)
240 args = p.parse_args(["describe", "--json"])
241 assert args.json_out is True
242
243 def test_j_shorthand_sets_json_out(self) -> None:
244 import argparse
245 from muse.cli.commands.describe import register
246 p = argparse.ArgumentParser()
247 subs = p.add_subparsers()
248 register(subs)
249 args = p.parse_args(["describe", "-j"])
250 assert args.json_out is True
251
252 def test_pre_flag_sets_include_pre(self) -> None:
253 import argparse
254 from muse.cli.commands.describe import register
255 p = argparse.ArgumentParser()
256 subs = p.add_subparsers()
257 register(subs)
258 args = p.parse_args(["describe", "--pre"])
259 assert args.include_pre is True
260
261 def test_default_include_pre_is_false(self) -> None:
262 import argparse
263 from muse.cli.commands.describe import register
264 p = argparse.ArgumentParser()
265 subs = p.add_subparsers()
266 register(subs)
267 args = p.parse_args(["describe"])
268 assert args.include_pre is False