gabriel / muse public
test_codemap_supercharge.py python
340 lines 13.1 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 142 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
21 import json
22 import pathlib
23 import textwrap
24
25 import pytest
26
27 from tests.cli_test_helper import CliRunner
28
29 runner = CliRunner()
30
31
32 # ---------------------------------------------------------------------------
33 # Helpers
34 # ---------------------------------------------------------------------------
35
36
37 def _env(root: pathlib.Path) -> dict[str, str]:
38 return {"MUSE_REPO_ROOT": str(root)}
39
40
41 def _run(root: pathlib.Path, *args: str):
42 return runner.invoke(None, list(args), env=_env(root))
43
44
45 def _stage_commit(root: pathlib.Path, msg: str = "commit") -> None:
46 r = _run(root, "code", "add", ".")
47 assert r.exit_code == 0, r.output
48 r2 = _run(root, "commit", "-m", msg)
49 assert r2.exit_code == 0, r2.output
50
51
52 # ---------------------------------------------------------------------------
53 # Fixture
54 # ---------------------------------------------------------------------------
55
56
57 @pytest.fixture()
58 def codemap_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
59 """Code-domain repo with a small import graph committed.
60
61 alpha.py is the hub — beta.py and gamma.py both import it.
62 delta.py is isolated (agent-safe zone).
63 """
64 monkeypatch.chdir(tmp_path)
65 r = _run(tmp_path, "init", "--domain", "code")
66 assert r.exit_code == 0, r.output
67
68 (tmp_path / "alpha.py").write_text(textwrap.dedent("""\
69 def alpha_fn():
70 return 1
71 """))
72 (tmp_path / "beta.py").write_text(textwrap.dedent("""\
73 import alpha
74
75 def beta_fn():
76 return alpha.alpha_fn() + 1
77 """))
78 (tmp_path / "gamma.py").write_text(textwrap.dedent("""\
79 import alpha
80
81 def gamma_fn():
82 return alpha.alpha_fn() + 2
83 """))
84 (tmp_path / "delta.py").write_text(textwrap.dedent("""\
85 def delta_fn():
86 return 42
87 """))
88 _stage_commit(tmp_path, "add modules")
89 return tmp_path
90
91
92 # ---------------------------------------------------------------------------
93 # TestJsonAlias — -j works identically to --json
94 # ---------------------------------------------------------------------------
95
96
97 class TestJsonAlias:
98 """-j shorthand must behave identically to --json."""
99
100 def test_j_alias_exits_zero(self, codemap_repo: pathlib.Path) -> None:
101 r = _run(codemap_repo, "code", "codemap", "-j")
102 assert r.exit_code == 0, r.output
103
104 def test_j_alias_valid_json(self, codemap_repo: pathlib.Path) -> None:
105 r = _run(codemap_repo, "code", "codemap", "-j")
106 json.loads(r.output) # must not raise
107
108 def test_j_alias_has_modules_key(self, codemap_repo: pathlib.Path) -> None:
109 r = _run(codemap_repo, "code", "codemap", "-j")
110 data = json.loads(r.output)
111 assert "modules" in data
112
113 def test_j_alias_has_commit_key(self, codemap_repo: pathlib.Path) -> None:
114 r = _run(codemap_repo, "code", "codemap", "-j")
115 data = json.loads(r.output)
116 assert "commit" in data
117
118 def test_j_alias_same_top_level_keys_as_json_flag(self, codemap_repo: pathlib.Path) -> None:
119 r1 = _run(codemap_repo, "code", "codemap", "--json")
120 r2 = _run(codemap_repo, "code", "codemap", "-j")
121 d1 = json.loads(r1.output)
122 d2 = json.loads(r2.output)
123 d1.pop("duration_ms", None)
124 d2.pop("duration_ms", None)
125 assert set(d1.keys()) == set(d2.keys())
126
127 def test_j_alias_module_count_matches_json_flag(self, codemap_repo: pathlib.Path) -> None:
128 r1 = _run(codemap_repo, "code", "codemap", "--json")
129 r2 = _run(codemap_repo, "code", "codemap", "-j")
130 d1 = json.loads(r1.output)
131 d2 = json.loads(r2.output)
132 assert len(d1["modules"]) == len(d2["modules"])
133
134 def test_j_alias_with_language_flag(self, codemap_repo: pathlib.Path) -> None:
135 r = _run(codemap_repo, "code", "codemap", "-j", "--language", "Python")
136 assert r.exit_code == 0, r.output
137 data = json.loads(r.output)
138 assert data["language_filter"] == "Python"
139
140 def test_j_alias_with_top_flag(self, codemap_repo: pathlib.Path) -> None:
141 r = _run(codemap_repo, "code", "codemap", "-j", "--top", "2")
142 assert r.exit_code == 0, r.output
143 data = json.loads(r.output)
144 assert len(data["modules"]) <= 2
145
146
147 # ---------------------------------------------------------------------------
148 # TestDurationMs — JSON output must include duration_ms
149 # ---------------------------------------------------------------------------
150
151
152 class TestDurationMs:
153 """JSON output must include a non-negative float duration_ms."""
154
155 def test_json_has_duration_ms(self, codemap_repo: pathlib.Path) -> None:
156 r = _run(codemap_repo, "code", "codemap", "--json")
157 data = json.loads(r.output)
158 assert "duration_ms" in data
159
160 def test_json_duration_ms_nonnegative(self, codemap_repo: pathlib.Path) -> None:
161 r = _run(codemap_repo, "code", "codemap", "--json")
162 data = json.loads(r.output)
163 assert data["duration_ms"] >= 0
164
165 def test_json_duration_ms_is_float(self, codemap_repo: pathlib.Path) -> None:
166 r = _run(codemap_repo, "code", "codemap", "--json")
167 data = json.loads(r.output)
168 assert isinstance(data["duration_ms"], float)
169
170 def test_j_alias_duration_ms_present(self, codemap_repo: pathlib.Path) -> None:
171 r = _run(codemap_repo, "code", "codemap", "-j")
172 data = json.loads(r.output)
173 assert "duration_ms" in data
174
175 def test_duration_ms_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
176 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
177 data = json.loads(r.output)
178 assert "duration_ms" in data
179 assert data["duration_ms"] >= 0
180
181 def test_duration_ms_with_top_filter(self, codemap_repo: pathlib.Path) -> None:
182 r = _run(codemap_repo, "code", "codemap", "--json", "--top", "5")
183 data = json.loads(r.output)
184 assert "duration_ms" in data
185 assert data["duration_ms"] >= 0
186
187
188 # ---------------------------------------------------------------------------
189 # TestExitCode — JSON includes exit_code = 0 on success
190 # ---------------------------------------------------------------------------
191
192
193 class TestExitCode:
194 """JSON exit_code must be 0 on success."""
195
196 def test_json_has_exit_code(self, codemap_repo: pathlib.Path) -> None:
197 r = _run(codemap_repo, "code", "codemap", "--json")
198 data = json.loads(r.output)
199 assert "exit_code" in data
200
201 def test_json_exit_code_zero_on_success(self, codemap_repo: pathlib.Path) -> None:
202 r = _run(codemap_repo, "code", "codemap", "--json")
203 assert r.exit_code == 0
204 data = json.loads(r.output)
205 assert data["exit_code"] == 0
206
207 def test_json_exit_code_is_int(self, codemap_repo: pathlib.Path) -> None:
208 r = _run(codemap_repo, "code", "codemap", "--json")
209 data = json.loads(r.output)
210 assert isinstance(data["exit_code"], int)
211
212 def test_j_alias_exit_code_present(self, codemap_repo: pathlib.Path) -> None:
213 r = _run(codemap_repo, "code", "codemap", "-j")
214 data = json.loads(r.output)
215 assert "exit_code" in data
216
217 def test_exit_code_mirrors_process_exit(self, codemap_repo: pathlib.Path) -> None:
218 r = _run(codemap_repo, "code", "codemap", "--json")
219 data = json.loads(r.output)
220 assert data["exit_code"] == r.exit_code
221
222 def test_exit_code_zero_with_min_importers(self, codemap_repo: pathlib.Path) -> None:
223 r = _run(codemap_repo, "code", "codemap", "--json", "--min-importers", "1")
224 assert r.exit_code == 0
225 data = json.loads(r.output)
226 assert data["exit_code"] == 0
227
228 def test_exit_code_zero_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
229 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
230 assert r.exit_code == 0
231 data = json.loads(r.output)
232 assert data["exit_code"] == 0
233
234
235 # ---------------------------------------------------------------------------
236 # TestTypedDicts — _CodemapOutputJson in production code carries new fields
237 # ---------------------------------------------------------------------------
238
239
240 class TestTypedDicts:
241 """_CodemapOutputJson must carry exit_code and duration_ms annotations."""
242
243 def test_codemap_output_json_exists(self) -> None:
244 from muse.cli.commands.codemap import _CodemapOutputJson # noqa: F401
245
246 def test_has_exit_code_annotation(self) -> None:
247 from muse.cli.commands.codemap import _CodemapOutputJson
248 assert "exit_code" in _CodemapOutputJson.__annotations__
249
250 def test_has_duration_ms_annotation(self) -> None:
251 from muse.cli.commands.codemap import _CodemapOutputJson
252 assert "duration_ms" in _CodemapOutputJson.__annotations__
253
254 def test_retains_modules_annotation(self) -> None:
255 from muse.cli.commands.codemap import _CodemapOutputJson
256 assert "modules" in _CodemapOutputJson.__annotations__
257
258 def test_retains_commit_annotation(self) -> None:
259 from muse.cli.commands.codemap import _CodemapOutputJson
260 assert "commit" in _CodemapOutputJson.__annotations__
261
262 def test_retains_import_cycles_annotation(self) -> None:
263 from muse.cli.commands.codemap import _CodemapOutputJson
264 assert "import_cycles" in _CodemapOutputJson.__annotations__
265
266 def test_retains_high_centrality_annotation(self) -> None:
267 from muse.cli.commands.codemap import _CodemapOutputJson
268 assert "high_centrality" in _CodemapOutputJson.__annotations__
269
270 def test_retains_boundary_files_annotation(self) -> None:
271 from muse.cli.commands.codemap import _CodemapOutputJson
272 assert "boundary_files" in _CodemapOutputJson.__annotations__
273
274 def test_retains_agent_safe_zones_annotation(self) -> None:
275 from muse.cli.commands.codemap import _CodemapOutputJson
276 assert "agent_safe_zones" in _CodemapOutputJson.__annotations__
277
278
279 # ---------------------------------------------------------------------------
280 # TestDocstrings — run() docstring documents new fields
281 # ---------------------------------------------------------------------------
282
283
284 class TestDocstrings:
285 """run() must document exit_code and duration_ms."""
286
287 def test_run_docstring_mentions_exit_code(self) -> None:
288 from muse.cli.commands.codemap import run
289 assert run.__doc__ is not None
290 assert "exit_code" in run.__doc__
291
292 def test_run_docstring_mentions_duration_ms(self) -> None:
293 from muse.cli.commands.codemap import run
294 assert run.__doc__ is not None
295 assert "duration_ms" in run.__doc__
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, codemap_repo: pathlib.Path) -> None:
307 r = _run(codemap_repo, "code", "codemap", "--json")
308 assert "\x1b" not in r.output
309
310 def test_j_alias_output_no_ansi(self, codemap_repo: pathlib.Path) -> None:
311 r = _run(codemap_repo, "code", "codemap", "-j")
312 assert "\x1b" not in r.output
313
314 def test_json_no_ansi_with_language_filter(self, codemap_repo: pathlib.Path) -> None:
315 r = _run(codemap_repo, "code", "codemap", "--json", "--language", "Python")
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, codemap_repo: pathlib.Path) -> None:
328 r = _run(codemap_repo, "code", "codemap", "--json")
329 data = json.loads(r.output)
330 assert data["duration_ms"] < 2000
331
332 def test_j_alias_duration_under_2000ms(self, codemap_repo: pathlib.Path) -> None:
333 r = _run(codemap_repo, "code", "codemap", "-j")
334 data = json.loads(r.output)
335 assert data["duration_ms"] < 2000
336
337 def test_duration_ms_is_float_not_int(self, codemap_repo: pathlib.Path) -> None:
338 r = _run(codemap_repo, "code", "codemap", "--json")
339 data = json.loads(r.output)
340 assert isinstance(data["duration_ms"], float)
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 142 days ago