"""Tests for ``muse code codemap``. Coverage layers --------------- Unit _build_import_graph — edge deduplication, self-loop exclusion, empty maps. _find_cycles — empty graph, linear chain, simple cycle, multi-cycle, overlapping cycles, self-loop, deep chain (no stack overflow), disconnected components, O(1) index lookup. Integration (live repo via CliRunner) Exits zero for valid invocations. JSON schema: all required top-level keys present. JSON schema: new fields — ``branch``, ``agent_safe_zones``. ``--top`` flag limits each ranked section. ``--top 0`` and ``--top -1`` are rejected with a non-zero exit. ``--min-importers`` filters ranked module list correctly. ``--language`` filter restricts analysis to the named language. ``--language`` with no match exits zero but emits empty modules list. ``--commit REF`` analyses a historical snapshot. Text output contains all expected section headers. No-repo invocation exits non-zero. Empty repo (no commits yet) exits non-zero or returns gracefully. E2E (real cross-file import cycles in a live repo) Cycle is detected when two Python files import each other. No false-positive cycle when imports are acyclic. Agent-safe zone reported for a fully isolated file. Stress 1 000-node linear chain: completes without RecursionError. 500-node graph with 50 embedded 3-cycles: all cycles found, no crash. Repeated runs produce identical output (determinism). Large sym_map with duplicate import records: edges deduplicated. """ from __future__ import annotations import json import pathlib import textwrap import time from typing import TypedDict import pytest from tests.cli_test_helper import CliRunner from muse.cli.commands.codemap import _build_import_graph, _find_cycles from muse.plugins.code.ast_parser import SymbolTree, SymbolRecord type _SymbolMap = dict[str, SymbolTree] type _AdjacencyMap = dict[str, list[str]] cli = None # argparse migration — CliRunner ignores this arg runner = CliRunner() # --------------------------------------------------------------------------- # Typed payload for JSON assertions — mirrors the codemap JSON schema. # --------------------------------------------------------------------------- class _ModuleEntry(TypedDict): file: str symbol_count: int importers: int imports: int class _CentralityEntry(TypedDict): name: str callers: int class _BoundaryEntry(TypedDict): file: str fan_out: int fan_in: int class _CodemapPayload(TypedDict): schema_version: str commit: str branch: str language_filter: str | None modules: list[_ModuleEntry] import_cycles: list[list[str]] high_centrality: list[_CentralityEntry] boundary_files: list[_BoundaryEntry] agent_safe_zones: list[str] # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Fresh code-domain Muse repo, no commits.""" monkeypatch.chdir(tmp_path) monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) result = runner.invoke(cli, ["init", "--domain", "code"]) assert result.exit_code == 0, result.output return tmp_path @pytest.fixture def code_repo(repo: pathlib.Path) -> pathlib.Path: """Repo with two Python commits — same fixture shape as other code tests.""" work = repo (work / "billing.py").write_text(textwrap.dedent("""\ class Invoice: def compute_total(self, items): return sum(items) def apply_discount(self, total, pct): return total * (1 - pct) def process_order(invoice, items): return invoice.compute_total(items) """)) r = runner.invoke(cli, ["commit", "-m", "Initial billing module"]) assert r.exit_code == 0, r.output (work / "billing.py").write_text(textwrap.dedent("""\ class Invoice: def compute_invoice_total(self, items): return sum(items) def apply_discount(self, total, pct): return total * (1 - pct) def generate_pdf(self): return b"pdf" def process_order(invoice, items): return invoice.compute_invoice_total(items) def send_email(address): pass """)) runner.invoke(cli, ["code", "add", "."]) r = runner.invoke(cli, ["commit", "-m", "Rename + add generate_pdf, send_email"]) assert r.exit_code == 0, r.output return repo @pytest.fixture def multi_file_repo(repo: pathlib.Path) -> pathlib.Path: """Repo with multiple files to exercise import-graph and cycle detection.""" work = repo (work / "utils.py").write_text(textwrap.dedent("""\ def helper(): pass """)) (work / "models.py").write_text(textwrap.dedent("""\ import utils class User: pass """)) (work / "api.py").write_text(textwrap.dedent("""\ import models import utils def handle(): pass """)) (work / "standalone.py").write_text(textwrap.dedent("""\ def isolated(): pass """)) r = runner.invoke(cli, ["commit", "-m", "Multi-file layout"]) assert r.exit_code == 0, r.output return repo @pytest.fixture def cycle_repo(repo: pathlib.Path) -> pathlib.Path: """Repo with a deliberate circular import: alpha ↔ beta.""" work = repo (work / "alpha.py").write_text(textwrap.dedent("""\ import beta def do_alpha(): pass """)) (work / "beta.py").write_text(textwrap.dedent("""\ import alpha def do_beta(): pass """)) r = runner.invoke(cli, ["commit", "-m", "Introduce alpha-beta cycle"]) assert r.exit_code == 0, r.output return repo # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_sym_tree(*import_names: str) -> SymbolTree: """Build a minimal SymbolTree matching the real parse_symbols format. ``name`` holds the bare module name (e.g. ``"utils"``). ``qualified_name`` uses the ``import::NAME`` format that ``parse_symbols`` actually produces. """ tree: SymbolTree = {} for name in import_names: rec: SymbolRecord = { "kind": "import", "name": name, "qualified_name": f"import::{name}", "lineno": 1, "end_lineno": 1, "content_id": "", "body_hash": "", "signature_id": "", "metadata_id": "", "canonical_key": "", } tree[f"import::{name}"] = rec return tree def _codemap_json(args: list[str] | None = None) -> _CodemapPayload: """Invoke codemap --json and return the typed payload.""" cmd = ["code", "codemap", "--json"] + (args or []) result = runner.invoke(cli, cmd) assert result.exit_code == 0, result.output raw: _CodemapPayload = json.loads(result.output) return raw # --------------------------------------------------------------------------- # Unit — _build_import_graph # --------------------------------------------------------------------------- class TestBuildImportGraph: def test_empty_sym_map_returns_empty_dicts(self) -> None: sym_map: _SymbolMap = {} imports_out, in_degree = _build_import_graph(sym_map) assert imports_out == {} assert in_degree == {} def test_single_file_no_imports(self) -> None: sym_map: _SymbolMap = {"src/a.py": {}} imports_out, in_degree = _build_import_graph(sym_map) assert imports_out == {"src/a.py": []} assert in_degree == {"src/a.py": 0} def test_simple_import_edge(self) -> None: sym_map: _SymbolMap = { "src/a.py": _make_sym_tree("b"), "src/b.py": {}, } imports_out, in_degree = _build_import_graph(sym_map) assert "src/b.py" in imports_out["src/a.py"] assert in_degree["src/b.py"] == 1 assert in_degree["src/a.py"] == 0 def test_self_loop_excluded(self) -> None: """A file importing its own stem must not create a self-edge.""" sym_map: _SymbolMap = { "src/a.py": _make_sym_tree("a"), } imports_out, in_degree = _build_import_graph(sym_map) assert imports_out["src/a.py"] == [] def test_duplicate_import_records_produce_single_edge(self) -> None: """Multiple import records for the same target count as one edge.""" tree: SymbolTree = {} for i in range(5): rec: SymbolRecord = { "kind": "import", "name": "b", # same bare module name — all edges to src/b.py "qualified_name": f"import::b_alias_{i}", "lineno": i + 1, "end_lineno": i + 1, "content_id": "", "body_hash": "", "signature_id": "", "metadata_id": "", "canonical_key": "", } tree[f"import::b_alias_{i}"] = rec sym_map: _SymbolMap = { "src/a.py": tree, "src/b.py": {}, } imports_out, in_degree = _build_import_graph(sym_map) assert imports_out["src/a.py"].count("src/b.py") == 1 assert in_degree["src/b.py"] == 1 def test_unknown_import_ignored(self) -> None: """Imports with no matching stem in the map are silently skipped.""" sym_map: _SymbolMap = { "src/a.py": _make_sym_tree("nonexistent_module"), } imports_out, in_degree = _build_import_graph(sym_map) assert imports_out["src/a.py"] == [] def test_non_import_records_ignored(self) -> None: tree: SymbolTree = {} fn_rec: SymbolRecord = { "kind": "function", "name": "b", "qualified_name": "b", "lineno": 1, "end_lineno": 3, "content_id": "", "body_hash": "", "signature_id": "", "metadata_id": "", "canonical_key": "", } tree["function::b"] = fn_rec sym_map: _SymbolMap = { "src/a.py": tree, "src/b.py": {}, } imports_out, _ = _build_import_graph(sym_map) assert imports_out["src/a.py"] == [] def test_fan_out_multiple_targets(self) -> None: sym_map: _SymbolMap = { "src/a.py": _make_sym_tree("b", "c", "d"), "src/b.py": {}, "src/c.py": {}, "src/d.py": {}, } imports_out, in_degree = _build_import_graph(sym_map) assert len(imports_out["src/a.py"]) == 3 assert in_degree["src/b.py"] == 1 assert in_degree["src/c.py"] == 1 assert in_degree["src/d.py"] == 1 def test_fan_in_multiple_importers(self) -> None: sym_map: _SymbolMap = { "src/a.py": _make_sym_tree("c"), "src/b.py": _make_sym_tree("c"), "src/c.py": {}, } imports_out, in_degree = _build_import_graph(sym_map) assert in_degree["src/c.py"] == 2 # --------------------------------------------------------------------------- # Unit — _find_cycles # --------------------------------------------------------------------------- class TestFindCycles: def test_empty_graph(self) -> None: assert _find_cycles({}) == [] def test_single_node_no_edges(self) -> None: assert _find_cycles({"A": []}) == [] def test_linear_chain_no_cycle(self) -> None: g: _AdjacencyMap = {"A": ["B"], "B": ["C"], "C": []} assert _find_cycles(g) == [] def test_self_loop(self) -> None: g: _AdjacencyMap = {"A": ["A"]} cycles = _find_cycles(g) assert len(cycles) == 1 assert cycles[0][0] == cycles[0][-1] == "A" def test_simple_two_node_cycle(self) -> None: g: _AdjacencyMap = {"A": ["B"], "B": ["A"]} cycles = _find_cycles(g) assert any("A" in c and "B" in c for c in cycles) def test_three_node_cycle(self) -> None: g: _AdjacencyMap = {"A": ["B"], "B": ["C"], "C": ["A"]} cycles = _find_cycles(g) assert len(cycles) >= 1 cycle = cycles[0] assert cycle[0] == cycle[-1] assert len(cycle) == 4 # A→B→C→A def test_two_independent_cycles(self) -> None: g: _AdjacencyMap = { "A": ["B"], "B": ["A"], # cycle 1 "C": ["D"], "D": ["C"], # cycle 2 } cycles = _find_cycles(g) assert len(cycles) == 2 def test_overlapping_cycles_shared_node(self) -> None: """Node B participates in both A→B→A and B→C→B.""" g: _AdjacencyMap = { "A": ["B"], "B": ["A", "C"], "C": ["B"], } cycles = _find_cycles(g) assert len(cycles) >= 2 def test_disconnected_graph_with_cycle_in_one_component(self) -> None: g: _AdjacencyMap = { "X": ["Y"], "Y": [], # acyclic component "A": ["B"], "B": ["A"], # cyclic component } cycles = _find_cycles(g) assert len(cycles) == 1 def test_cycle_path_forms_closed_ring(self) -> None: g: _AdjacencyMap = {"A": ["B"], "B": ["C"], "C": ["A"]} cycles = _find_cycles(g) for cycle in cycles: assert cycle[0] == cycle[-1], "cycle path must start and end at the same node" def test_deep_linear_chain_no_recursion_error(self) -> None: depth = 1_000 # well beyond Python's default recursion limit nodes = [f"mod_{i}" for i in range(depth)] g: _AdjacencyMap = {nodes[i]: [nodes[i + 1]] for i in range(depth - 1)} g[nodes[-1]] = [] cycles = _find_cycles(g) assert cycles == [] def test_deep_cycle_at_end_of_long_chain(self) -> None: depth = 500 nodes = [f"mod_{i}" for i in range(depth)] g: _AdjacencyMap = {nodes[i]: [nodes[i + 1]] for i in range(depth - 1)} g[nodes[-1]] = [nodes[-2]] # last two form a cycle cycles = _find_cycles(g) assert any(nodes[-1] in c or nodes[-2] in c for c in cycles) def test_no_duplicate_cycles_for_same_back_edge(self) -> None: g: _AdjacencyMap = {"A": ["B"], "B": ["A"]} cycles = _find_cycles(g) # There should be exactly one detected cycle for a simple two-node ring. assert len(cycles) == 1 def test_fully_connected_triangle(self) -> None: g: _AdjacencyMap = {"A": ["B", "C"], "B": ["A", "C"], "C": ["A", "B"]} cycles = _find_cycles(g) assert len(cycles) >= 1 def test_index_extraction_is_correct(self) -> None: """Cycle slice starts at the actual back-edge target, not index 0.""" g: _AdjacencyMap = {"A": ["B"], "B": ["C"], "C": ["B"]} cycles = _find_cycles(g) # The cycle involves B and C, not A. for cycle in cycles: assert "A" not in cycle, "A is not part of any cycle in this graph" # --------------------------------------------------------------------------- # Integration — basic CLI invocations # --------------------------------------------------------------------------- class TestCodemapCLIBasic: def test_exits_zero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap"]) assert result.exit_code == 0, result.output def test_text_output_has_all_sections(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap"]) assert result.exit_code == 0 out = result.output assert "Semantic codemap" in out assert "Top modules by size" in out assert "Import cycles" in out assert "High-centrality" in out assert "Boundary files" in out assert "Agent-safe zones" in out def test_text_output_contains_commit_hash(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap"]) assert result.exit_code == 0 import re assert re.search(r"commit sha256:[0-9a-f]{64}", result.output) def test_no_repo_exits_nonzero( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.chdir(tmp_path) monkeypatch.delenv("MUSE_REPO_ROOT", raising=False) result = runner.invoke(cli, ["code", "codemap"]) assert result.exit_code != 0 # --------------------------------------------------------------------------- # Integration — JSON schema # --------------------------------------------------------------------------- class TestCodemapJSONSchema: def test_json_exits_zero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--json"]) assert result.exit_code == 0, result.output def test_json_is_valid(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--json"]) assert result.exit_code == 0 data = json.loads(result.output) assert isinstance(data, dict) def test_json_required_top_level_keys(self, code_repo: pathlib.Path) -> None: data = _codemap_json() required = { "schema", "commit", "branch", "language_filter", "modules", "import_cycles", "high_centrality", "boundary_files", "agent_safe_zones", } assert required <= data.keys() def test_json_modules_is_list(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["modules"], list) def test_json_import_cycles_is_list(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["import_cycles"], list) def test_json_high_centrality_is_list(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["high_centrality"], list) def test_json_boundary_files_is_list(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["boundary_files"], list) def test_json_agent_safe_zones_is_list(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["agent_safe_zones"], list) def test_json_branch_field_is_string(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["branch"], str) assert data["branch"] # non-empty def test_json_commit_is_full_id(self, code_repo: pathlib.Path) -> None: data = _codemap_json() commit = data["commit"] assert isinstance(commit, str) assert commit.startswith("sha256:") hex_part = commit[len("sha256:"):] assert len(hex_part) == 64 assert all(c in "0123456789abcdef" for c in hex_part) def test_json_language_filter_none_when_unset(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert data["language_filter"] is None def test_json_module_entry_has_required_fields(self, code_repo: pathlib.Path) -> None: data = _codemap_json() modules: list[_ModuleEntry] = data["modules"] if modules: entry = modules[0] assert "file" in entry assert "symbol_count" in entry assert "importers" in entry assert "imports" in entry def test_json_schema_version_matches_package(self, code_repo: pathlib.Path) -> None: data = _codemap_json() assert isinstance(data["schema"], int) assert data["schema"] > 0 # --------------------------------------------------------------------------- # Integration — --top flag # --------------------------------------------------------------------------- class TestCodemapTopFlag: def test_top_1_limits_modules_to_1(self, code_repo: pathlib.Path) -> None: data = _codemap_json(["--top", "1"]) assert len(data["modules"]) <= 1 def test_top_3_limits_sections(self, code_repo: pathlib.Path) -> None: data = _codemap_json(["--top", "3"]) assert len(data["modules"]) <= 3 assert len(data["high_centrality"]) <= 3 assert len(data["boundary_files"]) <= 3 assert len(data["agent_safe_zones"]) <= 3 def test_top_zero_exits_nonzero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--top", "0"]) assert result.exit_code != 0 def test_top_negative_exits_nonzero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--top", "-1"]) assert result.exit_code != 0 def test_top_text_mode_respected(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--top", "1"]) assert result.exit_code == 0 # --------------------------------------------------------------------------- # Integration — --min-importers flag # --------------------------------------------------------------------------- class TestCodemapMinImporters: def test_min_importers_zero_includes_all(self, multi_file_repo: pathlib.Path) -> None: data_all = _codemap_json(["--min-importers", "0"]) data_zero = _codemap_json() # default = 0 assert len(data_all["modules"]) == len(data_zero["modules"]) def test_min_importers_1_excludes_unimported(self, multi_file_repo: pathlib.Path) -> None: data_all = _codemap_json() data_filtered = _codemap_json(["--min-importers", "1"]) modules_all: list[_ModuleEntry] = data_all["modules"] modules_filtered: list[_ModuleEntry] = data_filtered["modules"] # Every module in the filtered list must have importers >= 1. for mod in modules_filtered: assert mod["importers"] >= 1 # Filtered list cannot be larger than unfiltered. assert len(modules_filtered) <= len(modules_all) def test_min_importers_very_large_returns_empty_modules( self, code_repo: pathlib.Path ) -> None: data = _codemap_json(["--min-importers", "9999"]) assert data["modules"] == [] def test_min_importers_negative_exits_nonzero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--min-importers", "-1"]) assert result.exit_code != 0 def test_min_importers_label_in_text_output(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--min-importers", "2"]) assert result.exit_code == 0 assert "min-importers" in result.output # --------------------------------------------------------------------------- # Integration — --language flag # --------------------------------------------------------------------------- class TestCodemapLanguageFlag: def test_language_python_exits_zero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--language", "Python"]) assert result.exit_code == 0 def test_language_filter_in_json(self, code_repo: pathlib.Path) -> None: data = _codemap_json(["--language", "Python"]) assert data["language_filter"] == "Python" def test_language_no_match_exits_zero_empty_modules( self, code_repo: pathlib.Path ) -> None: data = _codemap_json(["--language", "COBOL"]) assert data["modules"] == [] def test_language_text_header_shown(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--language", "Python"]) assert result.exit_code == 0 assert "language: Python" in result.output # --------------------------------------------------------------------------- # Integration — --commit flag # --------------------------------------------------------------------------- class TestCodemapCommitFlag: def test_commit_head_is_default(self, code_repo: pathlib.Path) -> None: data_head = _codemap_json(["--commit", "HEAD"]) data_default = _codemap_json() assert data_head["commit"] == data_default["commit"] def test_commit_head_minus_1(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--commit", "HEAD~1", "--json"]) assert result.exit_code == 0 data = json.loads(result.output) data_head = _codemap_json() # Historical snapshot commit id must differ from HEAD. assert data["commit"] != data_head["commit"] def test_commit_invalid_ref_exits_nonzero(self, code_repo: pathlib.Path) -> None: result = runner.invoke(cli, ["code", "codemap", "--commit", "totally_bogus_ref_xyz"]) assert result.exit_code != 0 # --------------------------------------------------------------------------- # Integration — empty repo # --------------------------------------------------------------------------- class TestCodemapEmptyRepo: def test_no_commits_exits_nonzero(self, repo: pathlib.Path) -> None: """Repo with no commits: HEAD does not resolve — must exit non-zero.""" result = runner.invoke(cli, ["code", "codemap"]) assert result.exit_code != 0 # --------------------------------------------------------------------------- # E2E — cycle detection # --------------------------------------------------------------------------- class TestCodemapCycleE2E: def test_circular_import_detected(self, cycle_repo: pathlib.Path) -> None: data = _codemap_json() cycles: list[list[str]] = data["import_cycles"] assert len(cycles) >= 1 involved = {node for cycle in cycles for node in cycle} # alpha.py and beta.py should both appear in the cycle paths. assert any("alpha" in node for node in involved) assert any("beta" in node for node in involved) def test_acyclic_repo_has_no_cycles(self, multi_file_repo: pathlib.Path) -> None: data = _codemap_json() assert data["import_cycles"] == [] def test_cycle_paths_are_closed_rings(self, cycle_repo: pathlib.Path) -> None: data = _codemap_json() for cycle in data["import_cycles"]: assert isinstance(cycle, list) assert len(cycle) >= 2 assert cycle[0] == cycle[-1], "cycle path must start and end at the same node" # --------------------------------------------------------------------------- # E2E — agent-safe zones # --------------------------------------------------------------------------- class TestCodemapAgentSafeZones: def test_isolated_file_appears_in_agent_safe_zones( self, multi_file_repo: pathlib.Path ) -> None: """standalone.py imports nothing and is imported by nothing.""" data = _codemap_json() safe: list[str] = data["agent_safe_zones"] assert any("standalone" in fp for fp in safe) def test_imported_file_not_in_agent_safe_zones( self, multi_file_repo: pathlib.Path ) -> None: """utils.py is imported by models.py and api.py — not isolated.""" data = _codemap_json() safe: list[str] = data["agent_safe_zones"] assert not any("utils" in fp for fp in safe) def test_agent_safe_zones_are_sorted(self, multi_file_repo: pathlib.Path) -> None: data = _codemap_json() safe: list[str] = data["agent_safe_zones"] assert safe == sorted(safe) # --------------------------------------------------------------------------- # E2E — boundary files # --------------------------------------------------------------------------- class TestCodemapBoundaryFiles: def test_boundary_entry_has_required_fields(self, multi_file_repo: pathlib.Path) -> None: data = _codemap_json() for boundary in data["boundary_files"]: assert "file" in boundary assert "fan_out" in boundary assert "fan_in" in boundary def test_boundary_file_fan_in_is_zero(self, multi_file_repo: pathlib.Path) -> None: data = _codemap_json() for boundary in data["boundary_files"]: assert boundary["fan_in"] == 0 def test_boundary_file_fan_out_at_least_3(self, multi_file_repo: pathlib.Path) -> None: data = _codemap_json() for boundary in data["boundary_files"]: assert boundary["fan_out"] >= 3 # --------------------------------------------------------------------------- # Stress — performance and determinism # --------------------------------------------------------------------------- class TestCodemapStress: def test_find_cycles_1000_node_linear_chain_no_recursion_error(self) -> None: depth = 1_000 nodes = [f"file_{i}.py" for i in range(depth)] g: _AdjacencyMap = {nodes[i]: [nodes[i + 1]] for i in range(depth - 1)} g[nodes[-1]] = [] cycles = _find_cycles(g) assert cycles == [] def test_find_cycles_500_nodes_50_embedded_3_cycles(self) -> None: """500-node graph with 50 explicit A→B→C→A triangles plus 350 isolated nodes.""" g: _AdjacencyMap = {} expected_min = 50 for i in range(50): a, b, c = f"a_{i}", f"b_{i}", f"c_{i}" g[a] = [b] g[b] = [c] g[c] = [a] for i in range(350): g[f"iso_{i}"] = [] cycles = _find_cycles(g) assert len(cycles) >= expected_min def test_build_import_graph_large_sym_map_with_duplicates(self) -> None: """1 000 files each importing the same hub file via 10 duplicate records.""" hub = "src/hub.py" sym_map: _SymbolMap = {hub: {}} for i in range(999): fp = f"src/module_{i}.py" tree: SymbolTree = {} for j in range(10): rec: SymbolRecord = { "kind": "import", "name": "hub", # same bare module — all edges to hub "qualified_name": f"import::hub_alias_{j}", "lineno": j + 1, "end_lineno": j + 1, "content_id": "", "body_hash": "", "signature_id": "", "metadata_id": "", "canonical_key": "", } tree[f"import::hub_alias_{j}"] = rec sym_map[fp] = tree imports_out, in_degree = _build_import_graph(sym_map) assert in_degree[hub] == 999 for fp in sym_map: if fp == hub: continue assert imports_out[fp].count(hub) == 1, "each file must have exactly one edge to hub" def test_repeated_runs_produce_identical_json(self, code_repo: pathlib.Path) -> None: """Two back-to-back invocations must produce the exact same JSON.""" result_a = runner.invoke(cli, ["code", "codemap", "--json"]) result_b = runner.invoke(cli, ["code", "codemap", "--json"]) assert result_a.exit_code == 0 assert result_b.exit_code == 0 _volatile = {"duration_ms", "timestamp"} da = {k: v for k, v in json.loads(result_a.output).items() if k not in _volatile} db = {k: v for k, v in json.loads(result_b.output).items() if k not in _volatile} assert da == db def test_codemap_completes_within_reasonable_time( self, code_repo: pathlib.Path ) -> None: """Codemap on a small repo must finish within 10 seconds.""" start = time.monotonic() result = runner.invoke(cli, ["code", "codemap", "--json"]) elapsed = time.monotonic() - start assert result.exit_code == 0 assert elapsed < 10.0, f"codemap took {elapsed:.1f}s — too slow" # --------------------------------------------------------------------------- # Flag registration tests # --------------------------------------------------------------------------- import argparse as _argparse from muse.cli.commands.codemap import register as _register_codemap def _parse_codemap(*args: str) -> _argparse.Namespace: root_p = _argparse.ArgumentParser() subs = root_p.add_subparsers(dest="cmd") _register_codemap(subs) return root_p.parse_args(["codemap", *args]) class TestRegisterFlags: def test_default_json_out_is_false(self) -> None: ns = _parse_codemap() assert ns.json_out is False def test_json_flag_sets_json_out(self) -> None: ns = _parse_codemap("--json") assert ns.json_out is True def test_j_shorthand_sets_json_out(self) -> None: ns = _parse_codemap("-j") assert ns.json_out is True