gabriel / muse public
test_codemap_supercharge.py python
335 lines 12.9 KB
Raw
sha256:be3641f35bdbcc094677776a77b9aa6a5dab891f8fab201dc162d03c2bab5aea fix(read): strip position:null from structured_delta ops in… Sonnet 4.6 patch 23 days ago
1 """Supercharge tests for ``muse code codemap`` — agent-usability gaps.
2
3 The existing test_cmd_codemap.py covers import graph correctness, cycle
4 detection, all flags (--top, --min-importers, --language, --commit), JSON
5 schema, stress/performance, and agent-safe zones. This file targets only
6 the gaps those tests leave open:
7
8 Coverage matrix
9 ---------------
10 - --json / -j: -j alias works identically to --json
11 - exit_code: JSON output includes exit_code = 0 on success
12 - duration_ms: JSON output includes non-negative float duration_ms
13 - TypedDicts: _CodemapOutputJson in production code gains exit_code/duration_ms
14 - Docstrings: run() docstring mentions exit_code and duration_ms
15 - ANSI: JSON output never contains terminal escape sequences
16 - Performance: duration_ms stays under 2000 ms for a small repo
17 """
18
19 from __future__ import annotations
20 from collections.abc import Mapping
21
22 import json
23 import pathlib
24 import textwrap
25
26 import pytest
27
28 from tests.cli_test_helper import CliRunner
29
30 runner = CliRunner()
31
32
33 # ---------------------------------------------------------------------------
34 # Helpers
35 # ---------------------------------------------------------------------------
36
37
38 def _env(root: pathlib.Path) -> Mapping[str, str]:
39 return {"MUSE_REPO_ROOT": str(root)}
40
41
42 def _run(root: pathlib.Path, *args: str) -> "InvokeResult":
43 return runner.invoke(None, list(args), env=_env(root))
44
45
46 def _stage_commit(root: pathlib.Path, msg: str = "commit") -> None:
47 r = _run(root, "code", "add", ".")
48 assert r.exit_code == 0, r.output
49 r2 = _run(root, "commit", "-m", msg)
50 assert r2.exit_code == 0, r2.output
51
52
53 # ---------------------------------------------------------------------------
54 # Fixture
55 # ---------------------------------------------------------------------------
56
57
58 @pytest.fixture()
59 def codemap_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
60 """Code-domain repo with a small import graph committed.
61
62 alpha.py is the hub — beta.py and gamma.py both import it.
63 delta.py is isolated (agent-safe zone).
64 """
65 monkeypatch.chdir(tmp_path)
66 r = _run(tmp_path, "init", "--domain", "code")
67 assert r.exit_code == 0, r.output
68
69 (tmp_path / "alpha.py").write_text(textwrap.dedent("""\
70 def alpha_fn():
71 return 1
72 """))
73 (tmp_path / "beta.py").write_text(textwrap.dedent("""\
74 import alpha
75
76 def beta_fn():
77 return alpha.alpha_fn() + 1
78 """))
79 (tmp_path / "gamma.py").write_text(textwrap.dedent("""\
80 import alpha
81
82 def gamma_fn():
83 return alpha.alpha_fn() + 2
84 """))
85 (tmp_path / "delta.py").write_text(textwrap.dedent("""\
86 def delta_fn():
87 return 42
88 """))
89 _stage_commit(tmp_path, "add modules")
90 return tmp_path
91
92
93 # ---------------------------------------------------------------------------
94 # TestJsonAlias — -j works identically to --json
95 # ---------------------------------------------------------------------------
96
97
98 class TestJsonAlias:
99 """-j shorthand must behave identically to --json."""
100
101 def test_j_alias_exits_zero(self, codemap_repo: pathlib.Path) -> None:
102 r = _run(codemap_repo, "code", "codemap", "-j")
103 assert r.exit_code == 0, r.output
104
105 def test_j_alias_valid_json(self, codemap_repo: pathlib.Path) -> None:
106 r = _run(codemap_repo, "code", "codemap", "-j")
107 json.loads(r.output) # must not raise
108
109 def test_j_alias_has_modules_key(self, codemap_repo: pathlib.Path) -> None:
110 r = _run(codemap_repo, "code", "codemap", "-j")
111 data = json.loads(r.output)
112 assert "modules" in data
113
114 def test_j_alias_has_commit_key(self, codemap_repo: pathlib.Path) -> None:
115 r = _run(codemap_repo, "code", "codemap", "-j")
116 data = json.loads(r.output)
117 assert "commit" in data
118
119 def test_j_alias_same_top_level_keys_as_json_flag(self, codemap_repo: pathlib.Path) -> None:
120 r1 = _run(codemap_repo, "code", "codemap", "--json")
121 r2 = _run(codemap_repo, "code", "codemap", "-j")
122 d1 = json.loads(r1.output)
123 d2 = json.loads(r2.output)
124 d1.pop("duration_ms", None)
125 d2.pop("duration_ms", None)
126 assert set(d1.keys()) == set(d2.keys())
127
128 def test_j_alias_module_count_matches_json_flag(self, codemap_repo: pathlib.Path) -> None:
129 r1 = _run(codemap_repo, "code", "codemap", "--json")
130 r2 = _run(codemap_repo, "code", "codemap", "-j")
131 d1 = json.loads(r1.output)
132 d2 = json.loads(r2.output)
133 assert len(d1["modules"]) == len(d2["modules"])
134
135 def test_j_alias_with_language_flag(self, codemap_repo: pathlib.Path) -> None:
136 r = _run(codemap_repo, "code", "codemap", "-j", "--language", "Python")
137 assert r.exit_code == 0, r.output
138 data = json.loads(r.output)
139 assert data["language_filter"] == "Python"
140
141 def test_j_alias_with_top_flag(self, codemap_repo: pathlib.Path) -> None:
142 r = _run(codemap_repo, "code", "codemap", "-j", "--top", "2")
143 assert r.exit_code == 0, r.output
144 data = json.loads(r.output)
145 assert len(data["modules"]) <= 2
146
147
148 # ---------------------------------------------------------------------------
149 # TestDurationMs — JSON output must include duration_ms
150 # ---------------------------------------------------------------------------
151
152
153 class TestDurationMs:
154 """JSON output must include a non-negative float duration_ms."""
155
156 def test_json_has_duration_ms(self, codemap_repo: pathlib.Path) -> None:
157 r = _run(codemap_repo, "code", "codemap", "--json")
158 data = json.loads(r.output)
159 assert "duration_ms" in data
160
161 def test_json_duration_ms_nonnegative(self, codemap_repo: pathlib.Path) -> None:
162 r = _run(codemap_repo, "code", "codemap", "--json")
163 data = json.loads(r.output)
164 assert data["duration_ms"] >= 0
165
166 def test_json_duration_ms_is_float(self, codemap_repo: pathlib.Path) -> None:
167 r = _run(codemap_repo, "code", "codemap", "--json")
168 data = json.loads(r.output)
169 assert isinstance(data["duration_ms"], float)
170
171 def test_j_alias_duration_ms_present(self, codemap_repo: pathlib.Path) -> None:
172 r = _run(codemap_repo, "code", "codemap", "-j")
173 data = json.loads(r.output)
174 assert "duration_ms" in data
175
176 def test_duration_ms_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
177 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
178 data = json.loads(r.output)
179 assert "duration_ms" in data
180 assert data["duration_ms"] >= 0
181
182 def test_duration_ms_with_top_filter(self, codemap_repo: pathlib.Path) -> None:
183 r = _run(codemap_repo, "code", "codemap", "--json", "--top", "5")
184 data = json.loads(r.output)
185 assert "duration_ms" in data
186 assert data["duration_ms"] >= 0
187
188
189 # ---------------------------------------------------------------------------
190 # TestExitCode — JSON includes exit_code = 0 on success
191 # ---------------------------------------------------------------------------
192
193
194 class TestExitCode:
195 """JSON exit_code must be 0 on success."""
196
197 def test_json_has_exit_code(self, codemap_repo: pathlib.Path) -> None:
198 r = _run(codemap_repo, "code", "codemap", "--json")
199 data = json.loads(r.output)
200 assert "exit_code" in data
201
202 def test_json_exit_code_zero_on_success(self, codemap_repo: pathlib.Path) -> None:
203 r = _run(codemap_repo, "code", "codemap", "--json")
204 assert r.exit_code == 0
205 data = json.loads(r.output)
206 assert data["exit_code"] == 0
207
208 def test_json_exit_code_is_int(self, codemap_repo: pathlib.Path) -> None:
209 r = _run(codemap_repo, "code", "codemap", "--json")
210 data = json.loads(r.output)
211 assert isinstance(data["exit_code"], int)
212
213 def test_j_alias_exit_code_present(self, codemap_repo: pathlib.Path) -> None:
214 r = _run(codemap_repo, "code", "codemap", "-j")
215 data = json.loads(r.output)
216 assert "exit_code" in data
217
218 def test_exit_code_mirrors_process_exit(self, codemap_repo: pathlib.Path) -> None:
219 r = _run(codemap_repo, "code", "codemap", "--json")
220 data = json.loads(r.output)
221 assert data["exit_code"] == r.exit_code
222
223 def test_exit_code_zero_with_min_importers(self, codemap_repo: pathlib.Path) -> None:
224 r = _run(codemap_repo, "code", "codemap", "--json", "--min-importers", "1")
225 assert r.exit_code == 0
226 data = json.loads(r.output)
227 assert data["exit_code"] == 0
228
229 def test_exit_code_zero_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
230 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
231 assert r.exit_code == 0
232 data = json.loads(r.output)
233 assert data["exit_code"] == 0
234
235
236 # ---------------------------------------------------------------------------
237 # TestTypedDicts — _CodemapOutputJson in production code carries new fields
238 # ---------------------------------------------------------------------------
239
240
241 class TestTypedDicts:
242 """_CodemapOutputJson must carry exit_code and duration_ms annotations."""
243
244 def test_codemap_output_json_exists(self) -> None:
245 from muse.cli.commands.codemap import _CodemapOutputJson # noqa: F401
246
247 def test_has_exit_code_annotation(self) -> None:
248 from muse.cli.commands.codemap import _CodemapOutputJson
249 assert "exit_code" in _CodemapOutputJson.__annotations__
250
251 def test_has_duration_ms_annotation(self) -> None:
252 from muse.cli.commands.codemap import _CodemapOutputJson
253 assert "duration_ms" in _CodemapOutputJson.__annotations__
254
255 def test_retains_modules_annotation(self) -> None:
256 from muse.cli.commands.codemap import _CodemapOutputJson
257 assert "modules" in _CodemapOutputJson.__annotations__
258
259 def test_retains_commit_annotation(self) -> None:
260 from muse.cli.commands.codemap import _CodemapOutputJson
261 assert "commit" in _CodemapOutputJson.__annotations__
262
263 def test_retains_import_cycles_annotation(self) -> None:
264 from muse.cli.commands.codemap import _CodemapOutputJson
265 assert "import_cycles" in _CodemapOutputJson.__annotations__
266
267 def test_retains_high_centrality_annotation(self) -> None:
268 from muse.cli.commands.codemap import _CodemapOutputJson
269 assert "high_centrality" in _CodemapOutputJson.__annotations__
270
271 def test_retains_boundary_files_annotation(self) -> None:
272 from muse.cli.commands.codemap import _CodemapOutputJson
273 assert "boundary_files" in _CodemapOutputJson.__annotations__
274
275 def test_retains_agent_safe_zones_annotation(self) -> None:
276 from muse.cli.commands.codemap import _CodemapOutputJson
277 assert "agent_safe_zones" in _CodemapOutputJson.__annotations__
278
279
280 # ---------------------------------------------------------------------------
281 # TestDocstrings — run() docstring documents new fields
282 # ---------------------------------------------------------------------------
283
284
285 class TestDocstrings:
286 """run() must document exit_code."""
287
288 def test_run_docstring_documents_fields(self) -> None:
289 from muse.cli.commands.codemap import run
290 assert "exit_code" in run.__doc__
291
292
293 # ---------------------------------------------------------------------------
294 # TestAnsiSanitization — no escape codes in JSON output
295 # ---------------------------------------------------------------------------
296
297
298 class TestAnsiSanitization:
299 """No ANSI escape sequences anywhere in the JSON output."""
300
301 def test_json_output_no_ansi(self, codemap_repo: pathlib.Path) -> None:
302 r = _run(codemap_repo, "code", "codemap", "--json")
303 assert "\x1b" not in r.output
304
305 def test_j_alias_output_no_ansi(self, codemap_repo: pathlib.Path) -> None:
306 r = _run(codemap_repo, "code", "codemap", "-j")
307 assert "\x1b" not in r.output
308
309 def test_json_no_ansi_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
310 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
311 assert "\x1b" not in r.output
312
313
314 # ---------------------------------------------------------------------------
315 # TestPerformance — duration_ms under 2000 ms for a small repo
316 # ---------------------------------------------------------------------------
317
318
319 class TestPerformance:
320 """duration_ms must stay under 2000 ms for small repos."""
321
322 def test_json_duration_under_2000ms(self, codemap_repo: pathlib.Path) -> None:
323 r = _run(codemap_repo, "code", "codemap", "--json")
324 data = json.loads(r.output)
325 assert data["duration_ms"] < 2000
326
327 def test_j_alias_duration_under_2000ms(self, codemap_repo: pathlib.Path) -> None:
328 r = _run(codemap_repo, "code", "codemap", "-j")
329 data = json.loads(r.output)
330 assert data["duration_ms"] < 2000
331
332 def test_duration_ms_is_float_not_int(self, codemap_repo: pathlib.Path) -> None:
333 r = _run(codemap_repo, "code", "codemap", "--json")
334 data = json.loads(r.output)
335 assert isinstance(data["duration_ms"], float)
File History 3 commits
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 28 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago