gabriel / muse public
test_cmd_describe.py python
221 lines 7.4 KB
86000da9 fix: replace typer CliRunner with argparse-compatible test helper Gabriel Cardona <gabriel@tellurstori.com> 1d ago
1 """Tests for ``muse describe`` and ``muse/core/describe.py``.
2
3 Covers: no tags fallback to SHA, tag at tip, tag behind tip (distance),
4 --long format, --require-tag exit-1, --format json, core describe_commit,
5 stress: deep ancestry.
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.describe import describe_commit
20 from muse.core.object_store import write_object
21 from muse.core.snapshot import compute_snapshot_id
22 from muse.core.store import CommitRecord, SnapshotRecord, TagRecord, write_commit, write_snapshot, write_tag
23
24 runner = CliRunner()
25
26 _REPO_ID = "describe-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", f"tags/{_REPO_ID}"):
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": "midi"}), encoding="utf-8"
45 )
46 return path
47
48
49 def _env(repo: pathlib.Path) -> dict[str, str]:
50 return {"MUSE_REPO_ROOT": str(repo)}
51
52
53 def _make_commit(
54 root: pathlib.Path,
55 parent_id: str | None = None,
56 content: bytes = b"data",
57 branch: str = "main",
58 ) -> str:
59 obj_id = _sha(content)
60 write_object(root, obj_id, content)
61 manifest = {f"file_{obj_id[:8]}.txt": obj_id}
62 snap_id = compute_snapshot_id(manifest)
63 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
64 committed_at = datetime.datetime.now(datetime.timezone.utc)
65 suffix = parent_id or "root"
66 commit_id = _sha(f"{suffix}:{snap_id}:{committed_at.isoformat()}".encode())
67 write_commit(root, CommitRecord(
68 commit_id=commit_id,
69 repo_id=_REPO_ID,
70 branch=branch,
71 snapshot_id=snap_id,
72 message=f"commit on {branch}",
73 committed_at=committed_at,
74 parent_commit_id=parent_id,
75 ))
76 (root / ".muse" / "refs" / "heads" / branch).write_text(commit_id, encoding="utf-8")
77 return commit_id
78
79
80 def _make_tag(root: pathlib.Path, tag: str, commit_id: str) -> None:
81 import uuid as _uuid
82 write_tag(root, TagRecord(
83 tag_id=str(_uuid.uuid4()),
84 tag=tag,
85 commit_id=commit_id,
86 repo_id=_REPO_ID,
87 created_at=datetime.datetime.now(datetime.timezone.utc),
88 ))
89
90
91 # ---------------------------------------------------------------------------
92 # Unit: core describe_commit
93 # ---------------------------------------------------------------------------
94
95
96 def test_describe_no_tags_returns_short_sha(tmp_path: pathlib.Path) -> None:
97 _init_repo(tmp_path)
98 cid = _make_commit(tmp_path, content=b"alpha")
99 result = describe_commit(tmp_path, _REPO_ID, cid)
100 assert result["tag"] is None
101 assert result["name"] == cid[:12]
102 assert result["short_sha"] == cid[:12]
103
104
105 def test_describe_tag_at_tip(tmp_path: pathlib.Path) -> None:
106 _init_repo(tmp_path)
107 cid = _make_commit(tmp_path, content=b"beta")
108 _make_tag(tmp_path, "v1.0.0", cid)
109 result = describe_commit(tmp_path, _REPO_ID, cid)
110 assert result["tag"] == "v1.0.0"
111 assert result["distance"] == 0
112 assert result["name"] == "v1.0.0"
113
114
115 def test_describe_tag_one_hop_behind(tmp_path: pathlib.Path) -> None:
116 _init_repo(tmp_path)
117 cid1 = _make_commit(tmp_path, content=b"first")
118 _make_tag(tmp_path, "v0.9.0", cid1)
119 cid2 = _make_commit(tmp_path, parent_id=cid1, content=b"second")
120 result = describe_commit(tmp_path, _REPO_ID, cid2)
121 assert result["tag"] == "v0.9.0"
122 assert result["distance"] == 1
123 assert result["name"] == "v0.9.0~1"
124
125
126 def test_describe_long_format(tmp_path: pathlib.Path) -> None:
127 _init_repo(tmp_path)
128 cid = _make_commit(tmp_path, content=b"gamma")
129 _make_tag(tmp_path, "v2.0.0", cid)
130 result = describe_commit(tmp_path, _REPO_ID, cid, long_format=True)
131 assert result["tag"] == "v2.0.0"
132 assert result["distance"] == 0
133 # Long format always includes distance + SHA.
134 assert "v2.0.0-0-g" in result["name"]
135
136
137 # ---------------------------------------------------------------------------
138 # CLI: muse describe
139 # ---------------------------------------------------------------------------
140
141
142 def test_describe_cli_help() -> None:
143 result = runner.invoke(cli, ["describe", "--help"])
144 assert result.exit_code == 0
145 assert "--long" in result.output or "-l" in result.output
146
147
148 def test_describe_cli_no_commits(tmp_path: pathlib.Path) -> None:
149 _init_repo(tmp_path)
150 result = runner.invoke(cli, ["describe"], env=_env(tmp_path))
151 assert result.exit_code != 0
152
153
154 def test_describe_cli_text_output(tmp_path: pathlib.Path) -> None:
155 _init_repo(tmp_path)
156 cid = _make_commit(tmp_path, content=b"cli-test")
157 _make_tag(tmp_path, "v3.0.0", cid)
158 result = runner.invoke(cli, ["describe"], env=_env(tmp_path))
159 assert result.exit_code == 0
160 assert "v3.0.0" in result.output
161
162
163 def test_describe_cli_json_output(tmp_path: pathlib.Path) -> None:
164 _init_repo(tmp_path)
165 cid = _make_commit(tmp_path, content=b"json-test")
166 _make_tag(tmp_path, "v4.0.0", cid)
167 result = runner.invoke(cli, ["describe", "--format", "json"], env=_env(tmp_path))
168 assert result.exit_code == 0
169 data = json.loads(result.output)
170 assert data["tag"] == "v4.0.0"
171 assert data["distance"] == 0
172 assert "commit_id" in data
173
174
175 def test_describe_cli_require_tag_fails_without_tags(tmp_path: pathlib.Path) -> None:
176 _init_repo(tmp_path)
177 _make_commit(tmp_path, content=b"no-tags")
178 result = runner.invoke(cli, ["describe", "--require-tag"], env=_env(tmp_path))
179 assert result.exit_code != 0
180
181
182 def test_describe_cli_long_flag(tmp_path: pathlib.Path) -> None:
183 _init_repo(tmp_path)
184 cid = _make_commit(tmp_path, content=b"long")
185 _make_tag(tmp_path, "v5.0.0", cid)
186 result = runner.invoke(cli, ["describe", "--long"], env=_env(tmp_path))
187 assert result.exit_code == 0
188 assert "v5.0.0-0-g" in result.output
189
190
191 def test_describe_cli_short_flags(tmp_path: pathlib.Path) -> None:
192 _init_repo(tmp_path)
193 cid = _make_commit(tmp_path, content=b"short-flags")
194 _make_tag(tmp_path, "v6.0.0", cid)
195 result = runner.invoke(cli, ["describe", "-l", "-f", "json"], env=_env(tmp_path))
196 assert result.exit_code == 0
197 data = json.loads(result.output)
198 assert "v6.0.0" in data["name"]
199
200
201 # ---------------------------------------------------------------------------
202 # Stress: deep ancestry (100 commits, tag at root)
203 # ---------------------------------------------------------------------------
204
205
206 def test_describe_stress_deep_ancestry(tmp_path: pathlib.Path) -> None:
207 _init_repo(tmp_path)
208 prev: str | None = None
209 first_commit_id = ""
210 for i in range(100):
211 cid = _make_commit(tmp_path, parent_id=prev, content=f"step {i}".encode())
212 if i == 0:
213 first_commit_id = cid
214 prev = cid
215
216 _make_tag(tmp_path, "v-root", first_commit_id)
217 assert prev is not None
218 result = describe_commit(tmp_path, _REPO_ID, prev)
219 assert result["tag"] == "v-root"
220 assert result["distance"] == 99
221 assert "v-root~99" == result["name"]