gabriel / muse public

test_core_worktree.py file-level

at sha256:f · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:4 Merge branch 'dev' into main · gabriel · Jun 17, 2026
1 """Tests for muse/core/worktree.py — multiple simultaneous branch checkouts."""
2
3 from __future__ import annotations
4
5 import json
6 import pathlib
7
8 import pytest
9
10 from muse.core.types import NULL_COMMIT_ID
11 from muse.core.worktree import (
12 WorktreeInfo,
13 add_worktree,
14 list_worktrees,
15 prune_worktrees,
16 remove_worktree,
17 )
18 from muse.core.paths import muse_dir, ref_path, worktrees_dir
19
20
21 # ---------------------------------------------------------------------------
22 # Helpers
23 # ---------------------------------------------------------------------------
24
25
26 def _make_repo(tmp_path: pathlib.Path, branch: str = "main") -> pathlib.Path:
27 """Create a minimal Muse repo inside a named subdirectory."""
28 repo = tmp_path / "myproject"
29 muse = muse_dir(repo)
30 for d in ("objects", "commits", "snapshots", "refs/heads"):
31 (muse / d).mkdir(parents=True, exist_ok=True)
32 (muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo"}))
33 (muse / "HEAD").write_text(f"ref: refs/heads/{branch}\n")
34 (muse / "refs" / "heads" / branch).write_text(NULL_COMMIT_ID)
35 return repo
36
37
38 def _add_branch(repo: pathlib.Path, branch: str) -> None:
39 ref = ref_path(repo, branch)
40 ref.parent.mkdir(parents=True, exist_ok=True)
41 ref.write_text(NULL_COMMIT_ID)
42
43
44 # ---------------------------------------------------------------------------
45 # add_worktree
46 # ---------------------------------------------------------------------------
47
48
49 def test_add_worktree_creates_directory(tmp_path: pathlib.Path) -> None:
50 repo = _make_repo(tmp_path)
51 _add_branch(repo, "feat/audio")
52 wt_path = add_worktree(repo, "feat-audio", "feat/audio")
53 assert wt_path.exists()
54 assert (wt_path).exists()
55
56
57 def test_add_worktree_creates_metadata(tmp_path: pathlib.Path) -> None:
58 repo = _make_repo(tmp_path)
59 _add_branch(repo, "dev")
60 add_worktree(repo, "mydev", "dev")
61 meta = worktrees_dir(repo) / "mydev.json"
62 assert meta.exists()
63 data = json.loads(meta.read_text())
64 assert data["name"] == "mydev"
65 assert data["branch"] == "dev"
66
67
68 def test_add_worktree_creates_head_file(tmp_path: pathlib.Path) -> None:
69 repo = _make_repo(tmp_path)
70 _add_branch(repo, "dev")
71 add_worktree(repo, "mydev", "dev")
72 head = worktrees_dir(repo) / "mydev.HEAD"
73 assert head.exists()
74 assert "refs/heads/dev" in head.read_text()
75
76
77 def test_add_worktree_rejects_unknown_branch(tmp_path: pathlib.Path) -> None:
78 repo = _make_repo(tmp_path)
79 with pytest.raises(ValueError, match="does not exist"):
80 add_worktree(repo, "bad", "no-such-branch")
81
82
83 def test_add_worktree_rejects_duplicate_name(tmp_path: pathlib.Path) -> None:
84 repo = _make_repo(tmp_path)
85 _add_branch(repo, "dev")
86 add_worktree(repo, "mydev", "dev")
87 with pytest.raises(ValueError, match="already exists"):
88 add_worktree(repo, "mydev", "dev")
89
90
91 def test_add_worktree_rejects_invalid_name(tmp_path: pathlib.Path) -> None:
92 repo = _make_repo(tmp_path)
93 _add_branch(repo, "dev")
94 with pytest.raises(ValueError):
95 add_worktree(repo, "..", "dev")
96
97
98 def test_add_worktree_directory_name_includes_repo_name(tmp_path: pathlib.Path) -> None:
99 repo = _make_repo(tmp_path)
100 _add_branch(repo, "dev")
101 wt_path = add_worktree(repo, "dev", "dev")
102 assert "myproject" in wt_path.name
103
104
105 # ---------------------------------------------------------------------------
106 # list_worktrees
107 # ---------------------------------------------------------------------------
108
109
110 def test_list_worktrees_includes_main(tmp_path: pathlib.Path) -> None:
111 repo = _make_repo(tmp_path)
112 worktrees = list_worktrees(repo)
113 assert any(wt.is_main for wt in worktrees)
114
115
116 def test_list_worktrees_includes_linked(tmp_path: pathlib.Path) -> None:
117 repo = _make_repo(tmp_path)
118 _add_branch(repo, "dev")
119 add_worktree(repo, "dev", "dev")
120 worktrees = list_worktrees(repo)
121 names = [wt.name for wt in worktrees]
122 assert "dev" in names
123
124
125 def test_list_worktrees_empty_repo(tmp_path: pathlib.Path) -> None:
126 repo = _make_repo(tmp_path)
127 worktrees = list_worktrees(repo)
128 assert len(worktrees) == 1 # only main
129 assert worktrees[0].is_main
130
131
132 # ---------------------------------------------------------------------------
133 # remove_worktree
134 # ---------------------------------------------------------------------------
135
136
137 def test_remove_worktree_removes_directory(tmp_path: pathlib.Path) -> None:
138 repo = _make_repo(tmp_path)
139 _add_branch(repo, "dev")
140 wt_path = add_worktree(repo, "dev", "dev")
141 assert wt_path.exists()
142 remove_worktree(repo, "dev")
143 assert not wt_path.exists()
144
145
146 def test_remove_worktree_removes_metadata(tmp_path: pathlib.Path) -> None:
147 repo = _make_repo(tmp_path)
148 _add_branch(repo, "dev")
149 add_worktree(repo, "dev", "dev")
150 remove_worktree(repo, "dev")
151 meta = worktrees_dir(repo) / "dev.json"
152 assert not meta.exists()
153
154
155 def test_remove_worktree_not_found_raises(tmp_path: pathlib.Path) -> None:
156 repo = _make_repo(tmp_path)
157 with pytest.raises(ValueError, match="does not exist"):
158 remove_worktree(repo, "nonexistent")
159
160
161 def test_remove_worktree_not_in_list_after_removal(tmp_path: pathlib.Path) -> None:
162 repo = _make_repo(tmp_path)
163 _add_branch(repo, "dev")
164 add_worktree(repo, "dev", "dev")
165 remove_worktree(repo, "dev")
166 worktrees = list_worktrees(repo)
167 names = [wt.name for wt in worktrees]
168 assert "dev" not in names
169
170
171 # ---------------------------------------------------------------------------
172 # prune_worktrees
173 # ---------------------------------------------------------------------------
174
175
176 def test_prune_removes_stale_metadata(tmp_path: pathlib.Path) -> None:
177 repo = _make_repo(tmp_path)
178 _add_branch(repo, "dev")
179 wt_path = add_worktree(repo, "dev", "dev")
180 # Manually delete the worktree directory to simulate external removal.
181 import shutil
182 shutil.rmtree(wt_path)
183 pruned = prune_worktrees(repo)
184 assert "dev" in pruned
185
186
187 def test_prune_does_nothing_when_all_present(tmp_path: pathlib.Path) -> None:
188 repo = _make_repo(tmp_path)
189 _add_branch(repo, "dev")
190 add_worktree(repo, "dev", "dev")
191 pruned = prune_worktrees(repo)
192 assert pruned == []
193
194
195 def test_prune_empty_repo(tmp_path: pathlib.Path) -> None:
196 repo = _make_repo(tmp_path)
197 assert prune_worktrees(repo) == []
198
199
200 # ---------------------------------------------------------------------------
201 # Stress: multiple worktrees
202 # ---------------------------------------------------------------------------
203
204
205 def test_stress_many_worktrees(tmp_path: pathlib.Path) -> None:
206 """Creating 10 worktrees should all succeed and be listed."""
207 repo = _make_repo(tmp_path)
208 for i in range(10):
209 branch = f"feat-{i}"
210 _add_branch(repo, branch)
211 add_worktree(repo, f"wt{i}", branch)
212
213 worktrees = list_worktrees(repo)
214 linked = [wt for wt in worktrees if not wt.is_main]
215 assert len(linked) == 10