gabriel / muse public
test_clones_supercharge.py python
340 lines 13.4 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
1 """Supercharge tests for ``muse code clones`` — agent-usability gaps.
2
3 The existing test_cmd_clones.py covers correctness, JSON schema, flags, E2E,
4 and stress. This file targets only the gaps those tests leave open:
5
6 Coverage matrix
7 ---------------
8 - --json / -j: -j alias works identically to --json
9 - exit_code: JSON output includes exit_code = 0 on success
10 - duration_ms: JSON output includes non-negative float duration_ms
11 - TypedDicts: _ClonesOutputJson gains exit_code/duration_ms annotations
12 - Docstrings: run() docstring mentions exit_code and duration_ms
13 - ANSI: JSON output never contains terminal escape sequences
14 - Performance: duration_ms stays under 2000 ms for a small repo
15 """
16
17 from __future__ import annotations
18 from collections.abc import Mapping
19
20 import json
21 import pathlib
22 import textwrap
23
24 import pytest
25
26 from tests.cli_test_helper import CliRunner
27
28 runner = CliRunner()
29
30
31 # ---------------------------------------------------------------------------
32 # Helpers
33 # ---------------------------------------------------------------------------
34
35
36 def _env(root: pathlib.Path) -> Mapping[str, str]:
37 return {"MUSE_REPO_ROOT": str(root)}
38
39
40 def _run(root: pathlib.Path, *args: str) -> "InvokeResult":
41 return runner.invoke(None, list(args), env=_env(root))
42
43
44 # ---------------------------------------------------------------------------
45 # Fixture — repo with exact and near clones committed
46 # ---------------------------------------------------------------------------
47
48
49 @pytest.fixture()
50 def clones_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
51 """Code-domain repo with committed duplicate symbols.
52
53 billing.py and payments.py both define compute_total with identical bodies
54 (exact clone). validate() in both files shares the same signature but has
55 different bodies (near-clone).
56 """
57 monkeypatch.chdir(tmp_path)
58
59 r = _run(tmp_path, "init", "--domain", "code")
60 assert r.exit_code == 0, r.output
61
62 (tmp_path / "billing.py").write_text(textwrap.dedent("""\
63 def compute_total(items):
64 return sum(items)
65
66 def validate(value):
67 if value is None:
68 raise ValueError("billing: value required")
69 return True
70 """))
71 (tmp_path / "payments.py").write_text(textwrap.dedent("""\
72 def compute_total(items):
73 return sum(items)
74
75 def validate(value):
76 if not isinstance(value, (int, float)):
77 raise TypeError("payments: numeric value required")
78 return True
79 """))
80
81 r1 = _run(tmp_path, "code", "add", "billing.py")
82 assert r1.exit_code == 0, r1.output
83 r2 = _run(tmp_path, "code", "add", "payments.py")
84 assert r2.exit_code == 0, r2.output
85 r3 = _run(tmp_path, "commit", "-m", "add billing and payments with clones")
86 assert r3.exit_code == 0, r3.output
87
88 return tmp_path
89
90
91 # ---------------------------------------------------------------------------
92 # TestJsonAlias — -j works identically to --json
93 # ---------------------------------------------------------------------------
94
95
96 class TestJsonAlias:
97 """-j shorthand must behave identically to --json."""
98
99 def test_j_alias_exits_zero(self, clones_repo: pathlib.Path) -> None:
100 r = _run(clones_repo, "code", "clones", "-j")
101 assert r.exit_code == 0, r.output
102
103 def test_j_alias_valid_json(self, clones_repo: pathlib.Path) -> None:
104 r = _run(clones_repo, "code", "clones", "-j")
105 json.loads(r.output) # must not raise
106
107 def test_j_alias_has_clusters_key(self, clones_repo: pathlib.Path) -> None:
108 r = _run(clones_repo, "code", "clones", "-j")
109 data = json.loads(r.output)
110 assert "clusters" in data
111
112 def test_j_alias_has_commit_key(self, clones_repo: pathlib.Path) -> None:
113 r = _run(clones_repo, "code", "clones", "-j")
114 data = json.loads(r.output)
115 assert "commit" in data
116
117 def test_j_alias_same_top_level_keys_as_json_flag(self, clones_repo: pathlib.Path) -> None:
118 r1 = _run(clones_repo, "code", "clones", "--json")
119 r2 = _run(clones_repo, "code", "clones", "-j")
120 d1 = json.loads(r1.output)
121 d2 = json.loads(r2.output)
122 d1.pop("duration_ms", None)
123 d2.pop("duration_ms", None)
124 assert set(d1.keys()) == set(d2.keys())
125
126 def test_j_alias_same_cluster_count(self, clones_repo: pathlib.Path) -> None:
127 r1 = _run(clones_repo, "code", "clones", "--json")
128 r2 = _run(clones_repo, "code", "clones", "-j")
129 assert json.loads(r1.output)["exact_clone_clusters"] == \
130 json.loads(r2.output)["exact_clone_clusters"]
131
132 def test_j_alias_with_tier_exact(self, clones_repo: pathlib.Path) -> None:
133 r = _run(clones_repo, "code", "clones", "--tier", "exact", "-j")
134 assert r.exit_code == 0, r.output
135 data = json.loads(r.output)
136 assert data["tier"] == "exact"
137
138 def test_j_alias_with_tier_near(self, clones_repo: pathlib.Path) -> None:
139 r = _run(clones_repo, "code", "clones", "--tier", "near", "-j")
140 assert r.exit_code == 0, r.output
141 data = json.loads(r.output)
142 assert data["tier"] == "near"
143
144
145 # ---------------------------------------------------------------------------
146 # TestDurationMs — JSON output must include duration_ms
147 # ---------------------------------------------------------------------------
148
149
150 class TestDurationMs:
151 """JSON output must include a non-negative float duration_ms."""
152
153 def test_json_has_duration_ms(self, clones_repo: pathlib.Path) -> None:
154 r = _run(clones_repo, "code", "clones", "--json")
155 data = json.loads(r.output)
156 assert "duration_ms" in data
157
158 def test_json_duration_ms_nonnegative(self, clones_repo: pathlib.Path) -> None:
159 r = _run(clones_repo, "code", "clones", "--json")
160 data = json.loads(r.output)
161 assert data["duration_ms"] >= 0
162
163 def test_json_duration_ms_is_float(self, clones_repo: pathlib.Path) -> None:
164 r = _run(clones_repo, "code", "clones", "--json")
165 data = json.loads(r.output)
166 assert isinstance(data["duration_ms"], float)
167
168 def test_j_alias_duration_ms_present(self, clones_repo: pathlib.Path) -> None:
169 r = _run(clones_repo, "code", "clones", "-j")
170 data = json.loads(r.output)
171 assert "duration_ms" in data
172
173 def test_duration_ms_with_tier_exact(self, clones_repo: pathlib.Path) -> None:
174 r = _run(clones_repo, "code", "clones", "--json", "--tier", "exact")
175 data = json.loads(r.output)
176 assert "duration_ms" in data
177 assert data["duration_ms"] >= 0
178
179 def test_duration_ms_with_no_clones(self, tmp_path: pathlib.Path,
180 monkeypatch: pytest.MonkeyPatch) -> None:
181 """duration_ms is present even when no clones are found."""
182 monkeypatch.chdir(tmp_path)
183 r = _run(tmp_path, "init", "--domain", "code")
184 assert r.exit_code == 0
185 (tmp_path / "solo.py").write_text("def unique_fn():\n return 42\n")
186 _run(tmp_path, "code", "add", "solo.py")
187 _run(tmp_path, "commit", "-m", "solo")
188 r2 = _run(tmp_path, "code", "clones", "--json")
189 data = json.loads(r2.output)
190 assert "duration_ms" in data
191 assert data["duration_ms"] >= 0
192
193
194 # ---------------------------------------------------------------------------
195 # TestExitCode — JSON output must include exit_code = 0
196 # ---------------------------------------------------------------------------
197
198
199 class TestExitCode:
200 """JSON output must include exit_code = 0 on success."""
201
202 def test_json_has_exit_code(self, clones_repo: pathlib.Path) -> None:
203 r = _run(clones_repo, "code", "clones", "--json")
204 data = json.loads(r.output)
205 assert "exit_code" in data
206
207 def test_json_exit_code_zero_on_success(self, clones_repo: pathlib.Path) -> None:
208 r = _run(clones_repo, "code", "clones", "--json")
209 assert r.exit_code == 0
210 data = json.loads(r.output)
211 assert data["exit_code"] == 0
212
213 def test_json_exit_code_is_int(self, clones_repo: pathlib.Path) -> None:
214 r = _run(clones_repo, "code", "clones", "--json")
215 data = json.loads(r.output)
216 assert isinstance(data["exit_code"], int)
217
218 def test_j_alias_exit_code_present(self, clones_repo: pathlib.Path) -> None:
219 r = _run(clones_repo, "code", "clones", "-j")
220 data = json.loads(r.output)
221 assert "exit_code" in data
222
223 def test_exit_code_mirrors_process_exit(self, clones_repo: pathlib.Path) -> None:
224 r = _run(clones_repo, "code", "clones", "--json")
225 data = json.loads(r.output)
226 assert data["exit_code"] == r.exit_code
227
228 def test_exit_code_zero_with_no_clones(self, tmp_path: pathlib.Path,
229 monkeypatch: pytest.MonkeyPatch) -> None:
230 monkeypatch.chdir(tmp_path)
231 _run(tmp_path, "init", "--domain", "code")
232 (tmp_path / "solo.py").write_text("def unique_fn():\n return 42\n")
233 _run(tmp_path, "code", "add", "solo.py")
234 _run(tmp_path, "commit", "-m", "solo")
235 r = _run(tmp_path, "code", "clones", "--json")
236 assert r.exit_code == 0
237 data = json.loads(r.output)
238 assert data["exit_code"] == 0
239
240 def test_exit_code_zero_with_tier_near(self, clones_repo: pathlib.Path) -> None:
241 r = _run(clones_repo, "code", "clones", "--json", "--tier", "near")
242 assert r.exit_code == 0
243 data = json.loads(r.output)
244 assert data["exit_code"] == 0
245
246
247 # ---------------------------------------------------------------------------
248 # TestTypedDicts — _ClonesOutputJson carries the new fields
249 # ---------------------------------------------------------------------------
250
251
252 class TestTypedDicts:
253 """_ClonesOutputJson must carry exit_code and duration_ms annotations."""
254
255 def test_clones_output_json_exists(self) -> None:
256 from muse.cli.commands.clones import _ClonesOutputJson # noqa: F401
257
258 def test_has_exit_code_annotation(self) -> None:
259 from muse.cli.commands.clones import _ClonesOutputJson
260 assert "exit_code" in _ClonesOutputJson.__annotations__
261
262 def test_has_duration_ms_annotation(self) -> None:
263 from muse.cli.commands.clones import _ClonesOutputJson
264 assert "duration_ms" in _ClonesOutputJson.__annotations__
265
266 def test_retains_clusters_annotation(self) -> None:
267 from muse.cli.commands.clones import _ClonesOutputJson
268 assert "clusters" in _ClonesOutputJson.__annotations__
269
270 def test_retains_commit_annotation(self) -> None:
271 from muse.cli.commands.clones import _ClonesOutputJson
272 assert "commit" in _ClonesOutputJson.__annotations__
273
274 def test_retains_exact_clone_clusters_annotation(self) -> None:
275 from muse.cli.commands.clones import _ClonesOutputJson
276 assert "exact_clone_clusters" in _ClonesOutputJson.__annotations__
277
278 def test_retains_near_clone_clusters_annotation(self) -> None:
279 from muse.cli.commands.clones import _ClonesOutputJson
280 assert "near_clone_clusters" in _ClonesOutputJson.__annotations__
281
282 def test_retains_file_hotspots_annotation(self) -> None:
283 from muse.cli.commands.clones import _ClonesOutputJson
284 assert "file_hotspots" in _ClonesOutputJson.__annotations__
285
286 def test_member_dict_exists(self) -> None:
287 from muse.cli.commands.clones import _MemberDict # noqa: F401
288
289 def test_cluster_dict_exists(self) -> None:
290 from muse.cli.commands.clones import _ClusterDict # noqa: F401
291
292
293 # ---------------------------------------------------------------------------
294 # TestDocstrings — run() docstring documents new fields
295 # ---------------------------------------------------------------------------
296
297
298 # ---------------------------------------------------------------------------
299 # TestAnsiSanitization — no escape codes in JSON output
300 # ---------------------------------------------------------------------------
301
302
303 class TestAnsiSanitization:
304 """No ANSI escape sequences anywhere in the JSON output."""
305
306 def test_json_output_no_ansi(self, clones_repo: pathlib.Path) -> None:
307 r = _run(clones_repo, "code", "clones", "--json")
308 assert "\x1b" not in r.output
309
310 def test_j_alias_output_no_ansi(self, clones_repo: pathlib.Path) -> None:
311 r = _run(clones_repo, "code", "clones", "-j")
312 assert "\x1b" not in r.output
313
314 def test_tier_near_json_no_ansi(self, clones_repo: pathlib.Path) -> None:
315 r = _run(clones_repo, "code", "clones", "--json", "--tier", "near")
316 assert "\x1b" not in r.output
317
318
319 # ---------------------------------------------------------------------------
320 # TestPerformance — duration_ms under 2000 ms for a small repo
321 # ---------------------------------------------------------------------------
322
323
324 class TestPerformance:
325 """duration_ms must stay under 2000 ms for small repos."""
326
327 def test_json_duration_under_2000ms(self, clones_repo: pathlib.Path) -> None:
328 r = _run(clones_repo, "code", "clones", "--json")
329 data = json.loads(r.output)
330 assert data["duration_ms"] < 2000
331
332 def test_j_alias_duration_under_2000ms(self, clones_repo: pathlib.Path) -> None:
333 r = _run(clones_repo, "code", "clones", "-j")
334 data = json.loads(r.output)
335 assert data["duration_ms"] < 2000
336
337 def test_duration_ms_is_float_not_int(self, clones_repo: pathlib.Path) -> None:
338 r = _run(clones_repo, "code", "clones", "--json")
339 data = json.loads(r.output)
340 assert isinstance(data["duration_ms"], float)
File History 4 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 23 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 29 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago