test_cmd_symlog_analytics.py
python
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