"""Tests for ``muse describe`` and ``muse/core/describe.py``. Covers: no version tags → exit 1, tag at tip (distance 0), tag behind tip (distance N), --pre flag, --first-parent, --json, core describe_commit, stress: deep ancestry. """ from __future__ import annotations import datetime import json import pathlib import pytest from tests.cli_test_helper import CliRunner cli = None # argparse migration — CliRunner ignores this arg from muse.core.describe import describe_commit from muse.core.ids import hash_commit, hash_snapshot from muse.core.commits import CommitRecord, write_commit from muse.core.snapshots import SnapshotRecord, write_snapshot from muse.core.version_tags import VersionTagRecord, write_version_tag from muse.core.semver import parse_semver from muse.core.types import Manifest, content_hash runner = CliRunner() from muse.core.paths import muse_dir, ref_path _REPO_ID = content_hash({"name": "describe-test"}) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _init_repo(path: pathlib.Path) -> pathlib.Path: dot_muse = muse_dir(path) for d in ("commits", "snapshots", "objects", "refs/heads"): (dot_muse / d).mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") (dot_muse / "repo.json").write_text( json.dumps({"repo_id": _REPO_ID, "domain": "code"}), encoding="utf-8" ) return path def _env(repo: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(repo)} def _make_commit( root: pathlib.Path, parent_id: str | None = None, content: bytes = b"data", branch: str = "main", ) -> str: manifest: Manifest = {} snap_id = hash_snapshot(manifest) write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest)) committed_at = datetime.datetime.now(datetime.timezone.utc) parent_ids = [parent_id] if parent_id else [] commit_id = hash_commit( parent_ids=parent_ids, snapshot_id=snap_id, message=f"commit-{content[:8].hex()}", committed_at_iso=committed_at.isoformat(), ) write_commit(root, CommitRecord( commit_id=commit_id, branch=branch, snapshot_id=snap_id, message=f"commit-{content[:8].hex()}", committed_at=committed_at, parent_commit_id=parent_id, )) ref_path(root, branch).write_text(commit_id, encoding="utf-8") return commit_id def _make_vtag(root: pathlib.Path, tag: str, commit_id: str) -> None: write_version_tag(root, VersionTagRecord( tag_id=content_hash({"tag": tag, "commit_id": commit_id}), repo_id=_REPO_ID, tag=tag, semver=parse_semver(tag), commit_id=commit_id, created_at=datetime.datetime.now(datetime.timezone.utc), author="", message="", )) # --------------------------------------------------------------------------- # Unit: core describe_commit # --------------------------------------------------------------------------- def test_describe_no_tags_returns_none_fields(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"alpha") result = describe_commit(tmp_path, _REPO_ID, cid) assert result["tag"] is None assert result["distance"] is None assert result["description"] is None assert result["exact"] is False def test_describe_tag_at_tip(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"beta") _make_vtag(tmp_path, "v1.0.0", cid) result = describe_commit(tmp_path, _REPO_ID, cid) assert result["tag"] == "v1.0.0" assert result["distance"] == 0 assert result["description"] == "v1.0.0" assert result["exact"] is True def test_describe_tag_one_hop_behind(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid1 = _make_commit(tmp_path, content=b"first") _make_vtag(tmp_path, "v0.9.0", cid1) cid2 = _make_commit(tmp_path, parent_id=cid1, content=b"second") result = describe_commit(tmp_path, _REPO_ID, cid2) assert result["tag"] == "v0.9.0" assert result["distance"] == 1 assert result["exact"] is False def test_describe_excludes_pre_by_default(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"pre") _make_vtag(tmp_path, "v1.0.0-rc1", cid) result = describe_commit(tmp_path, _REPO_ID, cid, include_pre=False) assert result["tag"] is None def test_describe_includes_pre_with_flag(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"pre") _make_vtag(tmp_path, "v1.0.0-rc1", cid) result = describe_commit(tmp_path, _REPO_ID, cid, include_pre=True) assert result["tag"] == "v1.0.0-rc1" assert result["distance"] == 0 # --------------------------------------------------------------------------- # CLI: muse describe # --------------------------------------------------------------------------- def test_describe_cli_no_commits(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) result = runner.invoke(cli, ["describe"], env=_env(tmp_path)) assert result.exit_code != 0 def test_describe_cli_no_tags_exits_1(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) _make_commit(tmp_path, content=b"no-tags") result = runner.invoke(cli, ["describe", "--json"], env=_env(tmp_path)) assert result.exit_code == 1 data = json.loads(result.output) assert data["tag"] is None assert data["distance"] is None assert data["description"] is None def test_describe_cli_text_output(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"cli-test") _make_vtag(tmp_path, "v3.0.0", cid) result = runner.invoke(cli, ["describe"], env=_env(tmp_path)) assert result.exit_code == 0 assert "v3.0.0" in result.output def test_describe_cli_json_output(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"json-test") _make_vtag(tmp_path, "v4.0.0", cid) result = runner.invoke(cli, ["describe", "--json"], env=_env(tmp_path)) assert result.exit_code == 0 data = json.loads(result.output) assert data["tag"] == "v4.0.0" assert data["distance"] == 0 assert data["description"] == "v4.0.0" assert "commit_id" in data def test_describe_cli_pre_flag(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"rc") _make_vtag(tmp_path, "v5.0.0-rc1", cid) result = runner.invoke(cli, ["describe", "--pre", "--json"], env=_env(tmp_path)) assert result.exit_code == 0 data = json.loads(result.output) assert data["tag"] == "v5.0.0-rc1" # --------------------------------------------------------------------------- # Stress: deep ancestry (100 commits, tag at root) # --------------------------------------------------------------------------- def test_describe_stress_deep_ancestry(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) prev: str | None = None first_commit_id = "" for i in range(100): cid = _make_commit(tmp_path, parent_id=prev, content=f"step {i}".encode()) if i == 0: first_commit_id = cid prev = cid _make_vtag(tmp_path, "v1.0.0", first_commit_id) assert prev is not None result = describe_commit(tmp_path, _REPO_ID, prev) assert result["tag"] == "v1.0.0" assert result["distance"] == 99 class TestRegisterFlags: def test_default_json_out_is_false(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe"]) assert args.json_out is False def test_json_flag_sets_json_out(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe", "--json"]) assert args.json_out is True def test_j_shorthand_sets_json_out(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe", "-j"]) assert args.json_out is True def test_pre_flag_sets_include_pre(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe", "--pre"]) assert args.include_pre is True def test_default_include_pre_is_false(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe"]) assert args.include_pre is False