"""TDD — log._collect_all_commits must use iter_ancestors, not inline deque BFS. LC1 Structural — _collect_all_commits uses iter_ancestors; no inline deque BFS LC2 Behavioural — collects all commits reachable from start_ids LC3 Behavioural — truncated flag fires at max_commits cap """ from __future__ import annotations import datetime import inspect import json import pathlib import pytest from muse._version import __version__ from muse.core.object_store import write_object from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.core.types import blob_id from muse.core.paths import muse_dir def _repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: dot_muse = muse_dir(tmp_path) for d in ("commits", "snapshots", "objects", "refs/heads", "remotes"): (dot_muse / d).mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") (dot_muse / "repo.json").write_text( json.dumps({"repo_id": "test-repo", "schema_version": __version__, "domain": "code"}) ) monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) monkeypatch.chdir(tmp_path) return tmp_path def _make_commit( root: pathlib.Path, parent_id: str | None = None, *, message: str = "test", ) -> CommitRecord: oid = blob_id(b"data-" + message.encode()) write_object(root, oid, b"data-" + message.encode()) manifest = {"f.py": oid} snap_id = compute_snapshot_id(manifest) write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest)) ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) cid = compute_commit_id( parent_ids=[parent_id] if parent_id else [], snapshot_id=snap_id, message=message, committed_at_iso=ts.isoformat(), ) rec = CommitRecord( commit_id=cid, branch="main", snapshot_id=snap_id, message=message, committed_at=ts, parent_commit_id=parent_id, ) write_commit(root, rec) return rec # --------------------------------------------------------------------------- # LC1 Structural # --------------------------------------------------------------------------- def test_lc1_collect_all_commits_uses_iter_ancestors() -> None: """_collect_all_commits must delegate to iter_ancestors; no inline deque.""" from muse.cli.commands import log as log_mod src = inspect.getsource(log_mod._collect_all_commits) assert "iter_ancestors" in src, ( "_collect_all_commits must delegate to iter_ancestors. " "Replace the inline deque BFS." ) assert "deque" not in src, ( "_collect_all_commits still has an inline deque. Replace with iter_ancestors." ) # --------------------------------------------------------------------------- # LC2 Behavioural — collects all reachable commits # --------------------------------------------------------------------------- def test_lc2_collect_all_commits_returns_all( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """Returns a CommitIndex containing every reachable commit.""" from muse.cli.commands.log import _collect_all_commits # type: ignore[attr-defined] root = _repo(tmp_path, monkeypatch) c1 = _make_commit(root, message="c1") c2 = _make_commit(root, c1.commit_id, message="c2") c3 = _make_commit(root, c2.commit_id, message="c3") index, truncated = _collect_all_commits(root, [c3.commit_id]) assert truncated is False assert c1.commit_id in index assert c2.commit_id in index assert c3.commit_id in index # --------------------------------------------------------------------------- # LC3 Behavioural — truncated flag # --------------------------------------------------------------------------- def test_lc3_collect_all_commits_truncated_flag( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """truncated=True when max_commits is reached before exhausting the DAG.""" from muse.cli.commands.log import _collect_all_commits # type: ignore[attr-defined] root = _repo(tmp_path, monkeypatch) parent_id: str | None = None for i in range(5): c = _make_commit(root, parent_id, message=f"c{i}") parent_id = c.commit_id index, truncated = _collect_all_commits(root, [parent_id], max_commits=2) # type: ignore[arg-type] assert truncated is True assert len(index) == 2