Navegador

navegador / tests / test_go_parser.py
Blame History Raw 418 lines
1
"""Tests for navegador.ingestion.go — GoParser 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.go import GoParser
52
parser = GoParser.__new__(GoParser)
53
parser._parser = MagicMock()
54
return parser
55
56
57
class TestGoGetLanguage:
58
def test_raises_when_not_installed(self):
59
from navegador.ingestion.go import _get_go_language
60
with patch.dict("sys.modules", {"tree_sitter_go": None, "tree_sitter": None}):
61
with pytest.raises(ImportError, match="tree-sitter-go"):
62
_get_go_language()
63
64
65
class TestGoNodeText:
66
def test_extracts_bytes(self):
67
from navegador.ingestion.go import _node_text
68
source = b"hello world"
69
node = MockNode("identifier", start_byte=6, end_byte=11)
70
assert _node_text(node, source) == "world"
71
72
73
class TestGoHandleFunction:
74
def test_creates_function_node(self):
75
parser = _make_parser()
76
store = _make_store()
77
source = b"func Foo() {}"
78
name = _text_node(b"Foo")
79
node = MockNode("function_declaration", start_point=(0, 0), end_point=(0, 12))
80
node.set_field("name", name)
81
stats = {"functions": 0, "classes": 0, "edges": 0}
82
parser._handle_function(node, source, "main.go", store, stats, receiver=None)
83
assert stats["functions"] == 1
84
store.create_node.assert_called_once()
85
label = store.create_node.call_args[0][0]
86
assert label == NodeLabel.Function
87
88
def test_creates_method_when_receiver_given(self):
89
parser = _make_parser()
90
store = _make_store()
91
source = b"func (r *Repo) Save() {}"
92
name = _text_node(b"Save")
93
node = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 23))
94
node.set_field("name", name)
95
stats = {"functions": 0, "classes": 0, "edges": 0}
96
parser._handle_function(node, source, "repo.go", store, stats, receiver="Repo")
97
label = store.create_node.call_args[0][0]
98
assert label == NodeLabel.Method
99
100
def test_skips_if_no_name_node(self):
101
parser = _make_parser()
102
store = _make_store()
103
node = MockNode("function_declaration", start_point=(0, 0), end_point=(0, 0))
104
stats = {"functions": 0, "classes": 0, "edges": 0}
105
parser._handle_function(node, b"", "main.go", store, stats, receiver=None)
106
assert stats["functions"] == 0
107
108
109
class TestGoHandleType:
110
def test_ingests_struct(self):
111
parser = _make_parser()
112
store = _make_store()
113
source = b"type User struct {}"
114
name_node = _text_node(b"User", "type_identifier")
115
type_node = MockNode("struct_type")
116
spec = MockNode("type_spec")
117
spec.set_field("name", name_node)
118
spec.set_field("type", type_node)
119
decl = MockNode("type_declaration", children=[spec],
120
start_point=(0, 0), end_point=(0, 18))
121
stats = {"functions": 0, "classes": 0, "edges": 0}
122
parser._handle_type(decl, source, "main.go", store, stats)
123
assert stats["classes"] == 1
124
assert stats["edges"] == 1
125
126
def test_ingests_interface(self):
127
parser = _make_parser()
128
store = _make_store()
129
source = b"type Reader interface {}"
130
name_node = _text_node(b"Reader", "type_identifier")
131
type_node = MockNode("interface_type")
132
spec = MockNode("type_spec")
133
spec.set_field("name", name_node)
134
spec.set_field("type", type_node)
135
decl = MockNode("type_declaration", children=[spec],
136
start_point=(0, 0), end_point=(0, 23))
137
stats = {"functions": 0, "classes": 0, "edges": 0}
138
parser._handle_type(decl, source, "main.go", store, stats)
139
assert stats["classes"] == 1
140
141
def test_skips_non_struct_interface(self):
142
parser = _make_parser()
143
store = _make_store()
144
source = b"type MyInt int"
145
name_node = _text_node(b"MyInt", "type_identifier")
146
type_node = MockNode("int")
147
spec = MockNode("type_spec")
148
spec.set_field("name", name_node)
149
spec.set_field("type", type_node)
150
decl = MockNode("type_declaration", children=[spec],
151
start_point=(0, 0), end_point=(0, 13))
152
stats = {"functions": 0, "classes": 0, "edges": 0}
153
parser._handle_type(decl, source, "main.go", store, stats)
154
assert stats["classes"] == 0
155
156
157
class TestGoImportSpec:
158
def test_ingests_single_import(self):
159
parser = _make_parser()
160
store = _make_store()
161
source = b'"fmt"'
162
path_node = MockNode("interpreted_string_literal", start_byte=0, end_byte=5)
163
spec = MockNode("import_spec")
164
spec.set_field("path", path_node)
165
stats = {"functions": 0, "classes": 0, "edges": 0}
166
parser._ingest_import_spec(spec, source, "main.go", 1, store, stats)
167
assert stats["edges"] == 1
168
store.create_node.assert_called_once()
169
170
def test_skips_spec_without_path(self):
171
parser = _make_parser()
172
store = _make_store()
173
spec = MockNode("import_spec")
174
stats = {"functions": 0, "classes": 0, "edges": 0}
175
parser._ingest_import_spec(spec, b"", "main.go", 1, store, stats)
176
assert stats["edges"] == 0
177
178
179
class TestGoExtractCalls:
180
def test_extracts_call(self):
181
parser = _make_parser()
182
store = _make_store()
183
source = b"bar"
184
callee = _text_node(b"bar")
185
call_node = MockNode("call_expression")
186
call_node.set_field("function", callee)
187
body = MockNode("block", children=[call_node])
188
fn_node = MockNode("function_declaration")
189
fn_node.set_field("body", body)
190
stats = {"functions": 0, "classes": 0, "edges": 0}
191
parser._extract_calls(fn_node, source, "main.go", "foo",
192
NodeLabel.Function, store, stats)
193
assert stats["edges"] == 1
194
edge_call = store.create_edge.call_args[0]
195
assert edge_call[4]["name"] == "bar"
196
197
def test_no_calls_in_empty_body(self):
198
parser = _make_parser()
199
store = _make_store()
200
fn_node = MockNode("function_declaration")
201
fn_node.set_field("body", MockNode("block"))
202
stats = {"functions": 0, "classes": 0, "edges": 0}
203
parser._extract_calls(fn_node, b"", "main.go", "foo",
204
NodeLabel.Function, store, stats)
205
assert stats["edges"] == 0
206
207
208
class TestGoWalkDispatch:
209
def test_walk_handles_function(self):
210
parser = _make_parser()
211
store = _make_store()
212
source = b"func foo() {}"
213
name = _text_node(b"foo")
214
fn = MockNode("function_declaration", start_point=(0, 0), end_point=(0, 12))
215
fn.set_field("name", name)
216
root = MockNode("source_file", children=[fn])
217
stats = {"functions": 0, "classes": 0, "edges": 0}
218
parser._walk(root, source, "main.go", store, stats)
219
assert stats["functions"] == 1
220
221
def test_walk_handles_type(self):
222
parser = _make_parser()
223
store = _make_store()
224
source = b"type Foo struct {}"
225
name_node = _text_node(b"Foo", "type_identifier")
226
type_node = MockNode("struct_type")
227
spec = MockNode("type_spec")
228
spec.set_field("name", name_node)
229
spec.set_field("type", type_node)
230
decl = MockNode("type_declaration", children=[spec],
231
start_point=(0, 0), end_point=(0, 17))
232
root = MockNode("source_file", children=[decl])
233
stats = {"functions": 0, "classes": 0, "edges": 0}
234
parser._walk(root, source, "main.go", store, stats)
235
assert stats["classes"] == 1
236
237
238
# ── _get_go_language happy path ───────────────────────────────────────────────
239
240
class TestGoGetLanguageHappyPath:
241
def test_returns_language_object(self):
242
from navegador.ingestion.go import _get_go_language
243
mock_tsgo = MagicMock()
244
mock_ts = MagicMock()
245
with patch.dict("sys.modules", {
246
"tree_sitter_go": mock_tsgo,
247
"tree_sitter": mock_ts,
248
}):
249
result = _get_go_language()
250
assert result is mock_ts.Language.return_value
251
252
253
# ── GoParser init and parse_file ─────────────────────────────────────────────
254
255
class TestGoParserInit:
256
def test_init_creates_parser(self):
257
mock_tsgo = MagicMock()
258
mock_ts = MagicMock()
259
with patch.dict("sys.modules", {
260
"tree_sitter_go": mock_tsgo,
261
"tree_sitter": mock_ts,
262
}):
263
from navegador.ingestion.go import GoParser
264
parser = GoParser()
265
assert parser._parser is mock_ts.Parser.return_value
266
267
def test_parse_file_creates_file_node(self):
268
import tempfile
269
from pathlib import Path
270
271
from navegador.graph.schema import NodeLabel
272
parser = _make_parser()
273
store = _make_store()
274
mock_tree = MagicMock()
275
mock_tree.root_node.type = "source_file"
276
mock_tree.root_node.children = []
277
parser._parser.parse.return_value = mock_tree
278
with tempfile.NamedTemporaryFile(suffix=".go", delete=False) as f:
279
f.write(b"package main\n")
280
fpath = Path(f.name)
281
try:
282
parser.parse_file(fpath, fpath.parent, store)
283
store.create_node.assert_called_once()
284
assert store.create_node.call_args[0][0] == NodeLabel.File
285
assert store.create_node.call_args[0][1]["language"] == "go"
286
finally:
287
fpath.unlink()
288
289
290
# ── _handle_method ────────────────────────────────────────────────────────────
291
292
class TestGoHandleMethod:
293
def test_extracts_value_receiver(self):
294
parser = _make_parser()
295
store = _make_store()
296
source = b"Repo"
297
type_id = MockNode("type_identifier", start_byte=0, end_byte=4)
298
param_decl = MockNode("parameter_declaration", children=[type_id])
299
recv_list = MockNode("parameter_list", children=[param_decl])
300
name = _text_node(b"Save")
301
node = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 30))
302
node.set_field("receiver", recv_list)
303
node.set_field("name", name)
304
stats = {"functions": 0, "classes": 0, "edges": 0}
305
parser._handle_method(node, source, "main.go", store, stats)
306
assert stats["functions"] == 1
307
label = store.create_node.call_args[0][0]
308
from navegador.graph.schema import NodeLabel
309
assert label == NodeLabel.Method
310
311
def test_extracts_pointer_receiver(self):
312
parser = _make_parser()
313
store = _make_store()
314
source = b"*Repo"
315
ptr_type = MockNode("pointer_type", start_byte=0, end_byte=5)
316
param_decl = MockNode("parameter_declaration", children=[ptr_type])
317
recv_list = MockNode("parameter_list", children=[param_decl])
318
name = _text_node(b"Delete")
319
node = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 30))
320
node.set_field("receiver", recv_list)
321
node.set_field("name", name)
322
stats = {"functions": 0, "classes": 0, "edges": 0}
323
parser._handle_method(node, source, "main.go", store, stats)
324
# pointer receiver "*Repo" → lstrip("*") → "Repo"
325
assert stats["functions"] == 1
326
327
def test_no_receiver_field(self):
328
parser = _make_parser()
329
store = _make_store()
330
source = b"foo"
331
name = _text_node(b"foo")
332
node = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 10))
333
node.set_field("name", name)
334
stats = {"functions": 0, "classes": 0, "edges": 0}
335
parser._handle_method(node, source, "main.go", store, stats)
336
# No receiver → treated as plain function
337
assert stats["functions"] == 1
338
339
340
# ── _handle_import with import_spec_list ─────────────────────────────────────
341
342
class TestGoHandleImportSpecList:
343
def test_handles_grouped_imports(self):
344
parser = _make_parser()
345
store = _make_store()
346
source = b'"fmt"'
347
path_node = MockNode("interpreted_string_literal", start_byte=0, end_byte=5)
348
spec1 = MockNode("import_spec")
349
spec1.set_field("path", path_node)
350
spec_list = MockNode("import_spec_list", children=[spec1])
351
import_node = MockNode("import_declaration",
352
children=[spec_list],
353
start_point=(0, 0))
354
stats = {"functions": 0, "classes": 0, "edges": 0}
355
parser._handle_import(import_node, source, "main.go", store, stats)
356
assert stats["edges"] == 1
357
358
359
# ── _walk dispatch additions ──────────────────────────────────────────────────
360
361
class TestGoWalkDispatchAdditional:
362
def test_walk_handles_method_declaration(self):
363
parser = _make_parser()
364
store = _make_store()
365
source = b"Repo"
366
type_id = MockNode("type_identifier", start_byte=0, end_byte=4)
367
param_decl = MockNode("parameter_declaration", children=[type_id])
368
recv_list = MockNode("parameter_list", children=[param_decl])
369
name = _text_node(b"Save")
370
method = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 30))
371
method.set_field("receiver", recv_list)
372
method.set_field("name", name)
373
root = MockNode("source_file", children=[method])
374
stats = {"functions": 0, "classes": 0, "edges": 0}
375
parser._walk(root, source, "main.go", store, stats)
376
assert stats["functions"] == 1
377
378
def test_walk_handles_import_declaration(self):
379
parser = _make_parser()
380
store = _make_store()
381
source = b'"fmt"'
382
path_node = MockNode("interpreted_string_literal", start_byte=0, end_byte=5)
383
spec = MockNode("import_spec")
384
spec.set_field("path", path_node)
385
import_node = MockNode("import_declaration",
386
children=[spec],
387
start_point=(0, 0))
388
root = MockNode("source_file", children=[import_node])
389
stats = {"functions": 0, "classes": 0, "edges": 0}
390
parser._walk(root, source, "main.go", store, stats)
391
assert stats["edges"] == 1
392
393
394
class TestGoHandleTypeContinueBranches:
395
def test_skips_non_type_spec_children(self):
396
parser = _make_parser()
397
store = _make_store()
398
source = b""
399
# Child that is not type_spec
400
comment_child = MockNode("comment")
401
decl = MockNode("type_declaration", children=[comment_child],
402
start_point=(0, 0), end_point=(0, 10))
403
stats = {"functions": 0, "classes": 0, "edges": 0}
404
parser._handle_type(decl, source, "main.go", store, stats)
405
assert stats["classes"] == 0
406
407
def test_skips_type_spec_without_name_or_type(self):
408
parser = _make_parser()
409
store = _make_store()
410
source = b""
411
# type_spec with no fields set
412
spec = MockNode("type_spec")
413
decl = MockNode("type_declaration", children=[spec],
414
start_point=(0, 0), end_point=(0, 10))
415
stats = {"functions": 0, "classes": 0, "edges": 0}
416
parser._handle_type(decl, source, "main.go", store, stats)
417
assert stats["classes"] == 0
418

Keyboard Shortcuts

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