gabriel / muse public
test_plumbing_merge_base.py python
173 lines 6.0 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
1 """Tests for ``muse plumbing merge-base``.
2
3 Verifies commit-ID resolution, branch-name resolution, HEAD resolution,
4 text-format output, and error handling for unresolvable refs.
5 """
6
7 from __future__ import annotations
8
9 import datetime
10 import hashlib
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.errors import ExitCode
19 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
20 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
21 from muse.core._types import Manifest
22
23 runner = CliRunner()
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30
31 def _sha(tag: str) -> str:
32 return hashlib.sha256(tag.encode()).hexdigest()
33
34
35 def _init_repo(path: pathlib.Path, domain: str = "midi") -> pathlib.Path:
36 muse = path / ".muse"
37 (muse / "commits").mkdir(parents=True)
38 (muse / "snapshots").mkdir(parents=True)
39 (muse / "objects").mkdir(parents=True)
40 (muse / "refs" / "heads").mkdir(parents=True)
41 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
42 (muse / "repo.json").write_text(
43 json.dumps({"repo_id": "test-repo", "domain": domain}), 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 _snap(repo: pathlib.Path) -> str:
53 sid = compute_snapshot_id({})
54 write_snapshot(
55 repo,
56 SnapshotRecord(
57 snapshot_id=sid,
58 manifest={},
59 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
60 ),
61 )
62 return sid
63
64
65 def _commit(
66 repo: pathlib.Path,
67 tag: str,
68 snap_id: str,
69 branch: str = "main",
70 parent: str | None = None,
71 ) -> str:
72 committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
73 parent_ids: list[str] = [parent] if parent else []
74 cid = compute_commit_id(parent_ids, snap_id, tag, committed_at.isoformat())
75 write_commit(
76 repo,
77 CommitRecord(
78 commit_id=cid,
79 repo_id="test-repo",
80 branch=branch,
81 snapshot_id=snap_id,
82 message=tag,
83 committed_at=committed_at,
84 author="tester",
85 parent_commit_id=parent,
86 ),
87 )
88 return cid
89
90
91 def _set_branch(repo: pathlib.Path, branch: str, commit_id: str) -> None:
92 ref = repo / ".muse" / "refs" / "heads" / branch
93 ref.parent.mkdir(parents=True, exist_ok=True)
94 ref.write_text(commit_id, encoding="utf-8")
95
96
97 def _linear_dag(repo: pathlib.Path) -> tuple[str, str, str]:
98 """Build A → B (main) and A → C (feat). Returns (A, B, C)."""
99 sid = _snap(repo)
100 cid_a = _commit(repo, "base", sid)
101 cid_b = _commit(repo, "main-tip", sid, branch="main", parent=cid_a)
102 cid_c = _commit(repo, "feat-tip", sid, branch="feat", parent=cid_a)
103 _set_branch(repo, "main", cid_b)
104 _set_branch(repo, "feat", cid_c)
105 (repo / ".muse" / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
106 return cid_a, cid_b, cid_c
107
108
109 # ---------------------------------------------------------------------------
110 # Tests
111 # ---------------------------------------------------------------------------
112
113
114 class TestMergeBase:
115 def test_finds_common_ancestor_by_commit_id(self, tmp_path: pathlib.Path) -> None:
116 repo = _init_repo(tmp_path)
117 cid_a, cid_b, cid_c = _linear_dag(repo)
118 result = runner.invoke(cli, ["merge-base", cid_b, cid_c], env=_env(repo))
119 assert result.exit_code == 0, result.output
120 data = json.loads(result.stdout)
121 assert data["merge_base"] == cid_a
122 assert data["commit_a"] == cid_b
123 assert data["commit_b"] == cid_c
124
125 def test_branch_names_resolve_to_correct_base(self, tmp_path: pathlib.Path) -> None:
126 repo = _init_repo(tmp_path)
127 cid_a, _b, _c = _linear_dag(repo)
128 result = runner.invoke(cli, ["merge-base", "main", "feat"], env=_env(repo))
129 assert result.exit_code == 0, result.output
130 assert json.loads(result.stdout)["merge_base"] == cid_a
131
132 def test_head_resolves_to_current_branch(self, tmp_path: pathlib.Path) -> None:
133 repo = _init_repo(tmp_path)
134 cid_a, _b, _c = _linear_dag(repo)
135 result = runner.invoke(cli, ["merge-base", "HEAD", "feat"], env=_env(repo))
136 assert result.exit_code == 0, result.output
137 assert json.loads(result.stdout)["merge_base"] == cid_a
138
139 def test_same_commit_returns_itself(self, tmp_path: pathlib.Path) -> None:
140 repo = _init_repo(tmp_path)
141 sid = _snap(repo)
142 cid = _commit(repo, "solo", sid)
143 _set_branch(repo, "main", cid)
144 result = runner.invoke(cli, ["merge-base", cid, cid], env=_env(repo))
145 assert result.exit_code == 0, result.output
146 assert json.loads(result.stdout)["merge_base"] == cid
147
148 def test_text_format_emits_bare_commit_id(self, tmp_path: pathlib.Path) -> None:
149 repo = _init_repo(tmp_path)
150 cid_a, cid_b, cid_c = _linear_dag(repo)
151 result = runner.invoke(
152 cli, ["merge-base", "--format", "text", cid_b, cid_c], env=_env(repo)
153 )
154 assert result.exit_code == 0, result.output
155 assert cid_a in result.stdout
156
157 def test_unresolvable_ref_a_exits_user_error(self, tmp_path: pathlib.Path) -> None:
158 repo = _init_repo(tmp_path)
159 result = runner.invoke(
160 cli, ["merge-base", "no-such-branch", "also-missing"], env=_env(repo)
161 )
162 assert result.exit_code == ExitCode.USER_ERROR
163 assert "error" in json.loads(result.stdout)
164
165 def test_bad_format_flag_exits_user_error(self, tmp_path: pathlib.Path) -> None:
166 repo = _init_repo(tmp_path)
167 sid = _snap(repo)
168 cid = _commit(repo, "c", sid)
169 _set_branch(repo, "main", cid)
170 result = runner.invoke(
171 cli, ["merge-base", "--format", "yaml", cid, cid], env=_env(repo)
172 )
173 assert result.exit_code == ExitCode.USER_ERROR
File History 3 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 26 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 100 days ago