Navegador

navegador / tests / test_rust_parser.py
Source Blame History 376 lines
e52aae0… lmata 1 """Tests for navegador.ingestion.rust — RustParser internal methods."""
e52aae0… lmata 2
e52aae0… lmata 3 from unittest.mock import MagicMock, patch
e52aae0… lmata 4
e52aae0… lmata 5 import pytest
e52aae0… lmata 6
e52aae0… lmata 7 from navegador.graph.schema import NodeLabel
e52aae0… lmata 8
e52aae0… lmata 9
e52aae0… lmata 10 class MockNode:
e52aae0… lmata 11 _id_counter = 0
e52aae0… lmata 12
e52aae0… lmata 13 def __init__(self, type_: str, text: bytes = b"", children: list = None,
e52aae0… lmata 14 start_byte: int = 0, end_byte: int = 0,
e52aae0… lmata 15 start_point: tuple = (0, 0), end_point: tuple = (0, 0),
e52aae0… lmata 16 parent=None):
e52aae0… lmata 17 MockNode._id_counter += 1
e52aae0… lmata 18 self.id = MockNode._id_counter
e52aae0… lmata 19 self.type = type_
e52aae0… lmata 20 self._text = text
e52aae0… lmata 21 self.children = children or []
e52aae0… lmata 22 self.start_byte = start_byte
e52aae0… lmata 23 self.end_byte = end_byte
e52aae0… lmata 24 self.start_point = start_point
e52aae0… lmata 25 self.end_point = end_point
e52aae0… lmata 26 self.parent = parent
e52aae0… lmata 27 self._fields: dict = {}
e52aae0… lmata 28 for child in self.children:
e52aae0… lmata 29 child.parent = self
e52aae0… lmata 30
e52aae0… lmata 31 def child_by_field_name(self, name: str):
e52aae0… lmata 32 return self._fields.get(name)
e52aae0… lmata 33
e52aae0… lmata 34 def set_field(self, name: str, node):
e52aae0… lmata 35 self._fields[name] = node
e52aae0… lmata 36 node.parent = self
e52aae0… lmata 37 return self
e52aae0… lmata 38
e52aae0… lmata 39
e52aae0… lmata 40 def _text_node(text: bytes, type_: str = "identifier") -> MockNode:
e52aae0… lmata 41 return MockNode(type_, text, start_byte=0, end_byte=len(text))
e52aae0… lmata 42
e52aae0… lmata 43
e52aae0… lmata 44 def _make_store():
e52aae0… lmata 45 store = MagicMock()
e52aae0… lmata 46 store.query.return_value = MagicMock(result_set=[])
e52aae0… lmata 47 return store
e52aae0… lmata 48
e52aae0… lmata 49
e52aae0… lmata 50 def _make_parser():
e52aae0… lmata 51 from navegador.ingestion.rust import RustParser
e52aae0… lmata 52 parser = RustParser.__new__(RustParser)
e52aae0… lmata 53 parser._parser = MagicMock()
e52aae0… lmata 54 return parser
e52aae0… lmata 55
e52aae0… lmata 56
e52aae0… lmata 57 class TestRustGetLanguage:
e52aae0… lmata 58 def test_raises_when_not_installed(self):
e52aae0… lmata 59 from navegador.ingestion.rust import _get_rust_language
e52aae0… lmata 60 with patch.dict("sys.modules", {"tree_sitter_rust": None, "tree_sitter": None}):
e52aae0… lmata 61 with pytest.raises(ImportError, match="tree-sitter-rust"):
e52aae0… lmata 62 _get_rust_language()
e52aae0… lmata 63
e52aae0… lmata 64
e52aae0… lmata 65 class TestRustNodeText:
e52aae0… lmata 66 def test_extracts_bytes(self):
e52aae0… lmata 67 from navegador.ingestion.rust import _node_text
e52aae0… lmata 68 source = b"fn main() {}"
e52aae0… lmata 69 node = MockNode("identifier", start_byte=3, end_byte=7)
e52aae0… lmata 70 assert _node_text(node, source) == "main"
e52aae0… lmata 71
e52aae0… lmata 72
e52aae0… lmata 73 class TestRustDocComment:
e52aae0… lmata 74 def test_collects_triple_slash_comments(self):
e52aae0… lmata 75 from navegador.ingestion.rust import _doc_comment
e52aae0… lmata 76 source = b"/// Docs line 1\n/// Docs line 2\nfn foo() {}"
e52aae0… lmata 77 doc1 = MockNode("line_comment", start_byte=0, end_byte=15)
e52aae0… lmata 78 doc2 = MockNode("line_comment", start_byte=16, end_byte=31)
e52aae0… lmata 79 fn_node = MockNode("function_item", start_byte=32, end_byte=44)
e52aae0… lmata 80 _parent = MockNode("source_file", children=[doc1, doc2, fn_node])
e52aae0… lmata 81 result = _doc_comment(fn_node, source)
e52aae0… lmata 82 assert "Docs line 1" in result
e52aae0… lmata 83 assert "Docs line 2" in result
e52aae0… lmata 84
e52aae0… lmata 85 def test_ignores_non_doc_comments(self):
e52aae0… lmata 86 from navegador.ingestion.rust import _doc_comment
e52aae0… lmata 87 source = b"// regular comment\nfn foo() {}"
e52aae0… lmata 88 comment = MockNode("line_comment", start_byte=0, end_byte=18)
e52aae0… lmata 89 fn_node = MockNode("function_item", start_byte=19, end_byte=30)
e52aae0… lmata 90 MockNode("source_file", children=[comment, fn_node])
e52aae0… lmata 91 result = _doc_comment(fn_node, source)
e52aae0… lmata 92 assert result == ""
e52aae0… lmata 93
e52aae0… lmata 94 def test_no_parent(self):
e52aae0… lmata 95 from navegador.ingestion.rust import _doc_comment
e52aae0… lmata 96 fn_node = MockNode("function_item")
e52aae0… lmata 97 assert _doc_comment(fn_node, b"") == ""
e52aae0… lmata 98
e52aae0… lmata 99
e52aae0… lmata 100 class TestRustHandleFunction:
e52aae0… lmata 101 def test_creates_function_node(self):
e52aae0… lmata 102 parser = _make_parser()
e52aae0… lmata 103 store = _make_store()
e52aae0… lmata 104 source = b"fn foo() {}"
e52aae0… lmata 105 name = _text_node(b"foo")
e52aae0… lmata 106 node = MockNode("function_item", start_point=(0, 0), end_point=(0, 10))
e52aae0… lmata 107 node.set_field("name", name)
e52aae0… lmata 108 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 109 parser._handle_function(node, source, "lib.rs", store, stats, impl_type=None)
e52aae0… lmata 110 assert stats["functions"] == 1
e52aae0… lmata 111 label = store.create_node.call_args[0][0]
e52aae0… lmata 112 assert label == NodeLabel.Function
e52aae0… lmata 113
e52aae0… lmata 114 def test_creates_method_when_impl_type_given(self):
e52aae0… lmata 115 parser = _make_parser()
e52aae0… lmata 116 store = _make_store()
e52aae0… lmata 117 source = b"fn save(&self) {}"
e52aae0… lmata 118 name = _text_node(b"save")
e52aae0… lmata 119 node = MockNode("function_item", start_point=(0, 0), end_point=(0, 16))
e52aae0… lmata 120 node.set_field("name", name)
e52aae0… lmata 121 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 122 parser._handle_function(node, source, "lib.rs", store, stats, impl_type="Repo")
e52aae0… lmata 123 label = store.create_node.call_args[0][0]
e52aae0… lmata 124 assert label == NodeLabel.Method
e52aae0… lmata 125
e52aae0… lmata 126 def test_skips_if_no_name(self):
e52aae0… lmata 127 parser = _make_parser()
e52aae0… lmata 128 store = _make_store()
e52aae0… lmata 129 node = MockNode("function_item", start_point=(0, 0), end_point=(0, 0))
e52aae0… lmata 130 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 131 parser._handle_function(node, b"", "lib.rs", store, stats, impl_type=None)
e52aae0… lmata 132 assert stats["functions"] == 0
e52aae0… lmata 133
e52aae0… lmata 134
e52aae0… lmata 135 class TestRustHandleImpl:
e52aae0… lmata 136 def test_handles_impl_block(self):
e52aae0… lmata 137 parser = _make_parser()
e52aae0… lmata 138 store = _make_store()
e52aae0… lmata 139 source = b"impl Repo { fn save(&self) {} }"
e52aae0… lmata 140 type_node = _text_node(b"Repo", "type_identifier")
e52aae0… lmata 141 name_node = _text_node(b"save")
e52aae0… lmata 142 fn_item = MockNode("function_item", start_point=(0, 12), end_point=(0, 28))
e52aae0… lmata 143 fn_item.set_field("name", name_node)
e52aae0… lmata 144 body = MockNode("declaration_list", children=[fn_item])
e52aae0… lmata 145 impl = MockNode("impl_item", start_point=(0, 0), end_point=(0, 30))
e52aae0… lmata 146 impl.set_field("type", type_node)
e52aae0… lmata 147 impl.set_field("body", body)
e52aae0… lmata 148 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 149 parser._handle_impl(impl, source, "lib.rs", store, stats)
e52aae0… lmata 150 assert stats["functions"] == 1
e52aae0… lmata 151
e52aae0… lmata 152 def test_handles_impl_with_no_body(self):
e52aae0… lmata 153 parser = _make_parser()
e52aae0… lmata 154 store = _make_store()
e52aae0… lmata 155 impl = MockNode("impl_item")
e52aae0… lmata 156 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 157 parser._handle_impl(impl, b"", "lib.rs", store, stats)
e52aae0… lmata 158 assert stats["functions"] == 0
e52aae0… lmata 159
e52aae0… lmata 160
e52aae0… lmata 161 class TestRustHandleType:
e52aae0… lmata 162 def test_ingests_struct(self):
e52aae0… lmata 163 parser = _make_parser()
e52aae0… lmata 164 store = _make_store()
e52aae0… lmata 165 source = b"struct Foo {}"
e52aae0… lmata 166 name = _text_node(b"Foo", "type_identifier")
e52aae0… lmata 167 node = MockNode("struct_item", start_point=(0, 0), end_point=(0, 12))
e52aae0… lmata 168 node.set_field("name", name)
e52aae0… lmata 169 _parent = MockNode("source_file", children=[node])
e52aae0… lmata 170 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 171 parser._handle_type(node, source, "lib.rs", store, stats)
e52aae0… lmata 172 assert stats["classes"] == 1
e52aae0… lmata 173 props = store.create_node.call_args[0][1]
e52aae0… lmata 174 assert "struct" in props["docstring"]
e52aae0… lmata 175
e52aae0… lmata 176 def test_ingests_enum(self):
e52aae0… lmata 177 parser = _make_parser()
e52aae0… lmata 178 store = _make_store()
e52aae0… lmata 179 source = b"enum Color { Red, Green }"
e52aae0… lmata 180 name = _text_node(b"Color", "type_identifier")
e52aae0… lmata 181 node = MockNode("enum_item", start_point=(0, 0), end_point=(0, 24))
e52aae0… lmata 182 node.set_field("name", name)
e52aae0… lmata 183 MockNode("source_file", children=[node])
e52aae0… lmata 184 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 185 parser._handle_type(node, source, "lib.rs", store, stats)
e52aae0… lmata 186 assert stats["classes"] == 1
e52aae0… lmata 187
e52aae0… lmata 188 def test_ingests_trait(self):
e52aae0… lmata 189 parser = _make_parser()
e52aae0… lmata 190 store = _make_store()
e52aae0… lmata 191 source = b"trait Saveable {}"
e52aae0… lmata 192 name = _text_node(b"Saveable", "type_identifier")
e52aae0… lmata 193 node = MockNode("trait_item", start_point=(0, 0), end_point=(0, 16))
e52aae0… lmata 194 node.set_field("name", name)
e52aae0… lmata 195 MockNode("source_file", children=[node])
e52aae0… lmata 196 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 197 parser._handle_type(node, source, "lib.rs", store, stats)
e52aae0… lmata 198 assert stats["classes"] == 1
e52aae0… lmata 199
e52aae0… lmata 200 def test_skips_if_no_name(self):
e52aae0… lmata 201 parser = _make_parser()
e52aae0… lmata 202 store = _make_store()
e52aae0… lmata 203 node = MockNode("struct_item")
e52aae0… lmata 204 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 205 parser._handle_type(node, b"", "lib.rs", store, stats)
e52aae0… lmata 206 assert stats["classes"] == 0
e52aae0… lmata 207
e52aae0… lmata 208
e52aae0… lmata 209 class TestRustHandleUse:
e52aae0… lmata 210 def test_ingests_use_statement(self):
e52aae0… lmata 211 parser = _make_parser()
e52aae0… lmata 212 store = _make_store()
e52aae0… lmata 213 source = b"use std::collections::HashMap;"
e52aae0… lmata 214 node = MockNode("use_declaration", start_byte=0, end_byte=30,
e52aae0… lmata 215 start_point=(0, 0))
e52aae0… lmata 216 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 217 parser._handle_use(node, source, "lib.rs", store, stats)
e52aae0… lmata 218 assert stats["edges"] == 1
e52aae0… lmata 219 store.create_node.assert_called_once()
e52aae0… lmata 220 props = store.create_node.call_args[0][1]
e52aae0… lmata 221 assert "HashMap" in props["name"] or "std" in props["name"]
e52aae0… lmata 222
e52aae0… lmata 223
e52aae0… lmata 224 class TestRustExtractCalls:
e52aae0… lmata 225 def test_extracts_call(self):
e52aae0… lmata 226 parser = _make_parser()
e52aae0… lmata 227 store = _make_store()
2e96458… lmata 228 source = b"bar"
e52aae0… lmata 229 callee = _text_node(b"bar")
e52aae0… lmata 230 call_node = MockNode("call_expression")
e52aae0… lmata 231 call_node.set_field("function", callee)
e52aae0… lmata 232 body = MockNode("block", children=[call_node])
e52aae0… lmata 233 fn_node = MockNode("function_item")
e52aae0… lmata 234 fn_node.set_field("body", body)
e52aae0… lmata 235 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 236 parser._extract_calls(fn_node, source, "lib.rs", "foo",
e52aae0… lmata 237 NodeLabel.Function, store, stats)
e52aae0… lmata 238 assert stats["edges"] == 1
2e96458… lmata 239 edge_call = store.create_edge.call_args[0]
2e96458… lmata 240 assert edge_call[4]["name"] == "bar"
e52aae0… lmata 241
e52aae0… lmata 242 def test_handles_method_call_syntax(self):
e52aae0… lmata 243 parser = _make_parser()
e52aae0… lmata 244 store = _make_store()
e52aae0… lmata 245 source = b"Repo::save"
e52aae0… lmata 246 callee = _text_node(b"Repo::save")
e52aae0… lmata 247 call_node = MockNode("call_expression")
e52aae0… lmata 248 call_node.set_field("function", callee)
e52aae0… lmata 249 body = MockNode("block", children=[call_node])
e52aae0… lmata 250 fn_node = MockNode("function_item")
e52aae0… lmata 251 fn_node.set_field("body", body)
e52aae0… lmata 252 stats = {"functions": 0, "classes": 0, "edges": 0}
e52aae0… lmata 253 parser._extract_calls(fn_node, source, "lib.rs", "foo",
e52aae0… lmata 254 NodeLabel.Function, store, stats)
e52aae0… lmata 255 # "Repo::save" → callee = "save"
e52aae0… lmata 256 edge_call = store.create_edge.call_args[0]
e52aae0… lmata 257 assert edge_call[4]["name"] == "save"
7ae0080… lmata 258
7ae0080… lmata 259
7ae0080… lmata 260 # ── _get_rust_language happy path ─────────────────────────────────────────────
7ae0080… lmata 261
7ae0080… lmata 262 class TestRustGetLanguageHappyPath:
7ae0080… lmata 263 def test_returns_language_object(self):
7ae0080… lmata 264 from navegador.ingestion.rust import _get_rust_language
7ae0080… lmata 265 mock_tsrust = MagicMock()
7ae0080… lmata 266 mock_ts = MagicMock()
7ae0080… lmata 267 with patch.dict("sys.modules", {
7ae0080… lmata 268 "tree_sitter_rust": mock_tsrust,
7ae0080… lmata 269 "tree_sitter": mock_ts,
7ae0080… lmata 270 }):
7ae0080… lmata 271 result = _get_rust_language()
7ae0080… lmata 272 assert result is mock_ts.Language.return_value
7ae0080… lmata 273
7ae0080… lmata 274
7ae0080… lmata 275 # ── RustParser init and parse_file ───────────────────────────────────────────
7ae0080… lmata 276
7ae0080… lmata 277 class TestRustParserInit:
7ae0080… lmata 278 def test_init_creates_parser(self):
7ae0080… lmata 279 mock_tsrust = MagicMock()
7ae0080… lmata 280 mock_ts = MagicMock()
7ae0080… lmata 281 with patch.dict("sys.modules", {
7ae0080… lmata 282 "tree_sitter_rust": mock_tsrust,
7ae0080… lmata 283 "tree_sitter": mock_ts,
7ae0080… lmata 284 }):
7ae0080… lmata 285 from navegador.ingestion.rust import RustParser
7ae0080… lmata 286 parser = RustParser()
7ae0080… lmata 287 assert parser._parser is mock_ts.Parser.return_value
7ae0080… lmata 288
7ae0080… lmata 289 def test_parse_file_creates_file_node(self):
7ae0080… lmata 290 import tempfile
7ae0080… lmata 291 from pathlib import Path
7ae0080… lmata 292
7ae0080… lmata 293 from navegador.graph.schema import NodeLabel
7ae0080… lmata 294 parser = _make_parser()
7ae0080… lmata 295 store = _make_store()
7ae0080… lmata 296 mock_tree = MagicMock()
7ae0080… lmata 297 mock_tree.root_node.type = "source_file"
7ae0080… lmata 298 mock_tree.root_node.children = []
7ae0080… lmata 299 parser._parser.parse.return_value = mock_tree
7ae0080… lmata 300 with tempfile.NamedTemporaryFile(suffix=".rs", delete=False) as f:
7ae0080… lmata 301 f.write(b"fn main() {}\n")
7ae0080… lmata 302 fpath = Path(f.name)
7ae0080… lmata 303 try:
7ae0080… lmata 304 parser.parse_file(fpath, fpath.parent, store)
7ae0080… lmata 305 store.create_node.assert_called_once()
7ae0080… lmata 306 assert store.create_node.call_args[0][0] == NodeLabel.File
7ae0080… lmata 307 assert store.create_node.call_args[0][1]["language"] == "rust"
7ae0080… lmata 308 finally:
7ae0080… lmata 309 fpath.unlink()
7ae0080… lmata 310
7ae0080… lmata 311
7ae0080… lmata 312 # ── _walk dispatch ────────────────────────────────────────────────────────────
7ae0080… lmata 313
7ae0080… lmata 314 class TestRustWalkDispatch:
7ae0080… lmata 315 def test_walk_handles_function_item(self):
7ae0080… lmata 316 parser = _make_parser()
7ae0080… lmata 317 store = _make_store()
7ae0080… lmata 318 source = b"foo"
7ae0080… lmata 319 name = _text_node(b"foo")
7ae0080… lmata 320 fn_node = MockNode("function_item", start_point=(0, 0), end_point=(0, 10))
7ae0080… lmata 321 fn_node.set_field("name", name)
7ae0080… lmata 322 root = MockNode("source_file", children=[fn_node])
7ae0080… lmata 323 stats = {"functions": 0, "classes": 0, "edges": 0}
7ae0080… lmata 324 parser._walk(root, source, "lib.rs", store, stats, impl_type=None)
7ae0080… lmata 325 assert stats["functions"] == 1
7ae0080… lmata 326
7ae0080… lmata 327 def test_walk_handles_impl_item(self):
7ae0080… lmata 328 parser = _make_parser()
7ae0080… lmata 329 store = _make_store()
7ae0080… lmata 330 source = b"MyStruct"
7ae0080… lmata 331 type_node = MockNode("type_identifier", start_byte=0, end_byte=8)
7ae0080… lmata 332 body = MockNode("declaration_list", children=[])
7ae0080… lmata 333 impl_node = MockNode("impl_item")
7ae0080… lmata 334 impl_node.set_field("type", type_node)
7ae0080… lmata 335 impl_node.set_field("body", body)
7ae0080… lmata 336 root = MockNode("source_file", children=[impl_node])
7ae0080… lmata 337 stats = {"functions": 0, "classes": 0, "edges": 0}
7ae0080… lmata 338 parser._walk(root, source, "lib.rs", store, stats, impl_type=None)
7ae0080… lmata 339 # No functions in body, just verifies dispatch doesn't crash
7ae0080… lmata 340 assert stats["functions"] == 0
7ae0080… lmata 341
7ae0080… lmata 342 def test_walk_handles_struct_item(self):
7ae0080… lmata 343 parser = _make_parser()
7ae0080… lmata 344 store = _make_store()
7ae0080… lmata 345 source = b"Foo"
7ae0080… lmata 346 name = _text_node(b"Foo", "type_identifier")
7ae0080… lmata 347 node = MockNode("struct_item", start_point=(0, 0), end_point=(0, 10))
7ae0080… lmata 348 node.set_field("name", name)
7ae0080… lmata 349 _parent = MockNode("source_file", children=[node])
7ae0080… lmata 350 root = MockNode("source_file", children=[node])
7ae0080… lmata 351 stats = {"functions": 0, "classes": 0, "edges": 0}
7ae0080… lmata 352 parser._walk(root, source, "lib.rs", store, stats, impl_type=None)
7ae0080… lmata 353 assert stats["classes"] == 1
7ae0080… lmata 354
7ae0080… lmata 355 def test_walk_handles_use_declaration(self):
7ae0080… lmata 356 parser = _make_parser()
7ae0080… lmata 357 store = _make_store()
7ae0080… lmata 358 source = b"use std::io;"
7ae0080… lmata 359 use_node = MockNode("use_declaration", start_byte=0, end_byte=12)
7ae0080… lmata 360 root = MockNode("source_file", children=[use_node])
7ae0080… lmata 361 stats = {"functions": 0, "classes": 0, "edges": 0}
7ae0080… lmata 362 parser._walk(root, source, "lib.rs", store, stats, impl_type=None)
7ae0080… lmata 363 assert stats["edges"] == 1
7ae0080… lmata 364
7ae0080… lmata 365 def test_walk_recurses_into_unknown_nodes(self):
7ae0080… lmata 366 parser = _make_parser()
7ae0080… lmata 367 store = _make_store()
7ae0080… lmata 368 source = b"foo"
7ae0080… lmata 369 name = _text_node(b"foo")
7ae0080… lmata 370 fn_node = MockNode("function_item", start_point=(0, 0), end_point=(0, 10))
7ae0080… lmata 371 fn_node.set_field("name", name)
7ae0080… lmata 372 wrapper = MockNode("mod_item", children=[fn_node])
7ae0080… lmata 373 root = MockNode("source_file", children=[wrapper])
7ae0080… lmata 374 stats = {"functions": 0, "classes": 0, "edges": 0}
7ae0080… lmata 375 parser._walk(root, source, "lib.rs", store, stats, impl_type=None)
7ae0080… lmata 376 assert stats["functions"] == 1

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button