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