]>
Commit | Line | Data |
---|---|---|
1 | """Tests for the elpy.jedibackend module.""" | |
2 | ||
3 | import sys | |
4 | import unittest | |
5 | ||
6 | import jedi | |
7 | try: | |
8 | from unittest import mock | |
9 | except ImportError: | |
10 | import mock | |
11 | import re | |
12 | ||
13 | from elpy import jedibackend | |
14 | from elpy import rpc | |
15 | from elpy.tests import compat | |
16 | from elpy.tests.support import BackendTestCase | |
17 | from elpy.tests.support import RPCGetCompletionsTests | |
18 | from elpy.tests.support import RPCGetCompletionDocstringTests | |
19 | from elpy.tests.support import RPCGetCompletionLocationTests | |
20 | from elpy.tests.support import RPCGetDocstringTests | |
21 | from elpy.tests.support import RPCGetOnelineDocstringTests | |
22 | from elpy.tests.support import RPCGetDefinitionTests | |
23 | from elpy.tests.support import RPCGetCalltipTests | |
24 | from elpy.tests.support import RPCGetUsagesTests | |
25 | from elpy.tests.support import RPCGetNamesTests | |
26 | from elpy.tests.support import RPCGetRenameDiffTests | |
27 | from elpy.tests.support import RPCGetExtractFunctionDiffTests | |
28 | from elpy.tests.support import RPCGetExtractVariableDiffTests | |
29 | from elpy.tests.support import RPCGetInlineDiffTests | |
30 | from elpy.tests.support import RPCGetAssignmentTests | |
31 | ||
32 | ||
33 | class JediBackendTestCase(BackendTestCase): | |
34 | def setUp(self): | |
35 | super(JediBackendTestCase, self).setUp() | |
36 | env = jedi.get_default_environment().path | |
37 | self.backend = jedibackend.JediBackend(self.project_root, env) | |
38 | ||
39 | ||
40 | class TestInit(JediBackendTestCase): | |
41 | def test_should_have_jedi_as_name(self): | |
42 | self.assertEqual(self.backend.name, "jedi") | |
43 | ||
44 | ||
45 | class TestRPCGetCompletions(RPCGetCompletionsTests, | |
46 | JediBackendTestCase): | |
47 | BUILTINS = ['object', 'oct', 'open', 'ord', 'OSError', 'OverflowError'] | |
48 | ||
49 | ||
50 | class TestRPCGetAssignment(RPCGetAssignmentTests, | |
51 | JediBackendTestCase): | |
52 | pass | |
53 | ||
54 | ||
55 | class TestRPCGetCompletionDocstring(RPCGetCompletionDocstringTests, | |
56 | JediBackendTestCase): | |
57 | pass | |
58 | ||
59 | ||
60 | class TestRPCGetCompletionLocation(RPCGetCompletionLocationTests, | |
61 | JediBackendTestCase): | |
62 | pass | |
63 | ||
64 | ||
65 | class TestRPCGetDocstring(RPCGetDocstringTests, | |
66 | JediBackendTestCase): | |
67 | ||
68 | def __init__(self, *args, **kwargs): | |
69 | super(TestRPCGetDocstring, self).__init__(*args, **kwargs) | |
70 | self.JSON_LOADS_REGEX = ( | |
71 | r'loads\(s.*, cls.*, object_hook.*, parse_float.*, ' | |
72 | r'parse_int.*, .*\)' | |
73 | ) | |
74 | ||
75 | def check_docstring(self, docstring): | |
76 | lines = docstring.splitlines() | |
77 | self.assertEqual(lines[0], 'Documentation for json.loads:') | |
78 | match = re.match(self.JSON_LOADS_REGEX, lines[2]) | |
79 | self.assertIsNotNone(match) | |
80 | ||
81 | @mock.patch("elpy.jedibackend.run_with_debug") | |
82 | def test_should_not_return_empty_docstring(self, run_with_debug): | |
83 | location = mock.MagicMock() | |
84 | location.full_name = "testthing" | |
85 | location.docstring.return_value = "" | |
86 | run_with_debug.return_value = [location] | |
87 | filename = self.project_file("test.py", "print") | |
88 | docstring = self.backend.rpc_get_docstring(filename, "print", 0) | |
89 | self.assertIsNone(docstring) | |
90 | ||
91 | ||
92 | class TestRPCGetOnelineDocstring(RPCGetOnelineDocstringTests, | |
93 | JediBackendTestCase): | |
94 | ||
95 | def __init__(self, *args, **kwargs): | |
96 | super(TestRPCGetOnelineDocstring, self).__init__(*args, **kwargs) | |
97 | if sys.version_info >= (3, 6): | |
98 | self.JSON_LOADS_DOCSTRING = ( | |
99 | 'Deserialize ``s`` (a ``str``, ``bytes`` or' | |
100 | ' ``bytearray`` instance containing a JSON' | |
101 | ' document) to a Python object.' | |
102 | ) | |
103 | self.JSON_DOCSTRING = ( | |
104 | "JSON (JavaScript Object Notation) <http://json.org>" | |
105 | " is a subset of JavaScript syntax (ECMA-262" | |
106 | " 3rd edition) used as a lightweight data interchange format.") | |
107 | elif sys.version_info >= (3, 0): | |
108 | self.JSON_LOADS_DOCSTRING = ( | |
109 | 'Deserialize ``s`` (a ``str`` instance ' | |
110 | 'containing a JSON document) to a Python object.' | |
111 | ) | |
112 | self.JSON_DOCSTRING = ( | |
113 | "JSON (JavaScript Object Notation) <http://json.org>" | |
114 | " is a subset of JavaScript syntax (ECMA-262" | |
115 | " 3rd edition) used as a lightweight data interchange format.") | |
116 | else: | |
117 | self.JSON_LOADS_DOCSTRING = ( | |
118 | 'Deserialize ``s`` (a ``str`` or ``unicode`` ' | |
119 | 'instance containing a JSON document) to a Python object.' | |
120 | ) | |
121 | self.JSON_DOCSTRING = ( | |
122 | "JSON (JavaScript Object Notation) <http://json.org>" | |
123 | " is a subset of JavaScript syntax (ECMA-262" | |
124 | " 3rd edition) used as a lightweight data interchange format.") | |
125 | ||
126 | @mock.patch("elpy.jedibackend.run_with_debug") | |
127 | def test_should_not_return_empty_docstring(self, run_with_debug): | |
128 | location = mock.MagicMock() | |
129 | location.full_name = "testthing" | |
130 | location.docstring.return_value = "" | |
131 | run_with_debug.return_value = [location] | |
132 | filename = self.project_file("test.py", "print") | |
133 | docstring = self.backend.rpc_get_oneline_docstring(filename, "print", 0) | |
134 | self.assertIsNone(docstring) | |
135 | ||
136 | ||
137 | class TestRPCGetDefinition(RPCGetDefinitionTests, | |
138 | JediBackendTestCase): | |
139 | @mock.patch("jedi.Script") | |
140 | def test_should_not_fail_if_module_path_is_none(self, Script): | |
141 | """Do not fail if loc.module_path is None. | |
142 | ||
143 | This can happen under some circumstances I am unsure about. | |
144 | See #537 for the issue that reported this. | |
145 | ||
146 | """ | |
147 | locations = [ | |
148 | mock.Mock(module_path=None) | |
149 | ] | |
150 | script = Script.return_value | |
151 | script.goto_definitions.return_value = locations | |
152 | script.goto_assignments.return_value = locations | |
153 | ||
154 | location = self.rpc("", "", 0) | |
155 | ||
156 | self.assertIsNone(location) | |
157 | ||
158 | ||
159 | class TestRPCGetRenameDiff(RPCGetRenameDiffTests, | |
160 | JediBackendTestCase): | |
161 | pass | |
162 | ||
163 | ||
164 | class TestRPCGetExtractFunctionDiff(RPCGetExtractFunctionDiffTests, | |
165 | JediBackendTestCase): | |
166 | pass | |
167 | ||
168 | ||
169 | class TestRPCGetExtractVariableDiff(RPCGetExtractVariableDiffTests, | |
170 | JediBackendTestCase): | |
171 | pass | |
172 | ||
173 | ||
174 | class TestRPCGetInlineDiff(RPCGetInlineDiffTests, | |
175 | JediBackendTestCase): | |
176 | pass | |
177 | ||
178 | ||
179 | class TestRPCGetCalltip(RPCGetCalltipTests, | |
180 | JediBackendTestCase): | |
181 | KEYS_CALLTIP = {'index': None, | |
182 | 'params': [], | |
183 | 'name': u'keys'} | |
184 | RADIX_CALLTIP = {'index': None, | |
185 | 'params': [], | |
186 | 'name': u'radix'} | |
187 | ADD_CALLTIP = {'index': 0, | |
188 | 'params': [u'a', u'b'], | |
189 | 'name': u'add'} | |
190 | if compat.PYTHON3: | |
191 | THREAD_CALLTIP = {'name': 'Thread', | |
192 | 'index': 0, | |
193 | 'params': ['group: None=...', | |
194 | 'target: Optional[Callable[..., Any]]=...', | |
195 | 'name: Optional[str]=...', | |
196 | 'args: Iterable[Any]=...', | |
197 | 'kwargs: Mapping[str, Any]=...', | |
198 | 'daemon: Optional[bool]=...']} | |
199 | ||
200 | else: | |
201 | THREAD_CALLTIP = {'index': 0, | |
202 | 'name': u'Thread', | |
203 | 'params': [u'group: None=...', | |
204 | u'target: Optional[Callable[..., Any]]=...', | |
205 | u'name: Optional[str]=...', | |
206 | u'args: Iterable[Any]=...', | |
207 | u'kwargs: Mapping[str, Any]=...']} | |
208 | ||
209 | def test_should_not_fail_with_get_subscope_by_name(self): | |
210 | # Bug #677 / jedi#628 | |
211 | source = ( | |
212 | u"my_lambda = lambda x: x+1\n" | |
213 | u"my_lambda(1)" | |
214 | ) | |
215 | filename = self.project_file("project.py", source) | |
216 | offset = 37 | |
217 | ||
218 | sigs = self.backend.rpc_get_calltip(filename, source, offset) | |
219 | sigs["index"] | |
220 | ||
221 | ||
222 | class TestRPCGetUsages(RPCGetUsagesTests, | |
223 | JediBackendTestCase): | |
224 | def test_should_not_fail_for_missing_module(self): | |
225 | # This causes use.module_path to be None | |
226 | source = "import sys\n\nsys.path.\n" # insert()" | |
227 | offset = 21 | |
228 | filename = self.project_file("project.py", source) | |
229 | ||
230 | self.rpc(filename, source, offset) | |
231 | ||
232 | ||
233 | class TestRPCGetNames(RPCGetNamesTests, | |
234 | JediBackendTestCase): | |
235 | pass | |
236 | ||
237 | ||
238 | class TestPosToLinecol(unittest.TestCase): | |
239 | def test_should_handle_beginning_of_string(self): | |
240 | self.assertEqual(jedibackend.pos_to_linecol("foo", 0), | |
241 | (1, 0)) | |
242 | ||
243 | def test_should_handle_end_of_line(self): | |
244 | self.assertEqual(jedibackend.pos_to_linecol("foo\nbar\nbaz\nqux", 9), | |
245 | (3, 1)) | |
246 | ||
247 | def test_should_handle_end_of_string(self): | |
248 | self.assertEqual(jedibackend.pos_to_linecol("foo\nbar\nbaz\nqux", 14), | |
249 | (4, 2)) | |
250 | ||
251 | ||
252 | class TestLinecolToPos(unittest.TestCase): | |
253 | def test_should_handle_beginning_of_string(self): | |
254 | self.assertEqual(jedibackend.linecol_to_pos("foo", 1, 0), | |
255 | 0) | |
256 | ||
257 | def test_should_handle_end_of_string(self): | |
258 | self.assertEqual(jedibackend.linecol_to_pos("foo\nbar\nbaz\nqux", | |
259 | 3, 1), | |
260 | 9) | |
261 | ||
262 | def test_should_return_offset(self): | |
263 | self.assertEqual(jedibackend.linecol_to_pos("foo\nbar\nbaz\nqux", | |
264 | 4, 2), | |
265 | 14) | |
266 | ||
267 | def test_should_fail_for_line_past_text(self): | |
268 | self.assertRaises(ValueError, | |
269 | jedibackend.linecol_to_pos, "foo\n", 3, 1) | |
270 | ||
271 | def test_should_fail_for_column_past_text(self): | |
272 | self.assertRaises(ValueError, | |
273 | jedibackend.linecol_to_pos, "foo\n", 1, 10) | |
274 | ||
275 | ||
276 | class TestRunWithDebug(unittest.TestCase): | |
277 | @mock.patch('jedi.Script') | |
278 | def test_should_call_method(self, Script): | |
279 | Script.return_value.test_method.return_value = "test-result" | |
280 | ||
281 | result = jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, | |
282 | arg=3) | |
283 | ||
284 | Script.assert_called_with(1, 2, arg=3) | |
285 | self.assertEqual(result, 'test-result') | |
286 | ||
287 | @mock.patch('jedi.Script') | |
288 | def test_should_re_raise(self, Script): | |
289 | Script.side_effect = RuntimeError | |
290 | ||
291 | with self.assertRaises(RuntimeError): | |
292 | jedibackend.run_with_debug(jedi, 'test_method', 1, 2, arg=3, | |
293 | re_raise=(RuntimeError,)) | |
294 | ||
295 | @mock.patch('jedi.Script') | |
296 | @mock.patch('jedi.set_debug_function') | |
297 | def test_should_keep_debug_info(self, set_debug_function, Script): | |
298 | Script.side_effect = RuntimeError | |
299 | ||
300 | try: | |
301 | jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3) | |
302 | except rpc.Fault as e: | |
303 | self.assertGreaterEqual(e.code, 400) | |
304 | self.assertIsNotNone(e.data) | |
305 | self.assertIn("traceback", e.data) | |
306 | jedi_debug_info = e.data["jedi_debug_info"] | |
307 | self.assertIsNotNone(jedi_debug_info) | |
308 | self.assertEqual(jedi_debug_info["script_args"], | |
309 | "1, 2, arg=3") | |
310 | self.assertEqual(jedi_debug_info["source"], None) | |
311 | self.assertEqual(jedi_debug_info["method"], "test_method") | |
312 | self.assertEqual(jedi_debug_info["debug_info"], []) | |
313 | else: | |
314 | self.fail("Fault not thrown") | |
315 | ||
316 | @mock.patch('jedi.Script') | |
317 | @mock.patch('jedi.set_debug_function') | |
318 | def test_should_keep_error_text(self, set_debug_function, Script): | |
319 | Script.side_effect = RuntimeError | |
320 | ||
321 | try: | |
322 | jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3) | |
323 | except rpc.Fault as e: | |
324 | self.assertEqual(str(e), str(RuntimeError())) | |
325 | self.assertEqual(e.message, str(RuntimeError())) | |
326 | else: | |
327 | self.fail("Fault not thrown") | |
328 | ||
329 | @mock.patch('jedi.Script') | |
330 | @mock.patch('jedi.set_debug_function') | |
331 | def test_should_handle_source_special(self, set_debug_function, Script): | |
332 | Script.side_effect = RuntimeError | |
333 | ||
334 | try: | |
335 | jedibackend.run_with_debug(jedi, 'test_method', source="foo") | |
336 | except rpc.Fault as e: | |
337 | self.assertEqual(e.data["jedi_debug_info"]["script_args"], | |
338 | "source=source") | |
339 | self.assertEqual(e.data["jedi_debug_info"]["source"], "foo") | |
340 | else: | |
341 | self.fail("Fault not thrown") | |
342 | ||
343 | @mock.patch('jedi.Script') | |
344 | @mock.patch('jedi.set_debug_function') | |
345 | def test_should_set_debug_info(self, set_debug_function, Script): | |
346 | the_debug_function = [None] | |
347 | ||
348 | def my_set_debug_function(debug_function, **kwargs): | |
349 | the_debug_function[0] = debug_function | |
350 | ||
351 | def my_script(*args, **kwargs): | |
352 | the_debug_function[0](jedi.debug.NOTICE, "Notice") | |
353 | the_debug_function[0](jedi.debug.WARNING, "Warning") | |
354 | the_debug_function[0]("other", "Other") | |
355 | raise RuntimeError | |
356 | ||
357 | set_debug_function.side_effect = my_set_debug_function | |
358 | Script.return_value.test_method = my_script | |
359 | ||
360 | try: | |
361 | jedibackend.run_with_debug(jedi, 'test_method', source="foo") | |
362 | except rpc.Fault as e: | |
363 | self.assertEqual(e.data["jedi_debug_info"]["debug_info"], | |
364 | ["[N] Notice", | |
365 | "[W] Warning", | |
366 | "[?] Other"]) | |
367 | else: | |
368 | self.fail("Fault not thrown") | |
369 | ||
370 | @mock.patch('jedi.set_debug_function') | |
371 | @mock.patch('jedi.Script') | |
372 | def test_should_not_fail_with_bad_data(self, Script, set_debug_function): | |
373 | import jedi.debug | |
374 | ||
375 | def set_debug(function, speed=True): | |
376 | if function is not None: | |
377 | function(jedi.debug.NOTICE, u"\xab") | |
378 | ||
379 | set_debug_function.side_effect = set_debug | |
380 | Script.return_value.test_method.side_effect = Exception | |
381 | ||
382 | with self.assertRaises(rpc.Fault): | |
383 | jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3) |