gabriel / muse public
test_phase3_noncmt_bfs_migration.py python
151 lines 5.8 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
1 """TDD Phase 3 — migrate non-commit graph BFS/DFS to walk_dag.
2
3 Target sites
4 ------------
5 shard._connected_components — DFS connected-components over file coupling graph
6 identity/dag._DAG.would_cycle — DFS cycle detection for identity relationship DAG
7
8 Intentionally NOT migrated
9 --------------------------
10 callgraph.transitive_callers / transitive_callees: output format is
11 {depth: [name, ...]}, which requires tracking BFS depth alongside nodes.
12 walk_dag yields a flat node stream — encoding depth into the node type
13 (e.g. (name, depth) tuples) would make the code harder to read. These
14 functions have a genuinely different shape and are a sanctioned exception.
15 """
16
17 from __future__ import annotations
18
19 import inspect
20 import types
21
22
23 # ---------------------------------------------------------------------------
24 # shard._connected_components
25 # ---------------------------------------------------------------------------
26
27 class TestShardConnectedComponents:
28 def _fn(self) -> types.FunctionType:
29 from muse.cli.commands import shard as shard_module
30 return shard_module._connected_components
31
32 def test_structural_no_inline_dfs(self) -> None:
33 """_connected_components must not use an inline stack-based DFS after migration."""
34 src = inspect.getsource(self._fn())
35 assert "while stack:" not in src and "stack.pop()" not in src, (
36 "shard._connected_components still uses an inline DFS stack — "
37 "migrate to walk_dag(order='dfs')"
38 )
39
40 def test_single_node_single_component(self) -> None:
41 result = self._fn()(["a"], [])
42 assert len(result) == 1
43 assert result[0] == frozenset({"a"})
44
45 def test_two_connected_nodes_one_component(self) -> None:
46 result = self._fn()(["a", "b"], [("a", "b")])
47 assert len(result) == 1
48 assert result[0] == frozenset({"a", "b"})
49
50 def test_two_disconnected_nodes_two_components(self) -> None:
51 result = self._fn()(["a", "b"], [])
52 assert len(result) == 2
53 components = {frozenset(c) for c in result}
54 assert frozenset({"a"}) in components
55 assert frozenset({"b"}) in components
56
57 def test_triangle_one_component(self) -> None:
58 result = self._fn()(["a", "b", "c"], [("a", "b"), ("b", "c"), ("a", "c")])
59 assert len(result) == 1
60 assert result[0] == frozenset({"a", "b", "c"})
61
62 def test_two_disconnected_pairs(self) -> None:
63 result = self._fn()(["a", "b", "c", "d"], [("a", "b"), ("c", "d")])
64 assert len(result) == 2
65 components = {frozenset(c) for c in result}
66 assert frozenset({"a", "b"}) in components
67 assert frozenset({"c", "d"}) in components
68
69 def test_edges_are_undirected(self) -> None:
70 """Edge (a, b) means both a→b and b→a for connectivity."""
71 result = self._fn()(["a", "b"], [("a", "b")])
72 assert len(result) == 1
73 assert "a" in result[0] and "b" in result[0]
74
75 def test_empty_input(self) -> None:
76 assert self._fn()([], []) == []
77
78 def test_no_node_duplicated_across_components(self) -> None:
79 """Every node appears in exactly one component."""
80 files = ["a", "b", "c", "d", "e"]
81 edges = [("a", "b"), ("c", "d")]
82 result = self._fn()(files, edges)
83 all_nodes = [n for comp in result for n in comp]
84 assert len(all_nodes) == len(set(all_nodes))
85 assert set(all_nodes) == set(files)
86
87
88 # ---------------------------------------------------------------------------
89 # identity/dag._DAG.would_cycle
90 # ---------------------------------------------------------------------------
91
92 class TestDagWouldCycle:
93 def _cls(self) -> type:
94 from muse.plugins.identity import dag as dag_module
95 return dag_module._DAG
96
97 def test_structural_no_inline_dfs(self) -> None:
98 """_DAG.would_cycle must not use an inline DFS stack after migration."""
99 src = inspect.getsource(self._cls().would_cycle)
100 assert "while stack:" not in src and "stack.pop()" not in src, (
101 "identity/_DAG.would_cycle still uses an inline DFS stack — "
102 "migrate to walk_dag(order='dfs')"
103 )
104
105 def test_self_loop_is_cycle(self) -> None:
106 dag = self._cls()()
107 assert dag.would_cycle("a", "a") is True
108
109 def test_no_edges_no_cycle(self) -> None:
110 dag = self._cls()()
111 assert dag.would_cycle("a", "b") is False
112
113 def test_direct_forward_edge_no_cycle(self) -> None:
114 """a→b exists; adding a→b again is fine — no back-edge to a."""
115 dag = self._cls()()
116 dag.add_edge("a", "b")
117 assert dag.would_cycle("a", "b") is False
118
119 def test_back_edge_is_cycle(self) -> None:
120 """a→b exists; b→a would create a cycle."""
121 dag = self._cls()()
122 dag.add_edge("a", "b")
123 assert dag.would_cycle("b", "a") is True
124
125 def test_transitive_cycle(self) -> None:
126 """a→b→c exists; c→a would create a transitive cycle."""
127 dag = self._cls()()
128 dag.add_edge("a", "b")
129 dag.add_edge("b", "c")
130 assert dag.would_cycle("c", "a") is True
131
132 def test_no_transitive_cycle(self) -> None:
133 """a→b→c exists; d→a is fine (d is not reachable from a)."""
134 dag = self._cls()()
135 dag.add_edge("a", "b")
136 dag.add_edge("b", "c")
137 assert dag.would_cycle("d", "a") is False
138
139 def test_diamond_cycle_detection(self) -> None:
140 """a→b, a→c, b→d, c→d; d→a would cycle."""
141 dag = self._cls()()
142 dag.add_edge("a", "b")
143 dag.add_edge("a", "c")
144 dag.add_edge("b", "d")
145 dag.add_edge("c", "d")
146 assert dag.would_cycle("d", "a") is True
147
148 def test_unrelated_nodes_no_cycle(self) -> None:
149 dag = self._cls()()
150 dag.add_edge("x", "y")
151 assert dag.would_cycle("a", "b") is False
File History 4 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 23 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 29 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago