Navegador

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

Keyboard Shortcuts

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