gabriel / muse public
test_cmd_symlog_analytics.py python
182 lines 6.8 KB
Raw
sha256:ce542c4d0f6dacc9520c02d0e3378bf96bc377d118a7402f2bbc8af1749b08e0 feat(code-test): audit cache internals, add cache_hit/--cac… Sonnet 5 minor ⚠ breaking 5 hours ago
1 """muse#53 item 1 -- `muse symlog hotspots` and `muse symlog coupling`.
2
3 Analytics commands reading purely from the already-written symlog
4 journal, with no commit-history scan.
5 """
6
7 from __future__ import annotations
8
9 import json
10 import os
11 import pathlib
12 import textwrap
13
14 from tests.cli_test_helper import CliRunner
15
16 runner = CliRunner()
17
18
19 def _invoke(repo: pathlib.Path, args: list[str]):
20 saved = os.getcwd()
21 try:
22 os.chdir(repo)
23 return runner.invoke(None, args)
24 finally:
25 os.chdir(saved)
26
27
28 def _init_repo(repo: pathlib.Path) -> None:
29 repo.mkdir(parents=True, exist_ok=True)
30 _invoke(repo, ["init"])
31
32
33 def _write_file(repo: pathlib.Path, rel: str, content: str) -> None:
34 p = repo / rel
35 p.parent.mkdir(parents=True, exist_ok=True)
36 p.write_text(textwrap.dedent(content))
37
38
39 def _commit(repo: pathlib.Path, msg: str) -> str:
40 _invoke(repo, ["code", "add", "."])
41 result = _invoke(repo, [
42 "commit", "-m", msg,
43 "--agent-id", "claude-code",
44 "--model-id", "claude-sonnet-4-6",
45 "--author", "claude-code",
46 "--json",
47 ])
48 data = json.loads(result.stdout)
49 return data["commit_id"]
50
51
52 def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
53 _init_repo(tmp_path)
54 return tmp_path
55
56
57 def _symlog(repo: pathlib.Path, *args: str):
58 return _invoke(repo, ["symlog", *args])
59
60
61 class TestSymlogHotspots:
62 def test_ranks_by_modification_count(self, tmp_path: pathlib.Path) -> None:
63 repo = _make_repo(tmp_path)
64 _write_file(repo, "src/billing.py", (
65 "def compute_total(items):\n return sum(items)\n\n"
66 "def apply_discount(total, pct):\n return total * (1 - pct)\n"
67 ))
68 _commit(repo, "add two functions")
69 # Modify compute_total twice, apply_discount once.
70 _write_file(repo, "src/billing.py", (
71 "def compute_total(items):\n return sum(items) + 1\n\n"
72 "def apply_discount(total, pct):\n return total * (1 - pct) - 1\n"
73 ))
74 _commit(repo, "modify both")
75 _write_file(repo, "src/billing.py", (
76 "def compute_total(items):\n return sum(items) + 2\n\n"
77 "def apply_discount(total, pct):\n return total * (1 - pct) - 1\n"
78 ))
79 _commit(repo, "modify compute_total again")
80
81 result = _symlog(repo, "hotspots", "--json")
82 assert result.exit_code == 0, result.stdout
83 data = json.loads(result.stdout)
84 assert data["count"] == 2
85 assert data["hotspots"][0]["symbol"] == "src/billing.py::compute_total"
86 assert data["hotspots"][0]["modifications"] == 2
87 assert data["hotspots"][1]["symbol"] == "src/billing.py::apply_discount"
88 assert data["hotspots"][1]["modifications"] == 1
89
90 def test_creation_only_excluded(self, tmp_path: pathlib.Path) -> None:
91 repo = _make_repo(tmp_path)
92 _write_file(repo, "src/billing.py", "def compute_total(items):\n return sum(items)\n")
93 _commit(repo, "add compute_total")
94
95 result = _symlog(repo, "hotspots", "--json")
96 data = json.loads(result.stdout)
97 assert data["count"] == 0
98
99 def test_file_scoping(self, tmp_path: pathlib.Path) -> None:
100 repo = _make_repo(tmp_path)
101 _write_file(repo, "src/billing.py", "def compute_total(items):\n return sum(items)\n")
102 _write_file(repo, "src/other.py", "def unrelated():\n pass\n")
103 _commit(repo, "add two files")
104 _write_file(repo, "src/billing.py", "def compute_total(items):\n return sum(items) + 1\n")
105 _write_file(repo, "src/other.py", "def unrelated():\n return 1\n")
106 _commit(repo, "modify both")
107
108 result = _symlog(repo, "hotspots", "--file", "src/billing.py", "--json")
109 data = json.loads(result.stdout)
110 assert data["count"] == 1
111 assert data["hotspots"][0]["symbol"] == "src/billing.py::compute_total"
112
113 def test_top_limits_results(self, tmp_path: pathlib.Path) -> None:
114 repo = _make_repo(tmp_path)
115 _write_file(repo, "src/billing.py", (
116 "def a():\n return 1\n\ndef b():\n return 2\n"
117 ))
118 _commit(repo, "add")
119 _write_file(repo, "src/billing.py", (
120 "def a():\n return 11\n\ndef b():\n return 22\n"
121 ))
122 _commit(repo, "modify both")
123
124 result = _symlog(repo, "hotspots", "--top", "1", "--json")
125 data = json.loads(result.stdout)
126 assert len(data["hotspots"]) == 1
127
128
129 class TestSymlogCoupling:
130 def test_finds_symbols_that_change_together(self, tmp_path: pathlib.Path) -> None:
131 repo = _make_repo(tmp_path)
132 _write_file(repo, "src/billing.py", (
133 "def compute_total(items):\n return sum(items)\n\n"
134 "def apply_discount(total, pct):\n return total * (1 - pct)\n"
135 ))
136 _commit(repo, "add two functions")
137 _write_file(repo, "src/billing.py", (
138 "def compute_total(items):\n return sum(items) + 1\n\n"
139 "def apply_discount(total, pct):\n return total * (1 - pct) - 1\n"
140 ))
141 _commit(repo, "modify both together")
142
143 result = _symlog(repo, "coupling", "--json")
144 assert result.exit_code == 0, result.stdout
145 data = json.loads(result.stdout)
146 assert data["count"] == 1
147 pair = data["pairs"][0]
148 assert {pair["symbol_a"], pair["symbol_b"]} == {
149 "src/billing.py::compute_total", "src/billing.py::apply_discount",
150 }
151 assert pair["co_changes"] == 2 # both created + both modified together
152
153 def test_below_min_threshold_excluded(self, tmp_path: pathlib.Path) -> None:
154 repo = _make_repo(tmp_path)
155 _write_file(repo, "src/billing.py", (
156 "def compute_total(items):\n return sum(items)\n\n"
157 "def apply_discount(total, pct):\n return total * (1 - pct)\n"
158 ))
159 _commit(repo, "add two functions")
160
161 # Only one shared commit (the creation) -- below the default min of 2.
162 result = _symlog(repo, "coupling", "--json")
163 data = json.loads(result.stdout)
164 assert data["count"] == 0
165
166 def test_min_flag_lowers_threshold(self, tmp_path: pathlib.Path) -> None:
167 repo = _make_repo(tmp_path)
168 _write_file(repo, "src/billing.py", (
169 "def compute_total(items):\n return sum(items)\n\n"
170 "def apply_discount(total, pct):\n return total * (1 - pct)\n"
171 ))
172 _commit(repo, "add two functions")
173
174 result = _symlog(repo, "coupling", "--min", "1", "--json")
175 data = json.loads(result.stdout)
176 assert data["count"] == 1
177
178 def test_no_coupling_in_empty_repo(self, tmp_path: pathlib.Path) -> None:
179 repo = _make_repo(tmp_path)
180 result = _symlog(repo, "coupling", "--json")
181 data = json.loads(result.stdout)
182 assert data["count"] == 0
File History 1 commit
sha256:ce542c4d0f6dacc9520c02d0e3378bf96bc377d118a7402f2bbc8af1749b08e0 feat(code-test): audit cache internals, add cache_hit/--cac… Sonnet 5 minor ⚠ 5 hours ago