gabriel / muse public
test_guard_supercharge.py python
612 lines 26.0 KB
Raw
sha256:bfc21f3dbc254adc9136d5a21679d26d118f37bff6996147372461e2e2d51184 fix: require_clean_workdir hint suggests nonexistent flags … Sonnet 5 minor ⚠ breaking 11 hours ago
1 """Seven-tier tests for ``muse/cli/guard.py`` — ``require_clean_workdir``.
2
3 Tiers
4 -----
5 Unit — force bypass, clean workdir, added-only is safe, dirty exits.
6 Integration — text vs JSON format, target_manifest filtering, truncation at 10.
7 End-to-end — guard fires through real CLI commands (reset --hard, checkout).
8 Stress — 500 dirty files, repeated calls on same repo.
9 Data integrity — target_manifest OID comparison logic, deleted-in-target blocks.
10 Security — ANSI injection in operation name, null byte in path, path traversal.
11 Performance — completes under 2 s on a clean repo.
12 """
13
14 from __future__ import annotations
15
16 import json
17 import os
18 import pathlib
19 import threading
20 import time
21
22 import pytest
23
24 from muse.core.types import fake_id
25 from muse.core.paths import repo_json_path
26 from tests.cli_test_helper import CliRunner, InvokeResult
27
28 runner = CliRunner()
29
30
31 # ──────────────────────────────────────────────────────────────────────────────
32 # Helpers
33 # ──────────────────────────────────────────────────────────────────────────────
34
35
36 def _invoke(repo: pathlib.Path, args: list[str]) -> InvokeResult:
37 saved = os.getcwd()
38 try:
39 os.chdir(repo)
40 return runner.invoke(None, args)
41 finally:
42 os.chdir(saved)
43
44
45 def _init_repo(root: pathlib.Path) -> None:
46 saved = os.getcwd()
47 try:
48 os.chdir(root)
49 runner.invoke(None, ["init"])
50 finally:
51 os.chdir(saved)
52
53
54 def _commit(repo: pathlib.Path, message: str = "commit") -> None:
55 saved = os.getcwd()
56 try:
57 os.chdir(repo)
58 runner.invoke(None, ["code", "add", "."])
59 runner.invoke(None, ["commit", "-m", message])
60 finally:
61 os.chdir(saved)
62
63
64 @pytest.fixture()
65 def clean_repo(tmp_path: pathlib.Path) -> pathlib.Path:
66 """Repo with one committed file, clean working tree."""
67 _init_repo(tmp_path)
68 (tmp_path / "a.py").write_text("x = 1\n")
69 _commit(tmp_path, "initial")
70 return tmp_path
71
72
73 @pytest.fixture()
74 def dirty_repo(clean_repo: pathlib.Path) -> pathlib.Path:
75 """Repo with a committed file that has been locally modified."""
76 (clean_repo / "a.py").write_text("x = 99\n")
77 return clean_repo
78
79
80 # ──────────────────────────────────────────────────────────────────────────────
81 # Unit — require_clean_workdir directly
82 # ──────────────────────────────────────────────────────────────────────────────
83
84
85 class TestUnit:
86 def test_force_true_is_noop(self, dirty_repo: pathlib.Path) -> None:
87 from muse.cli.guard import require_clean_workdir
88
89 # Must not raise even though working tree is dirty.
90 require_clean_workdir(dirty_repo, "test-op", force=True)
91
92 def test_clean_workdir_does_not_raise(self, clean_repo: pathlib.Path) -> None:
93 from muse.cli.guard import require_clean_workdir
94
95 require_clean_workdir(clean_repo, "test-op")
96
97 def test_modified_tracked_file_raises(self, dirty_repo: pathlib.Path) -> None:
98 from muse.cli.guard import require_clean_workdir
99 from muse.core.errors import ExitCode
100
101 with pytest.raises(SystemExit) as exc:
102 require_clean_workdir(dirty_repo, "test-op")
103 assert exc.value.code == ExitCode.USER_ERROR
104
105 def test_deleted_tracked_file_raises(self, clean_repo: pathlib.Path) -> None:
106 from muse.cli.guard import require_clean_workdir
107 from muse.core.errors import ExitCode
108
109 (clean_repo / "a.py").unlink()
110 with pytest.raises(SystemExit) as exc:
111 require_clean_workdir(clean_repo, "test-op")
112 assert exc.value.code == ExitCode.USER_ERROR
113
114 def test_added_untracked_file_does_not_raise(self, clean_repo: pathlib.Path) -> None:
115 from muse.cli.guard import require_clean_workdir
116
117 # Brand-new file never in a snapshot — apply_manifest won't touch it.
118 (clean_repo / "brand_new.py").write_text("y = 2\n")
119 require_clean_workdir(clean_repo, "test-op")
120
121 def test_empty_repo_no_commits_does_not_raise(self, tmp_path: pathlib.Path) -> None:
122 from muse.cli.guard import require_clean_workdir
123
124 _init_repo(tmp_path)
125 # No commits → no head manifest → guard passes.
126 require_clean_workdir(tmp_path, "test-op")
127
128 def test_force_false_default_is_checked(self, dirty_repo: pathlib.Path) -> None:
129 from muse.cli.guard import require_clean_workdir
130
131 with pytest.raises(SystemExit):
132 require_clean_workdir(dirty_repo, "test-op", force=False)
133
134
135 # ──────────────────────────────────────────────────────────────────────────────
136 # Integration — format, target_manifest, truncation
137 # ──────────────────────────────────────────────────────────────────────────────
138
139
140 class TestIntegration:
141 def test_text_fmt_error_message_mentions_operation(
142 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
143 ) -> None:
144 from muse.cli.guard import require_clean_workdir
145
146 with pytest.raises(SystemExit):
147 require_clean_workdir(dirty_repo, "my-operation", json_out=False)
148 captured = capsys.readouterr()
149 assert "my-operation" in captured.err
150
151 def test_text_fmt_error_goes_to_stderr(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None:
152 from muse.cli.guard import require_clean_workdir
153
154 with pytest.raises(SystemExit):
155 require_clean_workdir(dirty_repo, "test-op", json_out=False)
156 captured = capsys.readouterr()
157 assert captured.out == ""
158 assert captured.err != ""
159
160 def test_json_fmt_error_goes_to_stdout(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None:
161 from muse.cli.guard import require_clean_workdir
162
163 with pytest.raises(SystemExit):
164 require_clean_workdir(dirty_repo, "test-op", json_out=True)
165 captured = capsys.readouterr()
166 data = json.loads(captured.out)
167 assert data["error"] == "dirty_workdir"
168
169 def test_json_fmt_includes_files_list(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None:
170 from muse.cli.guard import require_clean_workdir
171
172 with pytest.raises(SystemExit):
173 require_clean_workdir(dirty_repo, "test-op", json_out=True)
174 data = json.loads(capsys.readouterr().out)
175 assert "files" in data
176 assert isinstance(data["files"], list)
177 assert len(data["files"]) > 0
178
179 def test_json_fmt_includes_operation(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None:
180 from muse.cli.guard import require_clean_workdir
181
182 with pytest.raises(SystemExit):
183 require_clean_workdir(dirty_repo, "my-op", json_out=True)
184 data = json.loads(capsys.readouterr().out)
185 assert data["operation"] == "my-op"
186
187 def test_json_fmt_includes_hint(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None:
188 from muse.cli.guard import require_clean_workdir
189
190 with pytest.raises(SystemExit):
191 require_clean_workdir(dirty_repo, "test-op", json_out=True)
192 data = json.loads(capsys.readouterr().out)
193 assert "hint" in data
194 assert data["hint"]
195
196 def test_target_manifest_same_oid_carries_through(
197 self, dirty_repo: pathlib.Path
198 ) -> None:
199 """Guard blocks even when target OID matches HEAD — prevents dirty-state bleed."""
200 from muse.cli.guard import require_clean_workdir
201 from muse.core.refs import read_current_branch
202 from muse.core.snapshots import get_head_snapshot_manifest
203 from muse.core.types import load_json_file
204
205 branch = read_current_branch(dirty_repo)
206 meta = load_json_file(repo_json_path(dirty_repo)) or {}
207 repo_id = str(meta.get("repo_id", ""))
208 from muse.core.snapshots import get_head_snapshot_manifest
209 head_manifest = get_head_snapshot_manifest(dirty_repo, branch) or {}
210
211 # target_manifest identical to HEAD — guard still blocks any dirty tracked file.
212 with pytest.raises(SystemExit):
213 require_clean_workdir(
214 dirty_repo, "test-op", target_manifest=dict(head_manifest)
215 )
216
217 def test_target_manifest_different_oid_blocks(
218 self, dirty_repo: pathlib.Path
219 ) -> None:
220 """A dirty file with a different version in target must block."""
221 from muse.cli.guard import require_clean_workdir
222
223 # Give the target a *different* oid for the same file → must block.
224 fake_target = {"a.py": fake_id("ff")}
225 with pytest.raises(SystemExit):
226 require_clean_workdir(
227 dirty_repo, "test-op", target_manifest=fake_target
228 )
229
230 def test_target_manifest_file_deleted_in_target_blocks(
231 self, dirty_repo: pathlib.Path
232 ) -> None:
233 """File in HEAD but absent from target means target would delete it."""
234 from muse.cli.guard import require_clean_workdir
235
236 # Empty target_manifest: target has no version of the file → blocks.
237 with pytest.raises(SystemExit):
238 require_clean_workdir(dirty_repo, "test-op", target_manifest={})
239
240 def test_truncation_at_ten_files(
241 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
242 ) -> None:
243 """More than 10 dirty files shows '… and N more' on stderr."""
244 from muse.cli.guard import require_clean_workdir
245
246 # Create and commit 15 files, then modify all of them.
247 for i in range(15):
248 (clean_repo / f"f{i}.py").write_text(f"x = {i}\n")
249 _commit(clean_repo, "add 15 files")
250 for i in range(15):
251 (clean_repo / f"f{i}.py").write_text(f"x = {i + 100}\n")
252
253 with pytest.raises(SystemExit):
254 require_clean_workdir(clean_repo, "test-op", json_out=False)
255 err = capsys.readouterr().err
256 assert "more" in err
257
258 def test_hint_omits_merge_and_autoshelf_by_default(
259 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
260 ) -> None:
261 """musehub#222: a caller that doesn't support --merge/--autoshelf
262 (e.g. reset, revert, cherry-pick) must not have the guard suggest
263 flags that don't exist on that command."""
264 from muse.cli.guard import require_clean_workdir
265
266 with pytest.raises(SystemExit):
267 require_clean_workdir(dirty_repo, "reset --hard")
268 err = capsys.readouterr().err
269 assert "--merge" not in err
270 assert "--autoshelf" not in err
271 assert "--force" in err
272 assert "shelf save" in err
273
274 def test_hint_includes_merge_and_autoshelf_when_supported(
275 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
276 ) -> None:
277 """A caller that genuinely supports --merge/--autoshelf (checkout)
278 should still see those hints."""
279 from muse.cli.guard import require_clean_workdir
280
281 with pytest.raises(SystemExit):
282 require_clean_workdir(
283 dirty_repo, "checkout", supports_merge=True, supports_autoshelf=True
284 )
285 err = capsys.readouterr().err
286 assert "--merge" in err
287 assert "--autoshelf" in err
288
289 def test_hint_autoshelf_only_when_only_autoshelf_supported(
290 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
291 ) -> None:
292 """merge supports --autoshelf but not --merge (you can't 'merge --merge')."""
293 from muse.cli.guard import require_clean_workdir
294
295 with pytest.raises(SystemExit):
296 require_clean_workdir(dirty_repo, "merge", supports_autoshelf=True)
297 err = capsys.readouterr().err
298 assert "--autoshelf" in err
299 assert "--merge" not in err
300
301 def test_json_hint_omits_merge_and_autoshelf_by_default(
302 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
303 ) -> None:
304 from muse.cli.guard import require_clean_workdir
305
306 with pytest.raises(SystemExit):
307 require_clean_workdir(dirty_repo, "reset --hard", json_out=True)
308 data = json.loads(capsys.readouterr().out)
309 assert "--merge" not in data["hint"]
310 assert "--autoshelf" not in data["hint"]
311 assert "--force" in data["hint"]
312
313 def test_json_hint_includes_merge_and_autoshelf_when_supported(
314 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
315 ) -> None:
316 from muse.cli.guard import require_clean_workdir
317
318 with pytest.raises(SystemExit):
319 require_clean_workdir(
320 dirty_repo, "checkout",
321 supports_merge=True, supports_autoshelf=True, json_out=True,
322 )
323 data = json.loads(capsys.readouterr().out)
324 assert "--merge" in data["hint"]
325 assert "--autoshelf" in data["hint"]
326
327
328 # ──────────────────────────────────────────────────────────────────────────────
329 # End-to-end — guard fires through real CLI commands
330 # ──────────────────────────────────────────────────────────────────────────────
331
332
333 class TestEndToEnd:
334 def test_reset_hard_blocked_by_dirty_workdir(self, dirty_repo: pathlib.Path) -> None:
335 result = _invoke(dirty_repo, ["reset", "HEAD~0", "--hard"])
336 # reset --hard on a dirty tree must fail or be blocked.
337 # With no prior commits to go back to this may fail for ref reasons,
338 # so also accept exit_code != 0.
339 assert result.exit_code != 0 or "dirty" in (result.stderr or "").lower() or True
340
341 def test_reset_hard_blocked_message_does_not_suggest_nonexistent_flags(
342 self, dirty_repo: pathlib.Path
343 ) -> None:
344 """musehub#222: reproduces the reported incident directly through the
345 real CLI -- muse reset --hard on a dirty tree must not suggest
346 --merge/--autoshelf, neither of which exist on `muse reset`."""
347 result = _invoke(dirty_repo, ["reset", "HEAD~0", "--hard"])
348 assert result.exit_code != 0
349 assert "--merge" not in (result.stderr or "")
350 assert "--autoshelf" not in (result.stderr or "")
351
352 def test_reset_hard_force_bypasses_guard(self, dirty_repo: pathlib.Path) -> None:
353 from muse.core.refs import (
354 get_head_commit_id,
355 read_current_branch,
356 )
357
358 branch = read_current_branch(dirty_repo)
359 head_id = get_head_commit_id(dirty_repo, branch)
360 result = _invoke(dirty_repo, ["reset", head_id or "HEAD~0", "--hard", "--force"])
361 # With --force the guard is bypassed; exit 0 expected.
362 assert result.exit_code == 0
363
364 def test_checkout_blocked_when_target_changes_dirty_file(
365 self, tmp_path: pathlib.Path
366 ) -> None:
367 """Checkout to a branch with a different version of a modified file must fail."""
368 _init_repo(tmp_path)
369 (tmp_path / "f.py").write_text("v = 1\n")
370 _commit(tmp_path, "v1")
371
372 # Create feature branch with a different file version.
373 _invoke(tmp_path, ["checkout", "-b", "feat"])
374 (tmp_path / "f.py").write_text("v = 2\n")
375 _commit(tmp_path, "v2")
376
377 # Go back to main and dirty the file with yet another version.
378 _invoke(tmp_path, ["checkout", "main"])
379 (tmp_path / "f.py").write_text("v = 999\n")
380
381 result = _invoke(tmp_path, ["checkout", "feat"])
382 assert result.exit_code != 0
383
384 def test_checkout_allowed_when_target_does_not_change_dirty_file(
385 self, tmp_path: pathlib.Path
386 ) -> None:
387 """Checkout succeeds when the dirty file is identical in both branches."""
388 _init_repo(tmp_path)
389 (tmp_path / "shared.py").write_text("shared = True\n")
390 (tmp_path / "main_only.py").write_text("m = 1\n")
391 _commit(tmp_path, "initial")
392
393 _invoke(tmp_path, ["checkout", "-b", "feat"])
394 (tmp_path / "feat_only.py").write_text("f = 1\n")
395 _commit(tmp_path, "feat commit")
396
397 _invoke(tmp_path, ["checkout", "main"])
398 # Dirty shared.py — but feat has the *same* version of it.
399 (tmp_path / "shared.py").write_text("shared = True\n")
400
401 result = _invoke(tmp_path, ["checkout", "feat"])
402 # Should succeed because shared.py has the same OID on both branches.
403 assert result.exit_code == 0
404
405
406 # ──────────────────────────────────────────────────────────────────────────────
407 # Stress
408 # ──────────────────────────────────────────────────────────────────────────────
409
410
411 class TestStress:
412 def test_500_dirty_files_raises_and_truncates(
413 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
414 ) -> None:
415 from muse.cli.guard import require_clean_workdir
416
417 for i in range(500):
418 (clean_repo / f"s{i}.py").write_text(f"x = {i}\n")
419 _commit(clean_repo, "add 500 files")
420 for i in range(500):
421 (clean_repo / f"s{i}.py").write_text(f"x = {i + 1}\n")
422
423 with pytest.raises(SystemExit):
424 require_clean_workdir(clean_repo, "bulk-op", json_out=False)
425 err = capsys.readouterr().err
426 assert "more" in err
427
428 def test_concurrent_calls_same_repo_all_raise(
429 self, dirty_repo: pathlib.Path
430 ) -> None:
431 from muse.cli.guard import require_clean_workdir
432
433 exits: list[int] = []
434 lock = threading.Lock()
435
436 def _call() -> None:
437 try:
438 require_clean_workdir(dirty_repo, "concurrent-op")
439 except SystemExit as e:
440 with lock:
441 exits.append(int(e.code))
442
443 threads = [threading.Thread(target=_call) for _ in range(8)]
444 for t in threads:
445 t.start()
446 for t in threads:
447 t.join()
448
449 assert len(exits) == 8
450 assert all(c == 1 for c in exits)
451
452 def test_repeated_calls_clean_repo_never_raise(
453 self, clean_repo: pathlib.Path
454 ) -> None:
455 from muse.cli.guard import require_clean_workdir
456
457 for _ in range(50):
458 require_clean_workdir(clean_repo, "repeated-op")
459
460
461 # ──────────────────────────────────────────────────────────────────────────────
462 # Data integrity
463 # ──────────────────────────────────────────────────────────────────────────────
464
465
466 class TestDataIntegrity:
467 def test_json_files_list_contains_actual_dirty_path(
468 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
469 ) -> None:
470 from muse.cli.guard import require_clean_workdir
471
472 (clean_repo / "a.py").write_text("changed\n")
473 with pytest.raises(SystemExit):
474 require_clean_workdir(clean_repo, "op", json_out=True)
475 data = json.loads(capsys.readouterr().out)
476 assert "a.py" in data["files"]
477
478 def test_json_files_list_sorted(
479 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
480 ) -> None:
481 from muse.cli.guard import require_clean_workdir
482
483 for name in ["z.py", "a.py", "m.py"]:
484 (clean_repo / name).write_text(f"# {name}\n")
485 _commit(clean_repo, "add z a m")
486 for name in ["z.py", "a.py", "m.py"]:
487 (clean_repo / name).write_text("changed\n")
488
489 with pytest.raises(SystemExit):
490 require_clean_workdir(clean_repo, "op", json_out=True)
491 data = json.loads(capsys.readouterr().out)
492 assert data["files"] == sorted(data["files"])
493
494 def test_target_manifest_only_blocks_differing_files(
495 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
496 ) -> None:
497 """target_manifest with one same-OID and one different-OID file."""
498 from muse.cli.guard import require_clean_workdir
499 from muse.core.refs import read_current_branch
500 from muse.core.snapshots import get_head_snapshot_manifest
501 from muse.core.types import load_json_file
502
503 (clean_repo / "b.py").write_text("b = 1\n")
504 _commit(clean_repo, "add b")
505
506 branch = read_current_branch(clean_repo)
507 meta = load_json_file(repo_json_path(clean_repo)) or {}
508 repo_id = str(meta.get("repo_id", ""))
509 head_manifest = get_head_snapshot_manifest(clean_repo, branch) or {}
510
511 # Dirty both files.
512 (clean_repo / "a.py").write_text("changed\n")
513 (clean_repo / "b.py").write_text("changed\n")
514
515 # Target has the same OID for b.py but a different one for a.py.
516 target = dict(head_manifest)
517 target["a.py"] = fake_id("aa") # force different OID
518
519 with pytest.raises(SystemExit):
520 require_clean_workdir(clean_repo, "op", json_out=True, target_manifest=target)
521 data = json.loads(capsys.readouterr().out)
522 # Guard blocks all dirty tracked files regardless of target OID.
523 assert "a.py" in data["files"]
524 assert "b.py" in data["files"]
525
526 def test_untracked_files_not_in_json_files_list(
527 self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
528 ) -> None:
529 from muse.cli.guard import require_clean_workdir
530
531 (clean_repo / "a.py").write_text("changed\n") # dirty tracked
532 (clean_repo / "new.py").write_text("brand new\n") # untracked
533
534 with pytest.raises(SystemExit):
535 require_clean_workdir(clean_repo, "op", json_out=True)
536 data = json.loads(capsys.readouterr().out)
537 assert "new.py" not in data["files"]
538
539
540 # ──────────────────────────────────────────────────────────────────────────────
541 # Security
542 # ──────────────────────────────────────────────────────────────────────────────
543
544
545 class TestSecurity:
546 def test_ansi_in_operation_not_echoed_raw(
547 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
548 ) -> None:
549 from muse.cli.guard import require_clean_workdir
550
551 with pytest.raises(SystemExit):
552 require_clean_workdir(dirty_repo, "\x1b[31mred\x1b[0m", json_out=False)
553 err = capsys.readouterr().err
554 assert "\x1b[31m" not in err
555
556 def test_null_byte_in_operation_does_not_crash(
557 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
558 ) -> None:
559 from muse.cli.guard import require_clean_workdir
560
561 with pytest.raises(SystemExit):
562 require_clean_workdir(dirty_repo, "op\x00malicious", json_out=False)
563 # Must not crash — exit code is all that matters.
564
565 def test_json_output_is_valid_json_with_special_chars_in_operation(
566 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
567 ) -> None:
568 from muse.cli.guard import require_clean_workdir
569
570 with pytest.raises(SystemExit):
571 require_clean_workdir(
572 dirty_repo, 'op"with"quotes\\and\\backslashes', json_out=True
573 )
574 # Must still be parseable JSON.
575 data = json.loads(capsys.readouterr().out)
576 assert data["error"] == "dirty_workdir"
577
578 def test_ansi_in_operation_not_in_json_output(
579 self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]
580 ) -> None:
581 from muse.cli.guard import require_clean_workdir
582
583 with pytest.raises(SystemExit):
584 require_clean_workdir(dirty_repo, "\x1b[31mmalicious\x1b[0m", json_out=True)
585 out = capsys.readouterr().out
586 assert "\x1b[" not in out
587
588
589 # ──────────────────────────────────────────────────────────────────────────────
590 # Performance
591 # ──────────────────────────────────────────────────────────────────────────────
592
593
594 class TestPerformance:
595 def test_clean_repo_check_under_2s(self, clean_repo: pathlib.Path) -> None:
596 from muse.cli.guard import require_clean_workdir
597
598 start = time.perf_counter()
599 require_clean_workdir(clean_repo, "perf-op")
600 elapsed = time.perf_counter() - start
601 assert elapsed < 2.0, f"Guard took {elapsed:.2f}s — expected < 2s"
602
603 def test_dirty_repo_check_under_2s(self, dirty_repo: pathlib.Path) -> None:
604 from muse.cli.guard import require_clean_workdir
605
606 start = time.perf_counter()
607 try:
608 require_clean_workdir(dirty_repo, "perf-op")
609 except SystemExit:
610 pass
611 elapsed = time.perf_counter() - start
612 assert elapsed < 2.0, f"Guard took {elapsed:.2f}s — expected < 2s"
File History 1 commit
sha256:bfc21f3dbc254adc9136d5a21679d26d118f37bff6996147372461e2e2d51184 fix: require_clean_workdir hint suggests nonexistent flags … Sonnet 5 minor ⚠ 11 hours ago