gabriel / muse public
test_cmd_describe.py python
221 lines 7.3 KB
1ba7f7b1 feat(porcelain): implement 9 gap-fill porcelain commands with full test… Gabriel Cardona <gabriel@tellurstori.com> 2d 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 typer.testing import CliRunner
17
18 from muse.cli.app import cli
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"]