gabriel / muse public
test_cmd_describe.py python
268 lines 9.0 KB
Raw
sha256:c0eba5ad689cec79f4a3fcdc4f5da78556cb4b8cb7b330f944634356c10379ed chore: pivot to nightly channel — bump version to 0.2.0.dev… Sonnet 5 patch 12 days ago
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
File History 10 commits
sha256:c287f599c5429903a139eadf3c5db5d930520e57cb0c3c575d9570e953c3b2d6 chore: bump version to 0.2.0.dev2 — nightly.2 Sonnet 4.6 patch 11 days ago
sha256:8de4334a98c945aace420969d389ad678aa926d4ab4e886b2ac4c4241cb3bf2b revert: keep pyproject.toml in canonical PEP 440 form Sonnet 4.6 patch 14 days ago
sha256:50bb615c573aa4b928fca75b60f82ba2a659910066507cec6a95c412ae22ccb9 docs: add issue docs for push have-negotiation bug (#55) an… Sonnet 4.6 18 days ago
sha256:d90d175cded68aae1d4ffcf4858917854195d0cd8ce1fe73cee4dbc02541cb74 chore: trigger push to surface null-OID paths Sonnet 4.6 23 days ago
sha256:e452ad9a6ace6ccc6d875a35e06caf9da5576a970c1c36133b69a891ce5fefa8 chore: prebuild timing test Sonnet 4.6 33 days ago
sha256:0008ab6695e3e064b3e236b24fd19e538fef6a588eb0d211622f4466d919c0b1 merge: pull staging/dev — advance to 0.2.0rc12 Sonnet 4.6 patch 35 days ago
sha256:9c33d61749fff814c5226d5386aa2af7064c2c02788594a25fdd709358132eea fix: _PROPOSAL_PREFIX_RESOLVE_LIMIT 200 → 100 to match hub … Sonnet 4.6 47 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 49 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 56 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 56 days ago