gabriel / muse public
test_impact_supercharge.py python
320 lines 12.8 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago
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
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) -> dict[str, str]:
37 return {"MUSE_REPO_ROOT": str(root)}
38
39
40 def _run(root: pathlib.Path, *args: str):
41 return runner.invoke(None, list(args), env=_env(root))
42
43
44 # ---------------------------------------------------------------------------
45 # Fixture — minimal Python repo with call relationships
46 # ---------------------------------------------------------------------------
47
48
49 @pytest.fixture()
50 def impact_repo(
51 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
52 ) -> pathlib.Path:
53 """Repo with a simple call graph.
54
55 core.py — compute(x) + validate(x)
56 service.py — process(x) calls compute(x)
57 api.py — handle(req) calls process(x)
58 """
59 monkeypatch.chdir(tmp_path)
60 r = _run(tmp_path, "init", "--domain", "code")
61 assert r.exit_code == 0, r.output
62
63 (tmp_path / "core.py").write_text(textwrap.dedent("""\
64 def compute(x):
65 return x * 2
66
67 def validate(x):
68 return x > 0
69 """))
70 (tmp_path / "service.py").write_text(textwrap.dedent("""\
71 from core import compute
72
73 def process(x):
74 return compute(x)
75 """))
76 (tmp_path / "api.py").write_text(textwrap.dedent("""\
77 from service import process
78
79 def handle(req):
80 return process(req)
81 """))
82 r = _run(tmp_path, "code", "add", ".")
83 assert r.exit_code == 0, r.output
84 r = _run(tmp_path, "commit", "-m", "seed impact repo")
85 assert r.exit_code == 0, r.output
86
87 return tmp_path
88
89
90 # ---------------------------------------------------------------------------
91 # TestJsonAlias — -j works identically to --json
92 # ---------------------------------------------------------------------------
93
94
95 class TestJsonAlias:
96 """-j shorthand must behave identically to --json."""
97
98 def test_j_alias_exits_zero(self, impact_repo: pathlib.Path) -> None:
99 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
100 assert r.exit_code == 0, r.output
101
102 def test_j_alias_valid_json(self, impact_repo: pathlib.Path) -> None:
103 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
104 json.loads(r.output) # must not raise
105
106 def test_j_alias_has_blast_radius_key(self, impact_repo: pathlib.Path) -> None:
107 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
108 assert "blast_radius" in json.loads(r.output)
109
110 def test_j_alias_has_mode_key(self, impact_repo: pathlib.Path) -> None:
111 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
112 assert "mode" in json.loads(r.output)
113
114 def test_j_alias_same_keys_as_json_flag(self, impact_repo: pathlib.Path) -> None:
115 r1 = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
116 r2 = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
117 d1 = json.loads(r1.output)
118 d2 = json.loads(r2.output)
119 d1.pop("duration_ms", None)
120 d2.pop("duration_ms", None)
121 assert set(d1.keys()) == set(d2.keys())
122
123 def test_j_alias_mode_is_reverse(self, impact_repo: pathlib.Path) -> None:
124 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
125 assert json.loads(r.output)["mode"] == "reverse"
126
127 def test_j_alias_address_echoed(self, impact_repo: pathlib.Path) -> None:
128 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
129 assert json.loads(r.output)["address"] == "core.py::compute"
130
131 def test_j_alias_forward_mode(self, impact_repo: pathlib.Path) -> None:
132 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j", "--forward")
133 assert r.exit_code == 0, r.output
134 data = json.loads(r.output)
135 assert data["mode"] == "forward"
136
137 def test_j_alias_total_is_int(self, impact_repo: pathlib.Path) -> None:
138 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
139 assert isinstance(json.loads(r.output)["total"], int)
140
141
142 # ---------------------------------------------------------------------------
143 # TestDurationMs — JSON output must include duration_ms
144 # ---------------------------------------------------------------------------
145
146
147 class TestDurationMs:
148 """JSON output must include a non-negative float duration_ms."""
149
150 def test_json_has_duration_ms(self, impact_repo: pathlib.Path) -> None:
151 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
152 assert "duration_ms" in json.loads(r.output)
153
154 def test_json_duration_ms_nonnegative(self, impact_repo: pathlib.Path) -> None:
155 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
156 assert json.loads(r.output)["duration_ms"] >= 0
157
158 def test_json_duration_ms_is_float(self, impact_repo: pathlib.Path) -> None:
159 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
160 assert isinstance(json.loads(r.output)["duration_ms"], float)
161
162 def test_j_alias_duration_ms_present(self, impact_repo: pathlib.Path) -> None:
163 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
164 assert "duration_ms" in json.loads(r.output)
165
166 def test_forward_mode_duration_ms_present(self, impact_repo: pathlib.Path) -> None:
167 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
168 data = json.loads(r.output)
169 assert "duration_ms" in data
170 assert isinstance(data["duration_ms"], float)
171
172 def test_duration_ms_under_2000ms(self, impact_repo: pathlib.Path) -> None:
173 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
174 assert json.loads(r.output)["duration_ms"] < 2000
175
176
177 # ---------------------------------------------------------------------------
178 # TestExitCode — JSON includes exit_code = 0 on success
179 # ---------------------------------------------------------------------------
180
181
182 class TestExitCode:
183 """JSON exit_code must be 0 on success."""
184
185 def test_json_has_exit_code(self, impact_repo: pathlib.Path) -> None:
186 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
187 assert "exit_code" in json.loads(r.output)
188
189 def test_json_exit_code_zero(self, impact_repo: pathlib.Path) -> None:
190 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
191 assert r.exit_code == 0
192 assert json.loads(r.output)["exit_code"] == 0
193
194 def test_json_exit_code_is_int(self, impact_repo: pathlib.Path) -> None:
195 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
196 assert isinstance(json.loads(r.output)["exit_code"], int)
197
198 def test_j_alias_exit_code_present(self, impact_repo: pathlib.Path) -> None:
199 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
200 assert "exit_code" in json.loads(r.output)
201
202 def test_exit_code_mirrors_process_exit(self, impact_repo: pathlib.Path) -> None:
203 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
204 assert json.loads(r.output)["exit_code"] == r.exit_code
205
206 def test_forward_mode_exit_code_zero(self, impact_repo: pathlib.Path) -> None:
207 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
208 assert r.exit_code == 0
209 assert json.loads(r.output)["exit_code"] == 0
210
211 def test_exit_code_leaf_symbol(self, impact_repo: pathlib.Path) -> None:
212 """exit_code is 0 even for a symbol with no callers."""
213 r = _run(impact_repo, "code", "impact", "core.py::validate", "--json")
214 assert r.exit_code == 0
215 assert json.loads(r.output)["exit_code"] == 0
216
217
218 # ---------------------------------------------------------------------------
219 # TestTypedDicts — _ImpactJson carries exit_code and duration_ms
220 # ---------------------------------------------------------------------------
221
222
223 class TestTypedDicts:
224 """_ImpactJson must carry exit_code and duration_ms annotations."""
225
226 def test_impact_json_typeddict_exists(self) -> None:
227 from muse.cli.commands.impact import _ImpactJson # noqa: F401
228
229 def test_has_exit_code_annotation(self) -> None:
230 from muse.cli.commands.impact import _ImpactJson
231 assert "exit_code" in _ImpactJson.__annotations__
232
233 def test_has_duration_ms_annotation(self) -> None:
234 from muse.cli.commands.impact import _ImpactJson
235 assert "duration_ms" in _ImpactJson.__annotations__
236
237 def test_retains_blast_radius_annotation(self) -> None:
238 from muse.cli.commands.impact import _ImpactJson
239 assert "blast_radius" in _ImpactJson.__annotations__
240
241 def test_retains_mode_annotation(self) -> None:
242 from muse.cli.commands.impact import _ImpactJson
243 assert "mode" in _ImpactJson.__annotations__
244
245 def test_retains_address_annotation(self) -> None:
246 from muse.cli.commands.impact import _ImpactJson
247 assert "address" in _ImpactJson.__annotations__
248
249 def test_retains_total_annotation(self) -> None:
250 from muse.cli.commands.impact import _ImpactJson
251 assert "total" in _ImpactJson.__annotations__
252
253
254 # ---------------------------------------------------------------------------
255 # TestDocstrings — run() docstring documents exit_code and duration_ms
256 # ---------------------------------------------------------------------------
257
258
259 class TestDocstrings:
260 """run() must document exit_code and duration_ms."""
261
262 def test_run_docstring_mentions_exit_code(self) -> None:
263 from muse.cli.commands.impact import run
264 assert run.__doc__ is not None
265 assert "exit_code" in run.__doc__
266
267 def test_run_docstring_mentions_duration_ms(self) -> None:
268 from muse.cli.commands.impact import run
269 assert run.__doc__ is not None
270 assert "duration_ms" in run.__doc__
271
272
273 # ---------------------------------------------------------------------------
274 # TestAnsiSanitization — no escape codes in JSON output
275 # ---------------------------------------------------------------------------
276
277
278 class TestAnsiSanitization:
279 """No ANSI escape sequences anywhere in the JSON output."""
280
281 def test_json_output_no_ansi(self, impact_repo: pathlib.Path) -> None:
282 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json")
283 assert "\x1b" not in r.output
284
285 def test_j_alias_output_no_ansi(self, impact_repo: pathlib.Path) -> None:
286 r = _run(impact_repo, "code", "impact", "core.py::compute", "-j")
287 assert "\x1b" not in r.output
288
289 def test_forward_mode_no_ansi(self, impact_repo: pathlib.Path) -> None:
290 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
291 assert "\x1b" not in r.output
292
293
294 # ---------------------------------------------------------------------------
295 # TestForwardMode — forward mode shape
296 # ---------------------------------------------------------------------------
297
298
299 class TestForwardMode:
300 """--forward mode must emit a valid, distinct JSON shape."""
301
302 def test_forward_has_callees_key(self, impact_repo: pathlib.Path) -> None:
303 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
304 assert r.exit_code == 0, r.output
305 assert "callees" in json.loads(r.output)
306
307 def test_forward_mode_field_is_forward(self, impact_repo: pathlib.Path) -> None:
308 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
309 assert json.loads(r.output)["mode"] == "forward"
310
311 def test_forward_has_total_key(self, impact_repo: pathlib.Path) -> None:
312 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
313 data = json.loads(r.output)
314 assert "total" in data
315 assert isinstance(data["total"], int)
316
317 def test_forward_and_json_not_reverse(self, impact_repo: pathlib.Path) -> None:
318 r = _run(impact_repo, "code", "impact", "core.py::compute", "--json", "--forward")
319 data = json.loads(r.output)
320 assert "blast_radius" not in data
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 138 days ago