"""muse#53 item 2 -- `muse code blame` prefers the symlog journal (O(1) per symbol) over the full commit-history BFS scan when a journal exists. Correctness parity with the pre-existing BFS path is what matters here (the existing test_cmd_blame*/test_blame_supercharge suites already exercise that path's output and continue to pass unchanged, confirming the symlog fast path produces a structurally compatible result) -- this file targets the NEW decision logic: when the fast path is used, when it falls back, and rename-chain correctness via the journal. """ from __future__ import annotations from collections.abc import Mapping import json import pathlib import textwrap import pytest from tests.cli_test_helper import CliRunner runner = CliRunner() def _env(root: pathlib.Path) -> Mapping[str, str]: return {"MUSE_REPO_ROOT": str(root)} def _run(root: pathlib.Path, *args: str): return runner.invoke(None, list(args), env=_env(root)) @pytest.fixture() def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: monkeypatch.chdir(tmp_path) r = _run(tmp_path, "init", "--domain", "code") assert r.exit_code == 0, r.output return tmp_path def _write(repo: pathlib.Path, rel: str, content: str) -> None: (repo / rel).write_text(textwrap.dedent(content)) def _commit(repo: pathlib.Path, msg: str) -> None: r1 = _run(repo, "code", "add", ".") assert r1.exit_code == 0, r1.output r2 = _run(repo, "commit", "-m", msg) assert r2.exit_code == 0, r2.output class TestSymlogFastPathIsActuallyUsed: """Proves the symlog journal is the mechanism, not just that the final result happens to be correct (the pre-existing BFS path already produces correct results on its own, so output-only assertions can't distinguish the two paths -- these tests break the BFS path and confirm blame still works, or confirm the BFS entry point is never even called).""" def test_blame_still_works_when_bfs_walk_is_broken( self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "double it") import muse.cli.commands.blame as blame_mod def _broken_bfs(*args: object, **kwargs: object) -> None: raise AssertionError("walk_commits_bfs must not be called on the symlog fast path") monkeypatch.setattr(blame_mod, "walk_commits_bfs", _broken_bfs) result = _run(repo, "code", "blame", "billing.py::compute_total", "--json") assert result.exit_code == 0, result.output data = json.loads(result.output) kinds = [e["event"] for e in data["events"]] assert "created" in kinds assert "modified" in kinds def test_from_ref_falls_back_to_bfs_walk( self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """--from forces the full BFS scan -- the journal has no concept of 'as reachable from an older ref' and must not be used here.""" _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "double it") import muse.cli.commands.blame as blame_mod calls: list[bool] = [] real_bfs = blame_mod.walk_commits_bfs def _spy_bfs(*args: object, **kwargs: object): calls.append(True) return real_bfs(*args, **kwargs) monkeypatch.setattr(blame_mod, "walk_commits_bfs", _spy_bfs) result = _run(repo, "code", "blame", "billing.py::compute_total", "--from", "HEAD~1", "--json") assert result.exit_code == 0, result.output assert calls, "walk_commits_bfs should have been called for --from" def test_kind_filter_for_unsupported_kind_falls_back_to_bfs( self, repo: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """A --kind moved/signature query can never be answered from the journal (it doesn't track those categories) -- must fall back.""" _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") import muse.cli.commands.blame as blame_mod calls: list[bool] = [] real_bfs = blame_mod.walk_commits_bfs def _spy_bfs(*args: object, **kwargs: object): calls.append(True) return real_bfs(*args, **kwargs) monkeypatch.setattr(blame_mod, "walk_commits_bfs", _spy_bfs) result = _run(repo, "code", "blame", "billing.py::compute_total", "--kind", "signature", "--json") assert result.exit_code == 0, result.output assert calls, "walk_commits_bfs should have been called for --kind signature" class TestEventsFromSymlogDirect: """Direct unit tests against _events_from_symlog() -- exercises the new function in isolation rather than through blame's path-selection logic.""" def test_returns_none_when_no_journal_exists(self, repo: pathlib.Path) -> None: from muse.cli.commands.blame import _events_from_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") result = _events_from_symlog(repo, "billing.py::nonexistent", 500) assert result is None def test_builds_created_and_modified_events(self, repo: pathlib.Path) -> None: from muse.cli.commands.blame import _events_from_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") _write(repo, "billing.py", "def compute_total(items):\n return sum(items) * 2\n") _commit(repo, "double it") events = _events_from_symlog(repo, "billing.py::compute_total", 500) assert events is not None kinds = [e.kind for e in events] assert kinds == ["modified", "created"] # newest-first assert events[0].commit.message == "double it" def test_deleted_symbol(self, repo: pathlib.Path) -> None: from muse.cli.commands.blame import _events_from_symlog _write(repo, "billing.py", ( "def compute_total(items):\n return sum(items)\n\n" "def apply_discount(total, pct):\n return total * (1 - pct)\n" )) _commit(repo, "add two functions") _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "remove apply_discount") events = _events_from_symlog(repo, "billing.py::apply_discount", 500) assert events is not None assert events[0].kind == "deleted" def test_rename_chain_crosses_address_boundary(self, repo: pathlib.Path) -> None: from muse.cli.commands.blame import _events_from_symlog _write(repo, "billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") _write(repo, "billing.py", "def compute_invoice_total(items):\n return sum(items)\n") _commit(repo, "rename to compute_invoice_total") events = _events_from_symlog(repo, "billing.py::compute_invoice_total", 500) assert events is not None kinds = [e.kind for e in events] assert "created" in kinds # The creation event must be attributed to the OLD address, since # that's what the symbol was called at the time it was created. created_event = next(e for e in events if e.kind == "created") assert created_event.address == "billing.py::compute_total"