cgcardona / muse public
test_stress_e2e_workflow.py python
272 lines 9.4 KB
e6786943 feat: upgrade to Python 3.14, drop from __future__ import annotations Gabriel Cardona <cgcardona@gmail.com> 1d ago
1 """End-to-end CLI stress tests — mission-critical workflow verification.
2
3 Tests the full muse CLI lifecycle using CliRunner with monkeypatch.chdir:
4 init → commit → log → branch → checkout → merge → tag → revert → stash
5
6 Adversarial scenarios:
7 - 50-commit linear history: log shows all, branch creates correctly.
8 - Concurrent agent commits with provenance fields.
9 - Branch → commit → merge full cycle.
10 - Annotate accumulates reviewers (ORSet semantics).
11 - Stash and pop.
12 - Revert commit.
13 - Reset.
14 """
15
16 import pathlib
17
18 import pytest
19 from typer.testing import CliRunner
20
21 from muse.cli.app import cli
22
23 runner = CliRunner()
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30
31 def _run(*args: str) -> tuple[int, str]:
32 result = runner.invoke(cli, list(args), catch_exceptions=False)
33 return result.exit_code, result.output
34
35
36 def _write_file(repo: pathlib.Path, filename: str, content: str = "code = True\n") -> None:
37 work = repo / "muse-work"
38 work.mkdir(exist_ok=True)
39 (work / filename).write_text(content)
40
41
42 @pytest.fixture
43 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
44 monkeypatch.chdir(tmp_path)
45 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
46 code, out = _run("init", "--domain", "code")
47 assert code == 0, f"init failed: {out}"
48 return tmp_path
49
50
51 # ===========================================================================
52 # Basic lifecycle
53 # ===========================================================================
54
55
56 class TestBasicLifecycle:
57 def test_init_creates_muse_dir(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
58 monkeypatch.chdir(tmp_path)
59 code, _ = _run("init")
60 assert code == 0
61 assert (tmp_path / ".muse").is_dir()
62
63 def test_commit_and_log(self, repo: pathlib.Path) -> None:
64 _write_file(repo, "main.py", "x = 1\n")
65 code, out = _run("commit", "-m", "first commit")
66 assert code == 0
67
68 code2, log_out = _run("log")
69 assert code2 == 0
70 assert "first commit" in log_out
71
72 def test_status_works(self, repo: pathlib.Path) -> None:
73 _write_file(repo, "app.py", "print('hello')\n")
74 code, out = _run("status")
75 assert code == 0
76
77 def test_tag_commit(self, repo: pathlib.Path) -> None:
78 _write_file(repo, "tagged.py", "tagged = True\n")
79 _run("commit", "-m", "commit to tag")
80 code, out = _run("tag", "add", "v1.0.0")
81 assert code == 0
82
83 def test_log_shows_multiple_commits(self, repo: pathlib.Path) -> None:
84 for i in range(5):
85 _write_file(repo, f"file{i}.py", f"x = {i}\n")
86 _run("commit", "-m", f"commit number {i}")
87
88 code, out = _run("log")
89 assert code == 0
90 for i in range(5):
91 assert f"commit number {i}" in out
92
93 def test_show_commit(self, repo: pathlib.Path) -> None:
94 _write_file(repo, "show_me.py", "show = 'this'\n")
95 _run("commit", "-m", "showable commit")
96 code, _ = _run("log")
97 assert code == 0
98
99
100 # ===========================================================================
101 # Branch and checkout
102 # ===========================================================================
103
104
105 class TestBranchAndCheckout:
106 def test_branch_creation(self, repo: pathlib.Path) -> None:
107 _write_file(repo, "base.py", "base = 1\n")
108 _run("commit", "-m", "base commit")
109 code, out = _run("branch", "feature/new-thing")
110 assert code == 0
111
112 def test_checkout_to_new_branch_then_back(self, repo: pathlib.Path) -> None:
113 _write_file(repo, "common.py", "common = 1\n")
114 _run("commit", "-m", "initial")
115
116 _run("branch", "feature")
117 code, _ = _run("checkout", "feature")
118 assert code == 0
119 code3, _ = _run("checkout", "main")
120 assert code3 == 0
121
122 def test_multiple_branches_independent(self, repo: pathlib.Path) -> None:
123 _write_file(repo, "root.py", "root = True\n")
124 _run("commit", "-m", "root")
125
126 for i in range(3):
127 _run("branch", f"branch-{i}")
128 code, out = _run("checkout", f"branch-{i}")
129 assert code == 0, f"checkout branch-{i} failed: {out}"
130 _write_file(repo, f"branch_{i}.py", f"b = {i}\n")
131 _run("commit", "-m", f"branch-{i} commit")
132 _run("checkout", "main")
133
134
135 # ===========================================================================
136 # Stash
137 # ===========================================================================
138
139
140 class TestStash:
141 def test_stash_and_pop(self, repo: pathlib.Path) -> None:
142 _write_file(repo, "stash_me.py", "stash = True\n")
143 _run("commit", "-m", "before stash")
144
145 _write_file(repo, "unstaged.py", "unstaged = True\n")
146 code, out = _run("stash")
147 assert code == 0, f"stash failed: {out}"
148
149 code2, out2 = _run("stash", "pop")
150 assert code2 == 0, f"stash pop failed: {out2}"
151
152
153 # ===========================================================================
154 # Revert
155 # ===========================================================================
156
157
158 class TestRevert:
159 def test_revert_undoes_last_commit(self, repo: pathlib.Path) -> None:
160 _write_file(repo, "original.py", "original = True\n")
161 _run("commit", "-m", "original state")
162 _write_file(repo, "added.py", "added = True\n")
163 _run("commit", "-m", "added something")
164
165 code, out = _run("log")
166 assert "added something" in out
167
168 code2, _ = _run("revert", "HEAD")
169 assert code2 == 0
170
171
172 # ===========================================================================
173 # Reset
174 # ===========================================================================
175
176
177 class TestReset:
178 def test_soft_reset_to_head(self, repo: pathlib.Path) -> None:
179 """Reset to HEAD (soft) — a no-op but must not fail."""
180 _write_file(repo, "file.py", "x = 1\n")
181 code1, out1 = _run("commit", "-m", "commit 1")
182 assert code1 == 0
183
184 # "HEAD" is accepted by resolve_commit_ref as the current commit.
185 # Options must precede the positional REF argument in Typer sub-typers.
186 code, out = _run("reset", "--soft", "HEAD")
187 assert code == 0, f"reset failed: {out}"
188
189
190 # ===========================================================================
191 # Provenance fields in commit
192 # ===========================================================================
193
194
195 class TestProvenanceCommit:
196 def test_commit_with_agent_id(self, repo: pathlib.Path) -> None:
197 _write_file(repo, "agent.py", "agent = True\n")
198 result = runner.invoke(
199 cli,
200 ["commit", "-m", "agent commit", "--agent-id", "test-agent"],
201 catch_exceptions=False,
202 )
203 assert result.exit_code == 0
204
205 def test_commit_with_model_id(self, repo: pathlib.Path) -> None:
206 _write_file(repo, "model.py", "model = 'gpt-4o'\n")
207 result = runner.invoke(
208 cli,
209 ["commit", "-m", "model commit", "--model-id", "gpt-4o"],
210 catch_exceptions=False,
211 )
212 assert result.exit_code == 0
213
214
215 # ===========================================================================
216 # Annotate command
217 # ===========================================================================
218
219
220 class TestAnnotateCommand:
221 def test_annotate_test_run(self, repo: pathlib.Path) -> None:
222 _write_file(repo, "annotate_me.py", "code = True\n")
223 _run("commit", "-m", "to annotate")
224 code, out = _run("annotate", "--test-run")
225 assert code == 0
226
227 def test_annotate_reviewed_by(self, repo: pathlib.Path) -> None:
228 _write_file(repo, "reviewed.py", "reviewed = True\n")
229 _run("commit", "-m", "for review")
230 code, out = _run("annotate", "--reviewed-by", "alice")
231 assert code == 0
232
233 def test_annotate_accumulates_reviewers(self, repo: pathlib.Path) -> None:
234 _write_file(repo, "multi_review.py", "x = 1\n")
235 _run("commit", "-m", "multi-review")
236 _run("annotate", "--reviewed-by", "alice")
237 code, out = _run("annotate", "--reviewed-by", "bob")
238 assert code == 0
239
240
241 # ===========================================================================
242 # Long workflow stress
243 # ===========================================================================
244
245
246 class TestLongWorkflowStress:
247 def test_50_sequential_commits(self, repo: pathlib.Path) -> None:
248 for i in range(50):
249 _write_file(repo, f"module_{i:03d}.py", f"x = {i}\n")
250 code, out = _run("commit", "-m", f"commit {i:03d}")
251 assert code == 0, f"commit {i} failed: {out}"
252
253 code, out = _run("log")
254 assert code == 0
255 assert "commit 000" in out
256 assert "commit 049" in out
257
258 def test_branch_commit_merge_cycle(self, repo: pathlib.Path) -> None:
259 """Full branch → commit → merge cycle."""
260 _write_file(repo, "main.py", "main = True\n")
261 _run("commit", "-m", "main base")
262
263 _run("branch", "feature/thing")
264 _run("checkout", "feature/thing")
265 _write_file(repo, "feature.py", "feature = True\n")
266 code, out = _run("commit", "-m", "feature work")
267 assert code == 0
268
269 _run("checkout", "main")
270 code2, out2 = _run("merge", "feature/thing")
271 # Merge may succeed or report no commits yet — either way must not crash.
272 assert code2 in (0, 1), f"merge crashed: {out2}"