Navegador

navegador / tests / test_java_parser.py
Blame History Raw 411 lines
1
"""Tests for navegador.ingestion.java — JavaParser 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.children = children or []
21
self.start_byte = start_byte
22
self.end_byte = end_byte
23
self.start_point = start_point
24
self.end_point = end_point
25
self.parent = parent
26
self._fields: dict = {}
27
for child in self.children:
28
child.parent = self
29
30
def child_by_field_name(self, name: str):
31
return self._fields.get(name)
32
33
def set_field(self, name: str, node):
34
self._fields[name] = node
35
node.parent = self
36
return self
37
38
39
def _text_node(text: bytes, type_: str = "identifier") -> MockNode:
40
return MockNode(type_, text, start_byte=0, end_byte=len(text))
41
42
43
def _make_store():
44
store = MagicMock()
45
store.query.return_value = MagicMock(result_set=[])
46
return store
47
48
49
def _make_parser():
50
from navegador.ingestion.java import JavaParser
51
parser = JavaParser.__new__(JavaParser)
52
parser._parser = MagicMock()
53
return parser
54
55
56
class TestJavaGetLanguage:
57
def test_raises_when_not_installed(self):
58
from navegador.ingestion.java import _get_java_language
59
with patch.dict("sys.modules", {"tree_sitter_java": None, "tree_sitter": None}):
60
with pytest.raises(ImportError, match="tree-sitter-java"):
61
_get_java_language()
62
63
64
class TestJavaNodeText:
65
def test_extracts_bytes(self):
66
from navegador.ingestion.java import _node_text
67
source = b"class Foo {}"
68
node = MockNode("identifier", start_byte=6, end_byte=9)
69
assert _node_text(node, source) == "Foo"
70
71
72
class TestJavadoc:
73
def test_extracts_javadoc(self):
74
from navegador.ingestion.java import _javadoc
75
source = b"/** My class */\nclass Foo {}"
76
comment = MockNode("block_comment", start_byte=0, end_byte=15)
77
cls_node = MockNode("class_declaration", start_byte=16, end_byte=28)
78
_parent = MockNode("program", children=[comment, cls_node])
79
result = _javadoc(cls_node, source)
80
assert "My class" in result
81
82
def test_ignores_regular_block_comment(self):
83
from navegador.ingestion.java import _javadoc
84
source = b"/* regular */\nclass Foo {}"
85
comment = MockNode("block_comment", start_byte=0, end_byte=13)
86
cls_node = MockNode("class_declaration", start_byte=14, end_byte=26)
87
MockNode("program", children=[comment, cls_node])
88
result = _javadoc(cls_node, source)
89
assert result == ""
90
91
92
class TestJavaHandleClass:
93
def test_creates_class_node(self):
94
parser = _make_parser()
95
store = _make_store()
96
source = b"class Foo {}"
97
name_node = _text_node(b"Foo")
98
body = MockNode("class_body")
99
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 11))
100
node.set_field("name", name_node)
101
node.set_field("body", body)
102
_parent = MockNode("program", children=[node])
103
stats = {"functions": 0, "classes": 0, "edges": 0}
104
parser._handle_class(node, source, "Foo.java", store, stats)
105
assert stats["classes"] == 1
106
label = store.create_node.call_args[0][0]
107
assert label == NodeLabel.Class
108
109
def test_creates_inherits_edge(self):
110
parser = _make_parser()
111
store = _make_store()
112
source = b"class Child extends Parent {}"
113
name_node = _text_node(b"Child")
114
parent_id = _text_node(b"Parent", "type_identifier")
115
superclass = MockNode("superclass", children=[parent_id])
116
body = MockNode("class_body")
117
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 28))
118
node.set_field("name", name_node)
119
node.set_field("superclass", superclass)
120
node.set_field("body", body)
121
MockNode("program", children=[node])
122
stats = {"functions": 0, "classes": 0, "edges": 0}
123
parser._handle_class(node, source, "Child.java", store, stats)
124
# Should have CONTAINS edge + INHERITS edge
125
assert stats["edges"] == 2
126
127
def test_ingests_methods_in_body(self):
128
parser = _make_parser()
129
store = _make_store()
130
source = b"class Foo { void bar() {} }"
131
name_node = _text_node(b"Foo")
132
method_name = _text_node(b"bar")
133
method_body = MockNode("block")
134
method = MockNode("method_declaration", start_point=(1, 2), end_point=(1, 14))
135
method.set_field("name", method_name)
136
method.set_field("body", method_body)
137
body = MockNode("class_body", children=[method])
138
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 26))
139
node.set_field("name", name_node)
140
node.set_field("body", body)
141
MockNode("program", children=[node])
142
stats = {"functions": 0, "classes": 0, "edges": 0}
143
parser._handle_class(node, source, "Foo.java", store, stats)
144
assert stats["functions"] == 1
145
146
def test_skips_if_no_name(self):
147
parser = _make_parser()
148
store = _make_store()
149
node = MockNode("class_declaration")
150
stats = {"functions": 0, "classes": 0, "edges": 0}
151
parser._handle_class(node, b"", "X.java", store, stats)
152
assert stats["classes"] == 0
153
154
155
class TestJavaHandleInterface:
156
def test_creates_interface_node(self):
157
parser = _make_parser()
158
store = _make_store()
159
source = b"interface Saveable {}"
160
name_node = _text_node(b"Saveable")
161
body = MockNode("interface_body")
162
node = MockNode("interface_declaration", start_point=(0, 0), end_point=(0, 20))
163
node.set_field("name", name_node)
164
node.set_field("body", body)
165
MockNode("program", children=[node])
166
stats = {"functions": 0, "classes": 0, "edges": 0}
167
parser._handle_interface(node, source, "Saveable.java", store, stats)
168
assert stats["classes"] == 1
169
props = store.create_node.call_args[0][1]
170
assert "interface" in props.get("docstring", "")
171
172
def test_skips_if_no_name(self):
173
parser = _make_parser()
174
store = _make_store()
175
node = MockNode("interface_declaration")
176
stats = {"functions": 0, "classes": 0, "edges": 0}
177
parser._handle_interface(node, b"", "X.java", store, stats)
178
assert stats["classes"] == 0
179
180
181
class TestJavaHandleMethod:
182
def test_creates_method_node(self):
183
parser = _make_parser()
184
store = _make_store()
185
source = b"void save() {}"
186
name_node = _text_node(b"save")
187
body = MockNode("block")
188
node = MockNode("method_declaration", start_point=(0, 0), end_point=(0, 13))
189
node.set_field("name", name_node)
190
node.set_field("body", body)
191
MockNode("class_body", children=[node])
192
stats = {"functions": 0, "classes": 0, "edges": 0}
193
parser._handle_method(node, source, "Foo.java", store, stats, class_name="Foo")
194
assert stats["functions"] == 1
195
label = store.create_node.call_args[0][0]
196
assert label == NodeLabel.Method
197
198
def test_skips_if_no_name(self):
199
parser = _make_parser()
200
store = _make_store()
201
node = MockNode("method_declaration")
202
stats = {"functions": 0, "classes": 0, "edges": 0}
203
parser._handle_method(node, b"", "X.java", store, stats, class_name="X")
204
assert stats["functions"] == 0
205
206
207
class TestJavaHandleImport:
208
def test_ingests_import(self):
209
parser = _make_parser()
210
store = _make_store()
211
source = b"import java.util.List;"
212
node = MockNode("import_declaration", start_byte=0, end_byte=22,
213
start_point=(0, 0))
214
stats = {"functions": 0, "classes": 0, "edges": 0}
215
parser._handle_import(node, source, "Foo.java", store, stats)
216
assert stats["edges"] == 1
217
props = store.create_node.call_args[0][1]
218
assert "java.util.List" in props["name"]
219
220
def test_handles_static_import(self):
221
parser = _make_parser()
222
store = _make_store()
223
source = b"import static java.util.Collections.sort;"
224
node = MockNode("import_declaration", start_byte=0, end_byte=41,
225
start_point=(0, 0))
226
stats = {"functions": 0, "classes": 0, "edges": 0}
227
parser._handle_import(node, source, "Foo.java", store, stats)
228
assert stats["edges"] == 1
229
230
231
class TestJavaExtractCalls:
232
def test_extracts_method_invocation(self):
233
parser = _make_parser()
234
store = _make_store()
235
source = b"bar"
236
callee = _text_node(b"bar")
237
invocation = MockNode("method_invocation")
238
invocation.set_field("name", callee)
239
body = MockNode("block", children=[invocation])
240
node = MockNode("method_declaration")
241
node.set_field("body", body)
242
stats = {"functions": 0, "classes": 0, "edges": 0}
243
parser._extract_calls(node, source, "Foo.java", "foo", store, stats)
244
assert stats["edges"] == 1
245
edge_call = store.create_edge.call_args[0]
246
assert edge_call[4]["name"] == "bar"
247
248
def test_no_calls_in_empty_body(self):
249
parser = _make_parser()
250
store = _make_store()
251
node = MockNode("method_declaration")
252
node.set_field("body", MockNode("block"))
253
stats = {"functions": 0, "classes": 0, "edges": 0}
254
parser._extract_calls(node, b"", "X.java", "foo", store, stats)
255
assert stats["edges"] == 0
256
257
258
# ── _get_java_language happy path ─────────────────────────────────────────────
259
260
class TestJavaGetLanguageHappyPath:
261
def test_returns_language_object(self):
262
from navegador.ingestion.java import _get_java_language
263
mock_tsjava = MagicMock()
264
mock_ts = MagicMock()
265
with patch.dict("sys.modules", {
266
"tree_sitter_java": mock_tsjava,
267
"tree_sitter": mock_ts,
268
}):
269
result = _get_java_language()
270
assert result is mock_ts.Language.return_value
271
272
273
# ── JavaParser init and parse_file ───────────────────────────────────────────
274
275
class TestJavaParserInit:
276
def test_init_creates_parser(self):
277
mock_tsjava = MagicMock()
278
mock_ts = MagicMock()
279
with patch.dict("sys.modules", {
280
"tree_sitter_java": mock_tsjava,
281
"tree_sitter": mock_ts,
282
}):
283
from navegador.ingestion.java import JavaParser
284
parser = JavaParser()
285
assert parser._parser is mock_ts.Parser.return_value
286
287
def test_parse_file_creates_file_node(self):
288
import tempfile
289
from pathlib import Path
290
291
from navegador.graph.schema import NodeLabel
292
parser = _make_parser()
293
store = _make_store()
294
mock_tree = MagicMock()
295
mock_tree.root_node.type = "program"
296
mock_tree.root_node.children = []
297
parser._parser.parse.return_value = mock_tree
298
with tempfile.NamedTemporaryFile(suffix=".java", delete=False) as f:
299
f.write(b"class Foo {}\n")
300
fpath = Path(f.name)
301
try:
302
parser.parse_file(fpath, fpath.parent, store)
303
store.create_node.assert_called_once()
304
assert store.create_node.call_args[0][0] == NodeLabel.File
305
assert store.create_node.call_args[0][1]["language"] == "java"
306
finally:
307
fpath.unlink()
308
309
310
# ── _walk dispatch ────────────────────────────────────────────────────────────
311
312
class TestJavaWalkDispatch:
313
def test_walk_handles_class_declaration(self):
314
parser = _make_parser()
315
store = _make_store()
316
source = b"Foo"
317
name = _text_node(b"Foo")
318
body = MockNode("class_body", children=[])
319
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 10))
320
node.set_field("name", name)
321
node.set_field("body", body)
322
root = MockNode("program", children=[node])
323
stats = {"functions": 0, "classes": 0, "edges": 0}
324
parser._walk(root, source, "Foo.java", store, stats, class_name=None)
325
assert stats["classes"] == 1
326
327
def test_walk_handles_interface_declaration(self):
328
parser = _make_parser()
329
store = _make_store()
330
source = b"Readable"
331
name = _text_node(b"Readable")
332
body = MockNode("interface_body", children=[])
333
node = MockNode("interface_declaration", start_point=(0, 0), end_point=(0, 20))
334
node.set_field("name", name)
335
node.set_field("body", body)
336
root = MockNode("program", children=[node])
337
stats = {"functions": 0, "classes": 0, "edges": 0}
338
parser._walk(root, source, "R.java", store, stats, class_name=None)
339
assert stats["classes"] == 1
340
341
def test_walk_handles_import_declaration(self):
342
parser = _make_parser()
343
store = _make_store()
344
source = b"import java.util.List;"
345
node = MockNode("import_declaration", start_byte=0, end_byte=22,
346
start_point=(0, 0))
347
root = MockNode("program", children=[node])
348
stats = {"functions": 0, "classes": 0, "edges": 0}
349
parser._walk(root, source, "Foo.java", store, stats, class_name=None)
350
assert stats["edges"] == 1
351
352
def test_walk_recurses_into_children(self):
353
parser = _make_parser()
354
store = _make_store()
355
source = b"import java.util.List;"
356
import_node = MockNode("import_declaration", start_byte=0, end_byte=22,
357
start_point=(0, 0))
358
wrapper = MockNode("block", children=[import_node])
359
root = MockNode("program", children=[wrapper])
360
stats = {"functions": 0, "classes": 0, "edges": 0}
361
parser._walk(root, source, "Foo.java", store, stats, class_name=None)
362
assert stats["edges"] == 1
363
364
365
# ── _handle_class nested inner class ─────────────────────────────────────────
366
367
class TestJavaHandleClassNested:
368
def test_ingests_nested_inner_class(self):
369
parser = _make_parser()
370
store = _make_store()
371
source = b"Inner"
372
outer_name = _text_node(b"Outer")
373
inner_name = _text_node(b"Inner")
374
inner_class = MockNode("class_declaration",
375
start_point=(1, 4), end_point=(3, 4))
376
inner_class.set_field("name", inner_name)
377
body = MockNode("class_body", children=[inner_class])
378
outer_class = MockNode("class_declaration",
379
start_point=(0, 0), end_point=(4, 0))
380
outer_class.set_field("name", outer_name)
381
outer_class.set_field("body", body)
382
stats = {"functions": 0, "classes": 0, "edges": 0}
383
parser._handle_class(outer_class, source, "Outer.java", store, stats)
384
# outer + inner class both registered
385
assert stats["classes"] == 2
386
assert stats["edges"] == 2 # CONTAINS(File→Outer) + CONTAINS(Outer→Inner)
387
388
389
# ── _handle_interface with method body ───────────────────────────────────────
390
391
class TestJavaHandleInterfaceWithMethods:
392
def test_walks_methods_in_interface_body(self):
393
parser = _make_parser()
394
store = _make_store()
395
source = b"read"
396
iface_name = _text_node(b"Readable")
397
method_name = _text_node(b"read")
398
method = MockNode("method_declaration",
399
start_point=(1, 4), end_point=(1, 20))
400
method.set_field("name", method_name)
401
body = MockNode("interface_body", children=[method])
402
iface = MockNode("interface_declaration",
403
start_point=(0, 0), end_point=(2, 0))
404
iface.set_field("name", iface_name)
405
iface.set_field("body", body)
406
stats = {"functions": 0, "classes": 0, "edges": 0}
407
parser._handle_interface(iface, source, "R.java", store, stats)
408
# interface node + method node
409
assert stats["classes"] == 1
410
assert stats["functions"] == 1
411

Keyboard Shortcuts

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