gabriel / muse public

test_impact_supercharge.py file-level

at sha256:8 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:4 Merge branch 'dev' into main · gabriel · Jun 17, 2026
1 """Supercharge tests for ``muse code impact`` β€” agent-usability gaps.
2
3 No prior tests existed for ``muse code impact``. This file covers:
4
5 Coverage matrix
6 ---------------
7 - --json / -j: -j alias works identically to --json
8 - exit_code: JSON output includes exit_code = 0 on success
9 - duration_ms: JSON output includes non-negative float duration_ms
10 - TypedDicts: _ImpactJson carries exit_code and duration_ms
11 - ForwardJson: forward mode JSON carries exit_code and duration_ms
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 - Shapes: reverse mode vs forward mode JSON shapes are distinct
16 """
17
18 from __future__ import annotations
19 from collections.abc import Mapping
20
21 import argparse
22 import json
23 import pathlib
24 import textwrap
25
26 import pytest
27
28 from tests.cli_test_helper import CliRunner, InvokeResult
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 # ---------------------------------------------------------------------------
47 # Fixture β€” minimal Python repo with call relationships
48 # ---------------------------------------------------------------------------
49
50
51 @pytest.fixture()
52 def impact_repo(
53 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
54 ) -> pathlib.Path:
55 """Repo with a simple call graph.
56
57 core.py β€” compute(x) + validate(x)
58 service.py β€” process(x) calls compute(x)
59 api.py β€” handle(req) calls process(x)
60 """
61 monkeypatch.chdir(tmp_path)
62 r = _run(tmp_path, "init", "--domain", "code")
63 assert r.exit_code == 0, r.output
64
65 (tmp_path / "core.py").write_text(textwrap.dedent("""\
66 def compute(x):
67 return x * 2
68
69 def validate(x):
70 return x > 0
71 """))
72 (tmp_path / "service.py").write_text(textwrap.dedent("""\
73 from core import compute
74
75 def process(x):
76 return compute(x)
77 """))
78 (tmp_path / "api.py").write_text(textwrap.dedent("""\
79 from service import process
80
81 def handle(req):
82 return process(req)
83 """))
84 r = _run(tmp_path, "code", "add", ".")
85 assert r.exit_code == 0, r.output
86 r = _run(tmp_path, "commit", "-m", "seed impact repo")
87 assert r.exit_code == 0, r.output
88
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, impact_repo: pathlib.Path) -> None:
101 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
102 assert r.exit_code == 0, r.output
103
104 def test_j_alias_valid_json(self, impact_repo: pathlib.Path) -> None:
105 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
106 json.loads(r.output) # must not raise
107
108 def test_j_alias_has_blast_radius_key(self, impact_repo: pathlib.Path) -> None:
109 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
110 assert "blast_radius" in json.loads(r.output)
111
112 def test_j_alias_has_mode_key(self, impact_repo: pathlib.Path) -> None:
113 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
114 assert "mode" in json.loads(r.output)
115
116 def test_j_alias_same_keys_as_json_flag(self, impact_repo: pathlib.Path) -> None:
117 r1 = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
118 r2 = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
119 d1 = json.loads(r1.output)
120 d2 = json.loads(r2.output)
121 d1.pop("duration_ms", None)
122 d2.pop("duration_ms", None)
123 assert set(d1.keys()) == set(d2.keys())
124
125 def test_j_alias_mode_is_reverse(self, impact_repo: pathlib.Path) -> None:
126 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
127 assert json.loads(r.output)["mode"] == "reverse"
128
129 def test_j_alias_address_echoed(self, impact_repo: pathlib.Path) -> None:
130 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
131 assert json.loads(r.output)["address"] == "core.py::compute"
132
133 def test_j_alias_forward_mode(self, impact_repo: pathlib.Path) -> None:
134 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j", "--forward")
135 assert r.exit_code == 0, r.output
136 data = json.loads(r.output)
137 assert data["mode"] == "forward"
138
139 def test_j_alias_total_is_int(self, impact_repo: pathlib.Path) -> None:
140 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
141 assert isinstance(json.loads(r.output)["total"], int)
142
143
144 # ---------------------------------------------------------------------------
145 # TestDurationMs β€” JSON output must include duration_ms
146 # ---------------------------------------------------------------------------
147
148
149 class TestDurationMs:
150 """JSON output must include a non-negative float duration_ms."""
151
152 def test_json_has_duration_ms(self, impact_repo: pathlib.Path) -> None:
153 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
154 assert "duration_ms" in json.loads(r.output)
155
156 def test_json_duration_ms_nonnegative(self, impact_repo: pathlib.Path) -> None:
157 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
158 assert json.loads(r.output)["duration_ms"] >= 0
159
160 def test_json_duration_ms_is_float(self, impact_repo: pathlib.Path) -> None:
161 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
162 assert isinstance(json.loads(r.output)["duration_ms"], float)
163
164 def test_j_alias_duration_ms_present(self, impact_repo: pathlib.Path) -> None:
165 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
166 assert "duration_ms" in json.loads(r.output)
167
168 def test_forward_mode_duration_ms_present(self, impact_repo: pathlib.Path) -> None:
169 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
170 data = json.loads(r.output)
171 assert "duration_ms" in data
172 assert isinstance(data["duration_ms"], float)
173
174 def test_duration_ms_under_2000ms(self, impact_repo: pathlib.Path) -> None:
175 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
176 assert json.loads(r.output)["duration_ms"] < 2000
177
178
179 # ---------------------------------------------------------------------------
180 # TestExitCode β€” JSON includes exit_code = 0 on success
181 # ---------------------------------------------------------------------------
182
183
184 class TestExitCode:
185 """JSON exit_code must be 0 on success."""
186
187 def test_json_has_exit_code(self, impact_repo: pathlib.Path) -> None:
188 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
189 assert "exit_code" in json.loads(r.output)
190
191 def test_json_exit_code_zero(self, impact_repo: pathlib.Path) -> None:
192 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
193 assert r.exit_code == 0
194 assert json.loads(r.output)["exit_code"] == 0
195
196 def test_json_exit_code_is_int(self, impact_repo: pathlib.Path) -> None:
197 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
198 assert isinstance(json.loads(r.output)["exit_code"], int)
199
200 def test_j_alias_exit_code_present(self, impact_repo: pathlib.Path) -> None:
201 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
202 assert "exit_code" in json.loads(r.output)
203
204 def test_exit_code_mirrors_process_exit(self, impact_repo: pathlib.Path) -> None:
205 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
206 assert json.loads(r.output)["exit_code"] == r.exit_code
207
208 def test_forward_mode_exit_code_zero(self, impact_repo: pathlib.Path) -> None:
209 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
210 assert r.exit_code == 0
211 assert json.loads(r.output)["exit_code"] == 0
212
213 def test_exit_code_leaf_symbol(self, impact_repo: pathlib.Path) -> None:
214 """exit_code is 0 even for a symbol with no callers."""
215 r = _run(impact_repo, "code", "impact", "core.py::validate", "--json")
216 assert r.exit_code == 0
217 assert json.loads(r.output)["exit_code"] == 0
218
219
220 # ---------------------------------------------------------------------------
221 # TestTypedDicts β€” _ImpactJson carries exit_code and duration_ms
222 # ---------------------------------------------------------------------------
223
224
225 class TestTypedDicts:
226 """_ImpactJson must carry exit_code and duration_ms annotations."""
227
228 def test_impact_json_typeddict_exists(self) -> None:
229 from muse.cli.commands.impact import _ImpactJson # noqa: F401
230
231 def test_has_exit_code_annotation(self) -> None:
232 from muse.cli.commands.impact import _ImpactJson
233 assert "exit_code" in _ImpactJson.__annotations__
234
235 def test_has_duration_ms_annotation(self) -> None:
236 from muse.cli.commands.impact import _ImpactJson
237 assert "duration_ms" in _ImpactJson.__annotations__
238
239 def test_retains_blast_radius_annotation(self) -> None:
240 from muse.cli.commands.impact import _ImpactJson
241 assert "blast_radius" in _ImpactJson.__annotations__
242
243 def test_retains_mode_annotation(self) -> None:
244 from muse.cli.commands.impact import _ImpactJson
245 assert "mode" in _ImpactJson.__annotations__
246
247 def test_retains_address_annotation(self) -> None:
248 from muse.cli.commands.impact import _ImpactJson
249 assert "address" in _ImpactJson.__annotations__
250
251 def test_retains_total_annotation(self) -> None:
252 from muse.cli.commands.impact import _ImpactJson
253 assert "total" in _ImpactJson.__annotations__
254
255
256 # ---------------------------------------------------------------------------
257 # TestAnsiSanitization β€” no escape codes in JSON output
258 # ---------------------------------------------------------------------------
259
260
261 class TestAnsiSanitization:
262 """No ANSI escape sequences anywhere in the JSON output."""
263
264 def test_json_output_no_ansi(self, impact_repo: pathlib.Path) -> None:
265 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
266 assert "\x1b" not in r.output
267
268 def test_j_alias_output_no_ansi(self, impact_repo: pathlib.Path) -> None:
269 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
270 assert "\x1b" not in r.output
271
272 def test_forward_mode_no_ansi(self, impact_repo: pathlib.Path) -> None:
273 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
274 assert "\x1b" not in r.output
275
276
277 # ---------------------------------------------------------------------------
278 # TestForwardMode β€” forward mode shape
279 # ---------------------------------------------------------------------------
280
281
282 class TestForwardMode:
283 """--forward mode must emit a valid, distinct JSON shape."""
284
285 def test_forward_has_callees_key(self, impact_repo: pathlib.Path) -> None:
286 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
287 assert r.exit_code == 0, r.output
288 assert "callees" in json.loads(r.output)
289
290 def test_forward_mode_field_is_forward(self, impact_repo: pathlib.Path) -> None:
291 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
292 assert json.loads(r.output)["mode"] == "forward"
293
294 def test_forward_has_total_key(self, impact_repo: pathlib.Path) -> None:
295 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
296 data = json.loads(r.output)
297 assert "total" in data
298 assert isinstance(data["total"], int)
299
300 def test_forward_and_json_not_reverse(self, impact_repo: pathlib.Path) -> None:
301 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
302 data = json.loads(r.output)
303 assert "blast_radius" not in data
304
305
306 # ---------------------------------------------------------------------------
307 # TestRegisterFlags β€” argparse-level verification
308 # ---------------------------------------------------------------------------
309
310
311 class TestRegisterFlags:
312 """Verify that register() wires --json / -j correctly."""
313
314 def _make_parser(self) -> "argparse.ArgumentParser":
315 import argparse
316 from muse.cli.commands.impact import register
317 ap = argparse.ArgumentParser()
318 subs = ap.add_subparsers()
319 register(subs)
320 return ap
321
322 def test_json_flag_long(self) -> None:
323 ns = self._make_parser().parse_args(["impact", "core.py::Fn", "--json"])
324 assert ns.json_out is True
325
326 def test_j_alias(self) -> None:
327 ns = self._make_parser().parse_args(["impact", "core.py::Fn", "-j"])
328 assert ns.json_out is True
329
330 def test_default_is_text(self) -> None:
331 ns = self._make_parser().parse_args(["impact", "core.py::Fn"])
332 assert ns.json_out is False
333
334 def test_dest_is_json_out(self) -> None:
335 ns = self._make_parser().parse_args(["impact", "core.py::Fn", "-j"])
336 assert hasattr(ns, "json_out")
337 assert not hasattr(ns, "fmt")