gabriel / muse public
test_stable_supercharge.py python
438 lines 19.4 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 140 days ago
1 """Supercharge tests for ``muse code stable``.
2
3 Tiers
4 -----
5 Unit — TypedDict shape, alias registration, docstring completeness.
6 Integration — -j alias, exit_code/duration_ms/schema_version in envelope,
7 filter flags, --since, truncation flag, empty-repo edge case.
8 End-to-end — full CLI invocation; --json vs -j parity; --kind/--language filters.
9 Stress — many commits; concurrent invocations on separate repos.
10 Data integrity — stability counts correct; since_start_of_range semantics;
11 ranked order is descending.
12 Security — ANSI/null in --kind, --language, --since args.
13 Performance — duration_ms present and reasonable.
14 """
15
16 from __future__ import annotations
17
18 import json
19 import os
20 import pathlib
21 import textwrap
22 import threading
23
24 import pytest
25
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 _stable(repo: pathlib.Path, *args: str) -> InvokeResult:
46 return _invoke(repo, ["code", "stable", *args])
47
48
49 def _commit(repo: pathlib.Path, files: dict[str, str], message: str) -> None:
50 for name, content in files.items():
51 path = repo / name
52 path.parent.mkdir(parents=True, exist_ok=True)
53 path.write_text(content, encoding="utf-8")
54 saved = os.getcwd()
55 try:
56 os.chdir(repo)
57 runner.invoke(None, ["code", "add", "."])
58 runner.invoke(None, ["commit", "-m", message])
59 finally:
60 os.chdir(saved)
61
62
63 @pytest.fixture()
64 def stable_repo(tmp_path: pathlib.Path) -> pathlib.Path:
65 """Repo with two commits.
66
67 Commit 1: a.py (stable — never modified again) + b.py
68 Commit 2: b.py modified (hot), a.py untouched (stable)
69 """
70 saved = os.getcwd()
71 try:
72 os.chdir(tmp_path)
73 runner.invoke(None, ["init"])
74 finally:
75 os.chdir(saved)
76
77 _commit(tmp_path, {
78 "a.py": textwrap.dedent("""\
79 def stable_fn():
80 return 42
81 """),
82 "b.py": textwrap.dedent("""\
83 def hot_fn():
84 return 1
85 """),
86 }, "initial")
87
88 _commit(tmp_path, {
89 "b.py": textwrap.dedent("""\
90 def hot_fn():
91 return 2
92 """),
93 }, "modify b")
94
95 return tmp_path
96
97
98 # ──────────────────────────────────────────────────────────────────────────────
99 # Unit — TypedDict
100 # ──────────────────────────────────────────────────────────────────────────────
101
102
103 class TestTypedDict:
104 def test_stable_json_typed_dict_exists(self) -> None:
105 from muse.cli.commands.stable import _StableJson # noqa: F401
106
107 def test_has_exit_code(self) -> None:
108 import typing
109 from muse.cli.commands.stable import _StableJson
110 assert "exit_code" in typing.get_type_hints(_StableJson)
111
112 def test_has_duration_ms(self) -> None:
113 import typing
114 from muse.cli.commands.stable import _StableJson
115 assert "duration_ms" in typing.get_type_hints(_StableJson)
116
117 def test_has_schema_version(self) -> None:
118 import typing
119 from muse.cli.commands.stable import _StableJson
120 assert "schema_version" in typing.get_type_hints(_StableJson)
121
122 def test_retains_core_fields(self) -> None:
123 import typing
124 from muse.cli.commands.stable import _StableJson
125 hints = typing.get_type_hints(_StableJson)
126 required = {"from_ref", "to_ref", "commits_analysed", "truncated", "filters", "stable"}
127 assert required <= set(hints)
128
129
130 # ──────────────────────────────────────────────────────────────────────────────
131 # Unit — alias registration
132 # ──────────────────────────────────────────────────────────────────────────────
133
134
135 class TestAliasRegistration:
136 def _parser(self):
137 import argparse
138 from muse.cli.commands.stable import register
139 p = argparse.ArgumentParser()
140 sub = p.add_subparsers()
141 register(sub)
142 return p
143
144 def test_j_alias_sets_as_json(self) -> None:
145 ns = self._parser().parse_args(["stable", "-j"])
146 assert ns.as_json is True
147
148 def test_j_alias_with_other_flags(self) -> None:
149 ns = self._parser().parse_args(["stable", "-j", "--top", "5"])
150 assert ns.as_json is True
151 assert ns.top == 5
152
153
154 # ──────────────────────────────────────────────────────────────────────────────
155 # Unit — docstrings
156 # ──────────────────────────────────────────────────────────────────────────────
157
158
159 class TestDocstrings:
160 def test_register_docstring_lists_flags(self) -> None:
161 from muse.cli.commands.stable import register
162 doc = register.__doc__ or ""
163 assert "--json" in doc or "-j" in doc
164
165 def test_run_docstring_mentions_exit_code(self) -> None:
166 from muse.cli.commands.stable import run
167 assert "exit_code" in (run.__doc__ or "")
168
169 def test_run_docstring_mentions_duration_ms(self) -> None:
170 from muse.cli.commands.stable import run
171 assert "duration_ms" in (run.__doc__ or "")
172
173 def test_run_docstring_mentions_schema_version(self) -> None:
174 from muse.cli.commands.stable import run
175 assert "schema_version" in (run.__doc__ or "")
176
177
178 # ──────────────────────────────────────────────────────────────────────────────
179 # Integration — JSON envelope fields
180 # ──────────────────────────────────────────────────────────────────────────────
181
182
183 class TestJsonEnvelope:
184 def test_has_exit_code(self, stable_repo: pathlib.Path) -> None:
185 data = json.loads(_stable(stable_repo, "--json").output)
186 assert "exit_code" in data
187
188 def test_exit_code_zero(self, stable_repo: pathlib.Path) -> None:
189 data = json.loads(_stable(stable_repo, "--json").output)
190 assert data["exit_code"] == 0
191
192 def test_exit_code_is_int(self, stable_repo: pathlib.Path) -> None:
193 data = json.loads(_stable(stable_repo, "--json").output)
194 assert isinstance(data["exit_code"], int)
195
196 def test_has_duration_ms(self, stable_repo: pathlib.Path) -> None:
197 data = json.loads(_stable(stable_repo, "--json").output)
198 assert "duration_ms" in data
199
200 def test_duration_ms_nonnegative_float(self, stable_repo: pathlib.Path) -> None:
201 data = json.loads(_stable(stable_repo, "--json").output)
202 assert isinstance(data["duration_ms"], float)
203 assert data["duration_ms"] >= 0.0
204
205 def test_has_schema_version(self, stable_repo: pathlib.Path) -> None:
206 data = json.loads(_stable(stable_repo, "--json").output)
207 assert "schema_version" in data
208
209 def test_schema_version_nonempty_string(self, stable_repo: pathlib.Path) -> None:
210 data = json.loads(_stable(stable_repo, "--json").output)
211 assert isinstance(data["schema_version"], str)
212 assert data["schema_version"]
213
214
215 # ──────────────────────────────────────────────────────────────────────────────
216 # Integration — -j alias parity
217 # ──────────────────────────────────────────────────────────────────────────────
218
219
220 class TestJsonAlias:
221 def test_j_alias_exit_code_zero(self, stable_repo: pathlib.Path) -> None:
222 assert _stable(stable_repo, "-j").exit_code == 0
223
224 def test_j_alias_valid_json(self, stable_repo: pathlib.Path) -> None:
225 data = json.loads(_stable(stable_repo, "-j").output)
226 assert isinstance(data, dict)
227
228 def test_j_alias_same_top_level_keys(self, stable_repo: pathlib.Path) -> None:
229 keys_json = set(json.loads(_stable(stable_repo, "--json").output))
230 keys_j = set(json.loads(_stable(stable_repo, "-j").output))
231 assert keys_json == keys_j
232
233 def test_j_alias_stable_list_matches(self, stable_repo: pathlib.Path) -> None:
234 d1 = json.loads(_stable(stable_repo, "--json").output)
235 d2 = json.loads(_stable(stable_repo, "-j").output)
236 assert d1["stable"] == d2["stable"]
237
238
239 # ──────────────────────────────────────────────────────────────────────────────
240 # End-to-end — filters, output shape
241 # ──────────────────────────────────────────────────────────────────────────────
242
243
244 class TestEndToEnd:
245 def test_default_text_output_exits_zero(self, stable_repo: pathlib.Path) -> None:
246 assert _stable(stable_repo).exit_code == 0
247
248 def test_default_text_mentions_bedrock(self, stable_repo: pathlib.Path) -> None:
249 assert "bedrock" in _stable(stable_repo).output.lower()
250
251 def test_json_stable_list_is_list(self, stable_repo: pathlib.Path) -> None:
252 data = json.loads(_stable(stable_repo, "--json").output)
253 assert isinstance(data["stable"], list)
254
255 def test_json_stable_entry_has_address(self, stable_repo: pathlib.Path) -> None:
256 data = json.loads(_stable(stable_repo, "--json").output)
257 assert data["stable"]
258 assert "address" in data["stable"][0]
259
260 def test_json_stable_entry_has_unchanged_for(self, stable_repo: pathlib.Path) -> None:
261 data = json.loads(_stable(stable_repo, "--json").output)
262 assert "unchanged_for" in data["stable"][0]
263
264 def test_json_stable_entry_has_since_start_of_range(self, stable_repo: pathlib.Path) -> None:
265 data = json.loads(_stable(stable_repo, "--json").output)
266 assert "since_start_of_range" in data["stable"][0]
267
268 def test_top_flag_limits_results(self, stable_repo: pathlib.Path) -> None:
269 data = json.loads(_stable(stable_repo, "--json", "--top", "1").output)
270 assert len(data["stable"]) <= 1
271
272 def test_kind_filter_restricts_results(self, stable_repo: pathlib.Path) -> None:
273 data = json.loads(_stable(stable_repo, "--json", "--kind", "class").output)
274 for entry in data["stable"]:
275 # addresses filtered to class symbols — spot-check via filter echoed
276 pass # no crash and valid JSON is the assertion
277 assert "kind" in data["filters"]
278
279 def test_commits_analysed_positive(self, stable_repo: pathlib.Path) -> None:
280 data = json.loads(_stable(stable_repo, "--json").output)
281 assert data["commits_analysed"] > 0
282
283 def test_truncated_flag_present(self, stable_repo: pathlib.Path) -> None:
284 data = json.loads(_stable(stable_repo, "--json").output)
285 assert "truncated" in data
286 assert isinstance(data["truncated"], bool)
287
288 def test_filters_dict_present(self, stable_repo: pathlib.Path) -> None:
289 data = json.loads(_stable(stable_repo, "--json").output)
290 assert isinstance(data["filters"], dict)
291
292 def test_from_ref_and_to_ref_present(self, stable_repo: pathlib.Path) -> None:
293 data = json.loads(_stable(stable_repo, "--json").output)
294 assert "from_ref" in data
295 assert "to_ref" in data
296
297 def test_invalid_since_ref_exits_nonzero(self, stable_repo: pathlib.Path) -> None:
298 result = _stable(stable_repo, "--since", "nonexistent-ref-xyz")
299 assert result.exit_code != 0
300
301
302 # ──────────────────────────────────────────────────────────────────────────────
303 # Stress
304 # ──────────────────────────────────────────────────────────────────────────────
305
306
307 class TestStress:
308 def test_many_commits_does_not_crash(self, tmp_path: pathlib.Path) -> None:
309 saved = os.getcwd()
310 try:
311 os.chdir(tmp_path)
312 runner.invoke(None, ["init"])
313 finally:
314 os.chdir(saved)
315
316 for i in range(30):
317 _commit(tmp_path, {"f.py": f"def fn(): return {i}\n"}, f"commit {i}")
318
319 result = _stable(tmp_path, "--json")
320 assert result.exit_code == 0
321 data = json.loads(result.output)
322 assert data["commits_analysed"] >= 1
323
324 def test_concurrent_stable_separate_repos(self, tmp_path: pathlib.Path) -> None:
325 repos = []
326 for i in range(4):
327 r = tmp_path / f"repo{i}"
328 r.mkdir()
329 saved = os.getcwd()
330 try:
331 os.chdir(r)
332 runner.invoke(None, ["init"])
333 finally:
334 os.chdir(saved)
335 _commit(r, {"x.py": f"def f(): return {i}\n"}, "init")
336 repos.append(r)
337
338 results: list[int] = []
339 lock = threading.Lock()
340
341 def _run(repo: pathlib.Path) -> None:
342 rc = _stable(repo, "--json").exit_code
343 with lock:
344 results.append(rc)
345
346 threads = [threading.Thread(target=_run, args=(r,)) for r in repos]
347 for t in threads:
348 t.start()
349 for t in threads:
350 t.join()
351 assert all(rc == 0 for rc in results)
352
353
354 # ──────────────────────────────────────────────────────────────────────────────
355 # Data integrity
356 # ──────────────────────────────────────────────────────────────────────────────
357
358
359 class TestDataIntegrity:
360 def test_stable_list_sorted_descending(self, stable_repo: pathlib.Path) -> None:
361 data = json.loads(_stable(stable_repo, "--json").output)
362 counts = [e["unchanged_for"] for e in data["stable"]]
363 assert counts == sorted(counts, reverse=True)
364
365 def test_unchanged_for_is_nonnegative_int(self, stable_repo: pathlib.Path) -> None:
366 data = json.loads(_stable(stable_repo, "--json").output)
367 for entry in data["stable"]:
368 assert isinstance(entry["unchanged_for"], int)
369 assert entry["unchanged_for"] >= 0
370
371 def test_since_start_of_range_is_bool(self, stable_repo: pathlib.Path) -> None:
372 data = json.loads(_stable(stable_repo, "--json").output)
373 for entry in data["stable"]:
374 assert isinstance(entry["since_start_of_range"], bool)
375
376 def test_stable_fn_has_higher_stability_than_hot_fn(
377 self, stable_repo: pathlib.Path
378 ) -> None:
379 """stable_fn was never modified; hot_fn was — stable_fn must rank higher."""
380 data = json.loads(_stable(stable_repo, "--json").output)
381 stable_counts = {
382 e["address"].split("::")[-1]: e["unchanged_for"]
383 for e in data["stable"]
384 }
385 if "stable_fn" in stable_counts and "hot_fn" in stable_counts:
386 assert stable_counts["stable_fn"] >= stable_counts["hot_fn"]
387
388 def test_filters_reflect_input_flags(self, stable_repo: pathlib.Path) -> None:
389 data = json.loads(
390 _stable(stable_repo, "--json", "--top", "5", "--kind", "function").output
391 )
392 assert data["filters"]["top"] == 5
393 assert data["filters"]["kind"] == "function"
394
395 def test_max_commits_cap_respected(self, stable_repo: pathlib.Path) -> None:
396 data = json.loads(
397 _stable(stable_repo, "--json", "--max-commits", "1").output
398 )
399 assert data["commits_analysed"] <= 1
400
401
402 # ──────────────────────────────────────────────────────────────────────────────
403 # Security
404 # ──────────────────────────────────────────────────────────────────────────────
405
406
407 class TestSecurity:
408 def test_ansi_in_kind_filter_not_echoed_raw(self, stable_repo: pathlib.Path) -> None:
409 result = _stable(stable_repo, "--kind", "\x1b[31mfunc\x1b[0m")
410 combined = result.output + (result.stderr or "")
411 assert "\x1b[31m" not in combined
412
413 def test_ansi_in_language_filter_not_echoed_raw(
414 self, stable_repo: pathlib.Path
415 ) -> None:
416 result = _stable(stable_repo, "--language", "\x1b[31mpython\x1b[0m")
417 combined = result.output + (result.stderr or "")
418 assert "\x1b[31m" not in combined
419
420 def test_ansi_in_since_ref_not_echoed_raw(self, stable_repo: pathlib.Path) -> None:
421 result = _stable(stable_repo, "--since", "\x1b[31mevil\x1b[0m")
422 combined = result.output + (result.stderr or "")
423 assert "\x1b[31m" not in combined
424
425 def test_null_byte_in_kind_does_not_crash(self, stable_repo: pathlib.Path) -> None:
426 result = _stable(stable_repo, "--kind", "func\x00evil")
427 assert result.exit_code in (0, 1)
428
429
430 # ──────────────────────────────────────────────────────────────────────────────
431 # Performance
432 # ──────────────────────────────────────────────────────────────────────────────
433
434
435 class TestPerformance:
436 def test_duration_ms_under_10000(self, stable_repo: pathlib.Path) -> None:
437 data = json.loads(_stable(stable_repo, "--json").output)
438 assert data["duration_ms"] < 10_000.0
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 140 days ago