gabriel / muse public
test_cli_coverage_gaps.py python
316 lines 12.6 KB
Raw
1 """Tests targeting specific coverage gaps in checkout, tag, commit, diff, shelf, branch."""
2
3 import pathlib
4
5 import pytest
6 from tests.cli_test_helper import CliRunner
7
8 cli = None # argparse migration — CliRunner ignores this arg
9 from muse.core.refs import get_head_commit_id
10
11 runner = CliRunner()
12
13
14 @pytest.fixture
15 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
16 monkeypatch.chdir(tmp_path)
17 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
18 result = runner.invoke(cli, ["init"])
19 assert result.exit_code == 0, result.output
20 return tmp_path
21
22
23 def _write(repo: pathlib.Path, filename: str, content: str = "data") -> None:
24 (repo / filename).write_text(content)
25
26
27 def _commit(msg: str = "commit") -> str | None:
28 runner.invoke(cli, ["code", "add", "."])
29 result = runner.invoke(cli, ["commit", "-m", msg])
30 assert result.exit_code == 0, result.output
31 return get_head_commit_id(pathlib.Path("."), "main")
32
33
34 # ---------------------------------------------------------------------------
35 # checkout gaps
36 # ---------------------------------------------------------------------------
37
38
39 class TestCheckoutGaps:
40 def test_create_branch_already_exists_errors(self, repo: pathlib.Path) -> None:
41 result = runner.invoke(cli, ["checkout", "-b", "main"])
42 assert result.exit_code != 0
43 assert "already exists" in result.stderr
44
45 def test_unknown_ref_errors(self, repo: pathlib.Path) -> None:
46 result = runner.invoke(cli, ["checkout", "no-such-branch-or-commit"])
47 assert result.exit_code != 0
48 assert "not a branch" in result.stderr.lower() or "not found" in result.stderr.lower() or "no-such" in result.stderr
49
50 def test_checkout_by_commit_id_detaches_head(self, repo: pathlib.Path) -> None:
51 _write(repo, "beat.py")
52 runner.invoke(cli, ["code", "add", "."])
53 result = runner.invoke(cli, ["commit", "-m", "root"])
54 assert result.exit_code == 0
55 commit_id = get_head_commit_id(repo, "main")
56 assert commit_id is not None
57
58 _write(repo, "lead.py")
59 runner.invoke(cli, ["code", "add", "."])
60 runner.invoke(cli, ["commit", "-m", "second"])
61
62 result = runner.invoke(cli, ["checkout", commit_id])
63 assert result.exit_code == 0
64 assert "HEAD is now at" in result.output
65
66 def test_checkout_restores_workdir_to_target_snapshot(self, repo: pathlib.Path) -> None:
67 _write(repo, "beat.py", "v1")
68 runner.invoke(cli, ["code", "add", "."])
69 result = runner.invoke(cli, ["commit", "-m", "first"])
70 assert result.exit_code == 0
71 first_id = get_head_commit_id(repo, "main")
72 assert first_id is not None
73
74 _write(repo, "lead.py", "new")
75 runner.invoke(cli, ["code", "add", "."])
76 runner.invoke(cli, ["commit", "-m", "second"])
77
78 runner.invoke(cli, ["checkout", first_id])
79 assert (repo / "beat.py").exists()
80 assert not (repo / "lead.py").exists()
81
82
83 # ---------------------------------------------------------------------------
84 # tag gaps
85 # ---------------------------------------------------------------------------
86
87
88 class TestTagGaps:
89 def test_tag_add_unknown_ref_errors(self, repo: pathlib.Path) -> None:
90 _write(repo, "beat.py")
91 runner.invoke(cli, ["code", "add", "."])
92 runner.invoke(cli, ["commit", "-m", "base"])
93 result = runner.invoke(cli, ["tag", "add", "emotion:joyful", "deadbeef"])
94 assert result.exit_code != 0
95
96 def test_tag_list_for_specific_commit(self, repo: pathlib.Path) -> None:
97 _write(repo, "beat.py")
98 runner.invoke(cli, ["code", "add", "."])
99 runner.invoke(cli, ["commit", "-m", "base"])
100 commit_id = get_head_commit_id(repo, "main")
101 assert commit_id is not None
102
103 short = commit_id.removeprefix("sha256:")[:8]
104 runner.invoke(cli, ["tag", "add", "emotion:joyful", short])
105 result = runner.invoke(cli, ["tag", "list", short])
106 assert result.exit_code == 0
107 assert "joyful" in result.output
108
109 def test_tag_list_for_unknown_ref_errors(self, repo: pathlib.Path) -> None:
110 _write(repo, "beat.py")
111 runner.invoke(cli, ["code", "add", "."])
112 runner.invoke(cli, ["commit", "-m", "base"])
113 result = runner.invoke(cli, ["tag", "list", "deadbeef"])
114 assert result.exit_code != 0
115
116 def test_tag_list_all_shows_all_tags(self, repo: pathlib.Path) -> None:
117 _write(repo, "beat.py")
118 runner.invoke(cli, ["code", "add", "."])
119 runner.invoke(cli, ["commit", "-m", "base"])
120 commit_id = get_head_commit_id(repo, "main")
121 assert commit_id is not None
122
123 short = commit_id.removeprefix("sha256:")[:8]
124 runner.invoke(cli, ["tag", "add", "section:verse", short])
125 runner.invoke(cli, ["tag", "add", "emotion:joyful", short])
126 result = runner.invoke(cli, ["tag", "list"])
127 assert result.exit_code == 0
128 assert "section:verse" in result.output
129 assert "emotion:joyful" in result.output
130
131
132 # ---------------------------------------------------------------------------
133 # commit gaps
134 # ---------------------------------------------------------------------------
135
136
137 class TestCommitGaps:
138 def test_no_message_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
139 _write(repo, "beat.py")
140 runner.invoke(cli, ["code", "add", "."])
141 result = runner.invoke(cli, ["commit"])
142 assert result.exit_code != 0
143
144 def test_empty_repo_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
145 # muse init creates .museignore / .museattributes which are tracked by default.
146 # Commit that initial state, then verify a second commit on a clean tree
147 # reports nothing to commit.
148 runner.invoke(cli, ["code", "add", "."])
149 runner.invoke(cli, ["commit", "-m", "init files"])
150 runner.invoke(cli, ["code", "add", "."])
151 result = runner.invoke(cli, ["commit", "-m", "nothing here"])
152 assert result.exit_code == 0
153 assert "Nothing to commit" in result.output or "nothing" in result.output.lower()
154
155 def test_empty_workdir_without_allow_empty_errors(self, repo: pathlib.Path) -> None:
156 # Same as above: commit init-created dotfiles first, then verify clean tree.
157 runner.invoke(cli, ["code", "add", "."])
158 runner.invoke(cli, ["commit", "-m", "init files"])
159 runner.invoke(cli, ["code", "add", "."])
160 result = runner.invoke(cli, ["commit", "-m", "empty"])
161 assert result.exit_code == 0
162 assert "Nothing to commit" in result.output or "nothing" in result.output.lower()
163
164 def test_nothing_to_commit_clean_tree(self, repo: pathlib.Path) -> None:
165 _write(repo, "beat.py")
166 runner.invoke(cli, ["code", "add", "."])
167 runner.invoke(cli, ["commit", "-m", "first"])
168 runner.invoke(cli, ["code", "add", "."])
169 result = runner.invoke(cli, ["commit", "-m", "second"])
170 assert result.exit_code == 0
171 assert "Nothing to commit" in result.output or "clean" in result.output
172
173 def test_commit_with_pending_conflicts_errors(self, repo: pathlib.Path) -> None:
174 _write(repo, "shared.py", "def fn():\n return 1\n")
175 runner.invoke(cli, ["code", "add", "."])
176 runner.invoke(cli, ["commit", "-m", "base"])
177
178 runner.invoke(cli, ["branch", "feature"])
179 runner.invoke(cli, ["checkout", "feature"])
180 _write(repo, "shared.py", "def fn():\n return 2\n")
181 runner.invoke(cli, ["code", "add", "."])
182 runner.invoke(cli, ["commit", "-m", "feature changes"])
183
184 runner.invoke(cli, ["checkout", "main"])
185 _write(repo, "shared.py", "def fn():\n return 3\n")
186 runner.invoke(cli, ["code", "add", "."])
187 runner.invoke(cli, ["commit", "-m", "main changes"])
188
189 runner.invoke(cli, ["merge", "feature"])
190
191 # Now try to commit — should fail because of unresolved conflicts
192 _write(repo, "new.py", "x = 1\n")
193 runner.invoke(cli, ["code", "add", "."])
194 result = runner.invoke(cli, ["commit", "-m", "during conflict"])
195 assert result.exit_code != 0
196 assert "conflict" in result.stderr.lower()
197
198
199 # ---------------------------------------------------------------------------
200 # diff gaps
201 # ---------------------------------------------------------------------------
202
203
204 class TestDiffGaps:
205 def test_diff_two_commits(self, repo: pathlib.Path) -> None:
206 _write(repo, "a.py", "v1")
207 runner.invoke(cli, ["code", "add", "."])
208 runner.invoke(cli, ["commit", "-m", "first"])
209 first_id = get_head_commit_id(repo, "main")
210
211 _write(repo, "a.py", "v2")
212 runner.invoke(cli, ["code", "add", "."])
213 runner.invoke(cli, ["commit", "-m", "second"])
214 second_id = get_head_commit_id(repo, "main")
215
216 assert first_id is not None
217 assert second_id is not None
218 result = runner.invoke(cli, ["diff", first_id, second_id])
219 assert result.exit_code == 0
220 assert "a.py" in result.output
221
222 def test_diff_commit_vs_head(self, repo: pathlib.Path) -> None:
223 _write(repo, "a.py", "v1")
224 runner.invoke(cli, ["code", "add", "."])
225 runner.invoke(cli, ["commit", "-m", "first"])
226 first_id = get_head_commit_id(repo, "main")
227
228 _write(repo, "a.py", "v2")
229 runner.invoke(cli, ["code", "add", "."])
230 runner.invoke(cli, ["commit", "-m", "second"])
231 assert first_id is not None
232
233 result = runner.invoke(cli, ["diff", first_id])
234 assert result.exit_code == 0
235 assert "a.py" in result.output
236
237 def test_diff_stat_flag(self, repo: pathlib.Path) -> None:
238 _write(repo, "a.py")
239 runner.invoke(cli, ["code", "add", "."])
240 runner.invoke(cli, ["commit", "-m", "base"])
241 _write(repo, "b.py")
242 result = runner.invoke(cli, ["diff", "--stat"])
243 assert result.exit_code == 0
244 # --stat prints the structured delta summary line.
245 assert "added" in result.output or "No differences" in result.output
246
247
248 # ---------------------------------------------------------------------------
249 # shelf gaps
250 # ---------------------------------------------------------------------------
251
252
253 class TestShelfGaps:
254 def test_shelf_pop_restores_files(self, repo: pathlib.Path) -> None:
255 _write(repo, "beat.py")
256 runner.invoke(cli, ["code", "add", "."])
257 runner.invoke(cli, ["commit", "-m", "base"])
258 _write(repo, "unsaved.py", "wip")
259
260 runner.invoke(cli, ["shelf", "save"])
261 assert not (repo / "unsaved.py").exists()
262
263 runner.invoke(cli, ["shelf", "pop"])
264 assert (repo / "unsaved.py").exists()
265
266 def test_shelf_drop_removes_entry(self, repo: pathlib.Path) -> None:
267 _write(repo, "beat.py")
268 runner.invoke(cli, ["code", "add", "."])
269 runner.invoke(cli, ["commit", "-m", "base"])
270 _write(repo, "unsaved.py", "wip")
271
272 runner.invoke(cli, ["shelf", "save"])
273 result = runner.invoke(cli, ["shelf", "drop"])
274 assert result.exit_code == 0
275
276 result = runner.invoke(cli, ["shelf", "list"])
277 assert "No shelf entries" in result.output
278
279 def test_shelf_pop_empty_errors(self, repo: pathlib.Path) -> None:
280 result = runner.invoke(cli, ["shelf", "pop"])
281 assert result.exit_code != 0
282
283 def test_shelf_drop_empty_errors(self, repo: pathlib.Path) -> None:
284 result = runner.invoke(cli, ["shelf", "drop"])
285 assert result.exit_code != 0
286
287
288
289 # ---------------------------------------------------------------------------
290 # branch gaps
291 # ---------------------------------------------------------------------------
292
293
294 class TestBranchGaps:
295 def test_delete_branch_with_d_flag(self, repo: pathlib.Path) -> None:
296 runner.invoke(cli, ["branch", "to-delete"])
297 result = runner.invoke(cli, ["branch", "-D", "to-delete"])
298 assert result.exit_code == 0
299
300 def test_delete_current_branch_errors(self, repo: pathlib.Path) -> None:
301 result = runner.invoke(cli, ["branch", "-d", "main"])
302 assert result.exit_code != 0
303 assert "current" in result.stderr.lower() or "main" in result.stderr
304
305 def test_delete_nonexistent_branch_errors(self, repo: pathlib.Path) -> None:
306 result = runner.invoke(cli, ["branch", "-d", "no-such-branch"])
307 assert result.exit_code != 0
308
309 def test_create_branch_with_b_flag(self, repo: pathlib.Path) -> None:
310 result = runner.invoke(cli, ["branch", "new-feature"])
311 assert result.exit_code == 0
312
313 def test_branch_with_slash_shown_in_list(self, repo: pathlib.Path) -> None:
314 runner.invoke(cli, ["branch", "feature/my-thing"])
315 result = runner.invoke(cli, ["branch"])
316 assert "feature/my-thing" in result.output
File History 1 commit