]>
Commit | Line | Data |
---|---|---|
1 | # coding: utf-8 | |
2 | ||
3 | """Support classes and functions for the elpy test code. | |
4 | ||
5 | Elpy uses a bit of a peculiar test setup to avoid redundancy. For the | |
6 | tests of the two backends, we provide generic test cases for generic | |
7 | tests and for specific callback tests. | |
8 | ||
9 | These mixins can be included in the actual test classes. We can't add | |
10 | these tests to a BackendTestCase subclass directly because the test | |
11 | discovery would find them there and try to run them, which would fail. | |
12 | ||
13 | """ | |
14 | ||
15 | import os | |
16 | import re | |
17 | import shutil | |
18 | import sys | |
19 | import tempfile | |
20 | import unittest | |
21 | ||
22 | from elpy import jedibackend | |
23 | from elpy.rpc import Fault | |
24 | from elpy.tests import compat | |
25 | ||
26 | if jedibackend.JEDISUP18: | |
27 | import pathlib | |
28 | ||
29 | ||
30 | class BackendTestCase(unittest.TestCase): | |
31 | """Base class for backend tests. | |
32 | ||
33 | This class sets up a project root directory and provides an easy | |
34 | way to create files within the project root. | |
35 | ||
36 | """ | |
37 | ||
38 | def setUp(self): | |
39 | """Create the project root and make sure it gets cleaned up.""" | |
40 | super(BackendTestCase, self).setUp() | |
41 | self.project_root = tempfile.mkdtemp(prefix="elpy-test") | |
42 | self.addCleanup(shutil.rmtree, self.project_root, True) | |
43 | ||
44 | def project_file(self, relname, contents): | |
45 | """Create a file named relname within the project root. | |
46 | ||
47 | Write contents into that file. | |
48 | ||
49 | """ | |
50 | full_name = os.path.join(self.project_root, relname) | |
51 | try: | |
52 | os.makedirs(os.path.dirname(full_name)) | |
53 | except OSError: | |
54 | pass | |
55 | if compat.PYTHON3: | |
56 | fobj = open(full_name, "w", encoding="utf-8") | |
57 | else: | |
58 | fobj = open(full_name, "w") | |
59 | with fobj as f: | |
60 | f.write(contents) | |
61 | # return full_name | |
62 | if jedibackend.JEDISUP18: | |
63 | return pathlib.Path(full_name) | |
64 | else: | |
65 | return full_name | |
66 | ||
67 | ||
68 | class GenericRPCTests(object): | |
69 | """Generic RPC test methods. | |
70 | ||
71 | This is a mixin to add tests that should be run for all RPC | |
72 | methods that follow the generic (filename, source, offset) calling | |
73 | conventions. | |
74 | ||
75 | """ | |
76 | METHOD = None | |
77 | ||
78 | def rpc(self, filename, source, offset): | |
79 | method = getattr(self.backend, self.METHOD) | |
80 | return method(filename, source, offset) | |
81 | ||
82 | def test_should_not_fail_on_inexisting_file(self): | |
83 | filename = self.project_root + "/doesnotexist.py" | |
84 | self.rpc(filename, "", 0) | |
85 | ||
86 | def test_should_not_fail_on_empty_file(self): | |
87 | filename = self.project_file("test.py", "") | |
88 | self.rpc(filename, "", 0) | |
89 | ||
90 | def test_should_not_fail_if_file_is_none(self): | |
91 | self.rpc(None, "", 0) | |
92 | ||
93 | def test_should_not_fail_for_module_syntax_errors(self): | |
94 | source, offset = source_and_offset( | |
95 | "class Foo(object):\n" | |
96 | " def bar(self):\n" | |
97 | " foo(_|_" | |
98 | " bar(" | |
99 | "\n" | |
100 | " def a(self):\n" | |
101 | " pass\n" | |
102 | "\n" | |
103 | " def b(self):\n" | |
104 | " pass\n" | |
105 | "\n" | |
106 | " def b(self):\n" | |
107 | " pass\n" | |
108 | "\n" | |
109 | " def b(self):\n" | |
110 | " pass\n" | |
111 | "\n" | |
112 | " def b(self):\n" | |
113 | " pass\n" | |
114 | "\n" | |
115 | " def b(self):\n" | |
116 | " pass\n" | |
117 | ) | |
118 | filename = self.project_file("test.py", source) | |
119 | ||
120 | self.rpc(filename, source, offset) | |
121 | ||
122 | def test_should_not_fail_for_bad_indentation(self): | |
123 | source, offset = source_and_offset( | |
124 | "def foo():\n" | |
125 | " print(23)_|_\n" | |
126 | " print(17)\n") | |
127 | filename = self.project_file("test.py", source) | |
128 | ||
129 | self.rpc(filename, source, offset) | |
130 | ||
131 | # @unittest.skipIf((3, 3) <= sys.version_info < (3, 4), | |
132 | # "Bug in jedi for Python 3.3") | |
133 | def test_should_not_fail_for_relative_import(self): | |
134 | source, offset = source_and_offset( | |
135 | "from .. import foo_|_" | |
136 | ) | |
137 | filename = self.project_file("test.py", source) | |
138 | ||
139 | self.rpc(filename, source, offset) | |
140 | ||
141 | def test_should_not_fail_on_keyword(self): | |
142 | source, offset = source_and_offset( | |
143 | "_|_try:\n" | |
144 | " pass\n" | |
145 | "except:\n" | |
146 | " pass\n") | |
147 | filename = self.project_file("test.py", source) | |
148 | ||
149 | self.rpc(filename, source, offset) | |
150 | ||
151 | def test_should_not_fail_with_bad_encoding(self): | |
152 | source, offset = source_and_offset( | |
153 | u'# coding: utf-8X_|_\n' | |
154 | ) | |
155 | filename = self.project_file("test.py", source) | |
156 | ||
157 | self.rpc(filename, source, offset) | |
158 | ||
159 | def test_should_not_fail_with_form_feed_characters(self): | |
160 | # Bug in Jedi: jedi#424 | |
161 | source, offset = source_and_offset( | |
162 | "\f\n" | |
163 | "class Test(object):_|_\n" | |
164 | " pass" | |
165 | ) | |
166 | filename = self.project_file("test.py", source) | |
167 | ||
168 | self.rpc(filename, source, offset) | |
169 | ||
170 | def test_should_not_fail_for_dictionaries_in_weird_places(self): | |
171 | # Bug in Jedi: jedi#417 | |
172 | source, offset = source_and_offset( | |
173 | "import json\n" | |
174 | "\n" | |
175 | "def foo():\n" | |
176 | " json.loads(_|_\n" | |
177 | "\n" | |
178 | " json.load.return_value = {'foo': [],\n" | |
179 | " 'bar': True}\n" | |
180 | "\n" | |
181 | " c = Foo()\n" | |
182 | ) | |
183 | filename = self.project_file("test.py", source) | |
184 | ||
185 | self.rpc(filename, source, offset) | |
186 | ||
187 | def test_should_not_break_with_binary_characters_in_docstring(self): | |
188 | # Bug in Jedi: jedi#427 | |
189 | template = '''\ | |
190 | class Foo(object): | |
191 | def __init__(self): | |
192 | """ | |
193 | COMMUNITY instance that this conversion belongs to. | |
194 | DISPERSY_VERSION is the dispersy conversion identifier (on the wire version; must be one byte). | |
195 | COMMUNIY_VERSION is the community conversion identifier (on the wire version; must be one byte). | |
196 | ||
197 | COMMUNIY_VERSION may not be '\\x00' or '\\xff'. '\\x00' is used by the DefaultConversion until | |
198 | a proper conversion instance can be made for the Community. '\\xff' is reserved for when | |
199 | more than one byte is needed as a version indicator. | |
200 | """ | |
201 | pass | |
202 | ||
203 | ||
204 | x = Foo() | |
205 | x._|_ | |
206 | ''' | |
207 | source, offset = source_and_offset(template) | |
208 | filename = self.project_file("test.py", source) | |
209 | ||
210 | self.rpc(filename, source, offset) | |
211 | ||
212 | def test_should_not_fail_for_def_without_name(self): | |
213 | # Bug jedi#429 | |
214 | source, offset = source_and_offset( | |
215 | "def_|_():\n" | |
216 | " if True:\n" | |
217 | " return True\n" | |
218 | " else:\n" | |
219 | " return False\n" | |
220 | ) | |
221 | filename = self.project_file("project.py", source) | |
222 | ||
223 | self.rpc(filename, source, offset) | |
224 | ||
225 | def test_should_not_fail_on_lambda(self): | |
226 | # Bug #272 / jedi#431, jedi#572 | |
227 | source, offset = source_and_offset( | |
228 | "map(lambda_|_" | |
229 | ) | |
230 | filename = self.project_file("project.py", source) | |
231 | ||
232 | self.rpc(filename, source, offset) | |
233 | ||
234 | def test_should_not_fail_on_literals(self): | |
235 | # Bug #314, #344 / jedi#466 | |
236 | source = u'lit = u"""\\\n# -*- coding: utf-8 -*-\n"""\n' | |
237 | offset = 0 | |
238 | filename = self.project_file("project.py", source) | |
239 | ||
240 | self.rpc(filename, source, offset) | |
241 | ||
242 | def test_should_not_fail_with_args_as_args(self): | |
243 | # Bug #347 in rope_py3k | |
244 | source, offset = source_and_offset( | |
245 | "def my_function(*args):\n" | |
246 | " ret_|_" | |
247 | ) | |
248 | filename = self.project_file("project.py", source) | |
249 | ||
250 | self.rpc(filename, source, offset) | |
251 | ||
252 | def test_should_not_fail_for_unicode_chars_in_string(self): | |
253 | # Bug #358 / jedi#482 | |
254 | source = '''\ | |
255 | # coding: utf-8 | |
256 | ||
257 | logging.info(u"Saving «{}»...".format(title)) | |
258 | requests.get(u"https://web.archive.org/save/{}".format(url)) | |
259 | ''' | |
260 | offset = 57 | |
261 | filename = self.project_file("project.py", source) | |
262 | ||
263 | self.rpc(filename, source, offset) | |
264 | ||
265 | def test_should_not_fail_for_bad_escape_sequence(self): | |
266 | # Bug #360 / jedi#485 | |
267 | source = r"v = '\x'" | |
268 | offset = 8 | |
269 | filename = self.project_file("project.py", source) | |
270 | ||
271 | self.rpc(filename, source, offset) | |
272 | ||
273 | def test_should_not_fail_for_coding_declarations_in_strings(self): | |
274 | # Bug #314 / jedi#465 / python#22221 | |
275 | source = u'lit = """\\\n# -*- coding: utf-8 -*-\n"""' | |
276 | offset = 8 | |
277 | filename = self.project_file("project.py", source) | |
278 | ||
279 | self.rpc(filename, source, offset) | |
280 | ||
281 | def test_should_not_fail_if_root_vanishes(self): | |
282 | # Bug #353 | |
283 | source, offset = source_and_offset( | |
284 | "import foo\n" | |
285 | "foo._|_" | |
286 | ) | |
287 | filename = self.project_file("project.py", source) | |
288 | shutil.rmtree(self.project_root) | |
289 | ||
290 | self.rpc(filename, source, offset) | |
291 | ||
292 | # For some reason, this breaks a lot of other tests. Couldn't | |
293 | # figure out why. | |
294 | # | |
295 | # def test_should_not_fail_for_sys_path(self): | |
296 | # # Bug #365 / jedi#486 | |
297 | # source, offset = source_and_offset( | |
298 | # "import sys\n" | |
299 | # "\n" | |
300 | # "sys.path.index(_|_\n" | |
301 | # ) | |
302 | # filename = self.project_file("project.py", source) | |
303 | # | |
304 | # self.rpc(filename, source, offset) | |
305 | ||
306 | def test_should_not_fail_for_key_error(self): | |
307 | # Bug #561, #564, #570, #588, #593, #599 / jedi#572, jedi#579, | |
308 | # jedi#590 | |
309 | source, offset = source_and_offset( | |
310 | "map(lambda_|_" | |
311 | ) | |
312 | filename = self.project_file("project.py", source) | |
313 | ||
314 | self.rpc(filename, source, offset) | |
315 | ||
316 | def test_should_not_fail_for_badly_defined_global_variable(self): | |
317 | # Bug #519 / jedi#610 | |
318 | source, offset = source_and_offset( | |
319 | """\ | |
320 | def funct1(): | |
321 | global global_dict_var | |
322 | global_dict_var = dict() | |
323 | ||
324 | def funct2(): | |
325 | global global_dict_var | |
326 | q = global_dict_var.copy_|_() | |
327 | print(q)""") | |
328 | filename = self.project_file("project.py", source) | |
329 | ||
330 | self.rpc(filename, source, offset) | |
331 | ||
332 | def test_should_not_fail_with_mergednamesdict(self): | |
333 | # Bug #563 / jedi#589 | |
334 | source, offset = source_and_offset( | |
335 | u'from email import message_|_' | |
336 | ) | |
337 | filename = self.project_file("project.py", source) | |
338 | ||
339 | self.rpc(filename, source, offset) | |
340 | ||
341 | ||
342 | class RPCGetCompletionsTests(GenericRPCTests): | |
343 | METHOD = "rpc_get_completions" | |
344 | ||
345 | def test_should_complete_builtin(self): | |
346 | source, offset = source_and_offset("o_|_") | |
347 | ||
348 | expected = self.BUILTINS | |
349 | actual = [cand['name'] for cand in | |
350 | self.backend.rpc_get_completions("test.py", | |
351 | source, offset)] | |
352 | ||
353 | for candidate in expected: | |
354 | self.assertIn(candidate, actual) | |
355 | ||
356 | if sys.version_info >= (3, 5) or sys.version_info < (3, 0): | |
357 | JSON_COMPLETIONS = ["SONDecoder", "SONEncoder", "SONDecodeError"] | |
358 | else: | |
359 | JSON_COMPLETIONS = ["SONDecoder", "SONEncoder"] | |
360 | ||
361 | def test_should_complete_imports(self): | |
362 | source, offset = source_and_offset("import json\n" | |
363 | "json.J_|_") | |
364 | filename = self.project_file("test.py", source) | |
365 | completions = self.backend.rpc_get_completions(filename, | |
366 | source, | |
367 | offset) | |
368 | self.assertEqual( | |
369 | sorted([cand['suffix'] for cand in completions]), | |
370 | sorted(self.JSON_COMPLETIONS)) | |
371 | ||
372 | def test_should_complete_top_level_modules_for_import(self): | |
373 | source, offset = source_and_offset("import multi_|_") | |
374 | filename = self.project_file("test.py", source) | |
375 | completions = self.backend.rpc_get_completions(filename, | |
376 | source, | |
377 | offset) | |
378 | if compat.PYTHON3: | |
379 | expected = ["processing"] | |
380 | else: | |
381 | expected = ["file", "processing"] | |
382 | self.assertEqual(sorted([cand['suffix'] for cand in completions]), | |
383 | sorted(expected)) | |
384 | ||
385 | def test_should_complete_packages_for_import(self): | |
386 | source, offset = source_and_offset("import email.mi_|_") | |
387 | filename = self.project_file("test.py", source) | |
388 | completions = self.backend.rpc_get_completions(filename, | |
389 | source, | |
390 | offset) | |
391 | if sys.version_info < (3, 0): | |
392 | compl = [u'me', u'METext'] | |
393 | else: | |
394 | compl = ['me'] | |
395 | self.assertEqual([cand['suffix'] for cand in completions], | |
396 | compl) | |
397 | ||
398 | def test_should_not_complete_for_import(self): | |
399 | source, offset = source_and_offset("import foo.Conf_|_") | |
400 | filename = self.project_file("test.py", source) | |
401 | completions = self.backend.rpc_get_completions(filename, | |
402 | source, | |
403 | offset) | |
404 | self.assertEqual([cand['suffix'] for cand in completions], | |
405 | []) | |
406 | ||
407 | # @unittest.skipIf((3, 3) <= sys.version_info < (3, 4), | |
408 | # "Bug in jedi for Python 3.3") | |
409 | def test_should_not_fail_for_short_module(self): | |
410 | source, offset = source_and_offset("from .. import foo_|_") | |
411 | filename = self.project_file("test.py", source) | |
412 | completions = self.backend.rpc_get_completions(filename, | |
413 | source, | |
414 | offset) | |
415 | self.assertIsNotNone(completions) | |
416 | ||
417 | def test_should_complete_sys(self): | |
418 | source, offset = source_and_offset("import sys\nsys._|_") | |
419 | filename = self.project_file("test.py", source) | |
420 | completions = self.backend.rpc_get_completions(filename, | |
421 | source, | |
422 | offset) | |
423 | self.assertIn('path', [cand['suffix'] for cand in completions]) | |
424 | ||
425 | def test_should_find_with_trailing_text(self): | |
426 | source, offset = source_and_offset( | |
427 | "import threading\nthreading.T_|_mumble mumble") | |
428 | ||
429 | expected = ["Thread", "ThreadError", "Timer"] | |
430 | actual = [cand['name'] for cand in | |
431 | self.backend.rpc_get_completions("test.py", source, offset)] | |
432 | ||
433 | for candidate in expected: | |
434 | self.assertIn(candidate, actual) | |
435 | ||
436 | def test_should_find_completion_different_package(self): | |
437 | # See issue #74 | |
438 | self.project_file("project/__init__.py", "") | |
439 | source1 = ("class Add:\n" | |
440 | " def add(self, a, b):\n" | |
441 | " return a + b\n") | |
442 | self.project_file("project/add.py", source1) | |
443 | source2, offset = source_and_offset( | |
444 | "from project.add import Add\n" | |
445 | "class Calculator:\n" | |
446 | " def add(self, a, b):\n" | |
447 | " c = Add()\n" | |
448 | " c.ad_|_\n") | |
449 | file2 = self.project_file("project/calculator.py", source2) | |
450 | proposals = self.backend.rpc_get_completions(file2, | |
451 | source2, | |
452 | offset) | |
453 | self.assertEqual(["add"], | |
454 | [proposal["name"] for proposal in proposals]) | |
455 | ||
456 | def test_should_return_nothing_when_no_completion(self): | |
457 | source, offset = source_and_offset("nothingcancompletethis_|_") | |
458 | self.assertEqual([], self.backend.rpc_get_completions("test.py", | |
459 | source, offset)) | |
460 | ||
461 | ||
462 | class RPCGetCompletionDocstringTests(object): | |
463 | def test_should_return_docstring(self): | |
464 | source, offset = source_and_offset("import json\n" | |
465 | "json.JSONEnc_|_") | |
466 | filename = self.project_file("test.py", source) | |
467 | completions = self.backend.rpc_get_completions(filename, | |
468 | source, | |
469 | offset) | |
470 | completions.sort(key=lambda p: p["name"]) | |
471 | prop = completions[0] | |
472 | self.assertEqual(prop["name"], "JSONEncoder") | |
473 | ||
474 | docs = self.backend.rpc_get_completion_docstring("JSONEncoder") | |
475 | ||
476 | self.assertIn("Extensible JSON", docs) | |
477 | ||
478 | def test_should_return_none_if_unknown(self): | |
479 | docs = self.backend.rpc_get_completion_docstring("Foo") | |
480 | ||
481 | self.assertIsNone(docs) | |
482 | ||
483 | def test_should_return_none_if_on_a_builtin(self): | |
484 | source, offset = source_and_offset("a = 12\n" | |
485 | "print(12_|_)") | |
486 | filename = self.project_file("test.py", source) | |
487 | completions = self.backend.rpc_get_docstring(filename, | |
488 | source, | |
489 | offset) | |
490 | self.assertIsNone(completions) | |
491 | ||
492 | ||
493 | class RPCGetCompletionLocationTests(object): | |
494 | def test_should_return_location(self): | |
495 | source, offset = source_and_offset("donaudampfschiff = 1\n" | |
496 | "donau_|_") | |
497 | filename = self.project_file("test.py", source) | |
498 | completions = self.backend.rpc_get_completions(filename, | |
499 | source, | |
500 | offset) | |
501 | prop = completions[0] | |
502 | self.assertEqual(prop["name"], "donaudampfschiff") | |
503 | ||
504 | loc = self.backend.rpc_get_completion_location("donaudampfschiff") | |
505 | ||
506 | self.assertEqual((filename, 1), loc) | |
507 | ||
508 | def test_should_return_none_if_unknown(self): | |
509 | docs = self.backend.rpc_get_completion_location("Foo") | |
510 | ||
511 | self.assertIsNone(docs) | |
512 | ||
513 | ||
514 | class RPCGetDefinitionTests(GenericRPCTests): | |
515 | METHOD = "rpc_get_definition" | |
516 | ||
517 | def test_should_return_definition_location_same_file(self): | |
518 | source, offset = source_and_offset("import threading\n" | |
519 | "def test_function(a, b):\n" | |
520 | " return a + b\n" | |
521 | "\n" | |
522 | "test_func_|_tion(\n") | |
523 | filename = self.project_file("test.py", source) | |
524 | ||
525 | location = self.backend.rpc_get_definition(filename, | |
526 | source, | |
527 | offset) | |
528 | ||
529 | self.assertEqual(location[0], filename) | |
530 | # On def or on the function name | |
531 | self.assertIn(location[1], (17, 21)) | |
532 | ||
533 | def test_should_return_location_in_same_file_if_not_saved(self): | |
534 | source, offset = source_and_offset( | |
535 | "import threading\n" | |
536 | "\n" | |
537 | "\n" | |
538 | "def other_function():\n" | |
539 | " test_f_|_unction(1, 2)\n" | |
540 | "\n" | |
541 | "\n" | |
542 | "def test_function(a, b):\n" | |
543 | " return a + b\n") | |
544 | filename = self.project_file("test.py", "") | |
545 | ||
546 | location = self.backend.rpc_get_definition(filename, | |
547 | source, | |
548 | offset) | |
549 | ||
550 | self.assertEqual(location[0], filename) | |
551 | # def or function name | |
552 | self.assertIn(location[1], (67, 71)) | |
553 | ||
554 | def test_should_return_location_in_different_file(self): | |
555 | source1 = ("def test_function(a, b):\n" | |
556 | " return a + b\n") | |
557 | file1 = self.project_file("test1.py", source1) | |
558 | source2, offset = source_and_offset("from test1 import test_function\n" | |
559 | "test_funct_|_ion(1, 2)\n") | |
560 | file2 = self.project_file("test2.py", source2) | |
561 | ||
562 | definition = self.backend.rpc_get_definition(file2, | |
563 | source2, | |
564 | offset) | |
565 | ||
566 | self.assertEqual(definition[0], file1) | |
567 | # Either on the def or on the function name | |
568 | self.assertIn(definition[1], (0, 4)) | |
569 | ||
570 | def test_should_return_none_if_location_not_found(self): | |
571 | source, offset = source_and_offset("test_f_|_unction()\n") | |
572 | filename = self.project_file("test.py", source) | |
573 | ||
574 | definition = self.backend.rpc_get_definition(filename, | |
575 | source, | |
576 | offset) | |
577 | ||
578 | self.assertIsNone(definition) | |
579 | ||
580 | def test_should_return_none_if_outside_of_symbol(self): | |
581 | source, offset = source_and_offset("test_function(_|_)\n") | |
582 | filename = self.project_file("test.py", source) | |
583 | ||
584 | definition = self.backend.rpc_get_definition(filename, | |
585 | source, | |
586 | offset) | |
587 | ||
588 | self.assertIsNone(definition) | |
589 | ||
590 | def test_should_return_definition_location_different_package(self): | |
591 | # See issue #74 | |
592 | self.project_file("project/__init__.py", "") | |
593 | source1 = ("class Add:\n" | |
594 | " def add(self, a, b):\n" | |
595 | " return a + b\n") | |
596 | file1 = self.project_file("project/add.py", source1) | |
597 | source2, offset = source_and_offset( | |
598 | "from project.add import Add\n" | |
599 | "class Calculator:\n" | |
600 | " def add(self, a, b):\n" | |
601 | " return Add_|_().add(a, b)\n") | |
602 | file2 = self.project_file("project/calculator.py", source2) | |
603 | ||
604 | location = self.backend.rpc_get_definition(file2, | |
605 | source2, | |
606 | offset) | |
607 | ||
608 | self.assertEqual(location[0], file1) | |
609 | # class or class name | |
610 | self.assertIn(location[1], (0, 6)) | |
611 | ||
612 | def test_should_find_variable_definition(self): | |
613 | source, offset = source_and_offset("SOME_VALUE = 1\n" | |
614 | "\n" | |
615 | "variable = _|_SOME_VALUE\n") | |
616 | filename = self.project_file("test.py", source) | |
617 | self.assertEqual(self.backend.rpc_get_definition(filename, | |
618 | source, | |
619 | offset), | |
620 | (filename, 0)) | |
621 | ||
622 | ||
623 | class RPCGetAssignmentTests(): | |
624 | METHOD = "rpc_get_assignment" | |
625 | ||
626 | def test_should_raise_fault(self): | |
627 | if jedibackend.JEDISUP17: | |
628 | with self.assertRaises(Fault): | |
629 | self.backend.rpc_get_assignment("test.py", "dummy code", 1) | |
630 | ||
631 | ||
632 | class RPCGetCalltipTests(GenericRPCTests): | |
633 | METHOD = "rpc_get_calltip" | |
634 | ||
635 | def test_should_get_calltip(self): | |
636 | expected = self.THREAD_CALLTIP | |
637 | source, offset = source_and_offset( | |
638 | "import threading\nthreading.Thread(_|_") | |
639 | filename = self.project_file("test.py", source) | |
640 | calltip = self.backend.rpc_get_calltip(filename, | |
641 | source, | |
642 | offset) | |
643 | self.assertEqual(calltip, expected) | |
644 | calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
645 | source, | |
646 | offset) | |
647 | calltip.pop('kind') | |
648 | self.assertEqual(calltip, expected) | |
649 | ||
650 | def test_should_get_calltip_even_after_parens(self): | |
651 | source, offset = source_and_offset( | |
652 | "import threading\nthreading.Thread(foo()_|_") | |
653 | filename = self.project_file("test.py", source) | |
654 | actual = self.backend.rpc_get_calltip(filename, | |
655 | source, | |
656 | offset) | |
657 | self.assertEqual(self.THREAD_CALLTIP, actual) | |
658 | actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
659 | source, | |
660 | offset) | |
661 | actual.pop('kind') | |
662 | self.assertEqual(self.THREAD_CALLTIP, actual) | |
663 | ||
664 | def test_should_get_calltip_at_closing_paren(self): | |
665 | source, offset = source_and_offset( | |
666 | "import threading\nthreading.Thread(_|_)") | |
667 | filename = self.project_file("test.py", source) | |
668 | actual = self.backend.rpc_get_calltip(filename, | |
669 | source, | |
670 | offset) | |
671 | self.assertEqual(self.THREAD_CALLTIP, actual) | |
672 | actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
673 | source, | |
674 | offset) | |
675 | self.assertEqual(actual['kind'], "oneline_doc") | |
676 | ||
677 | def test_should_not_missing_attribute_get_definition(self): | |
678 | # Bug #627 / jedi#573 | |
679 | source, offset = source_and_offset( | |
680 | "import threading\nthreading.Thread(_|_)") | |
681 | filename = self.project_file("test.py", source) | |
682 | ||
683 | self.backend.rpc_get_calltip(filename, source, offset) | |
684 | ||
685 | def test_should_return_none_for_bad_identifier(self): | |
686 | source, offset = source_and_offset( | |
687 | "froblgoo(_|_") | |
688 | filename = self.project_file("test.py", source) | |
689 | calltip = self.backend.rpc_get_calltip(filename, | |
690 | source, | |
691 | offset) | |
692 | self.assertIsNone(calltip) | |
693 | calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
694 | source, | |
695 | offset) | |
696 | self.assertIsNone(calltip) | |
697 | ||
698 | def test_should_remove_self_argument(self): | |
699 | source, offset = source_and_offset( | |
700 | "d = dict()\n" | |
701 | "d.keys(_|_") | |
702 | filename = self.project_file("test.py", source) | |
703 | ||
704 | actual = self.backend.rpc_get_calltip(filename, | |
705 | source, | |
706 | offset) | |
707 | self.assertEqual(self.KEYS_CALLTIP, actual) | |
708 | actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
709 | source, | |
710 | offset) | |
711 | actual.pop('kind') | |
712 | self.assertEqual(self.KEYS_CALLTIP, actual) | |
713 | ||
714 | def test_should_remove_package_prefix(self): | |
715 | source, offset = source_and_offset( | |
716 | "import decimal\n" | |
717 | "d = decimal.Decimal('1.5')\n" | |
718 | "d.radix(_|_") | |
719 | filename = self.project_file("test.py", source) | |
720 | ||
721 | actual = self.backend.rpc_get_calltip(filename, | |
722 | source, | |
723 | offset) | |
724 | self.assertEqual(self.RADIX_CALLTIP, actual) | |
725 | actual = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
726 | source, | |
727 | offset) | |
728 | actual.pop('kind') | |
729 | self.assertEqual(self.RADIX_CALLTIP, actual) | |
730 | ||
731 | def test_should_return_none_outside_of_all(self): | |
732 | filename = self.project_file("test.py", "") | |
733 | source, offset = source_and_offset("import thr_|_eading\n") | |
734 | calltip = self.backend.rpc_get_calltip(filename, | |
735 | source, offset) | |
736 | self.assertIsNone(calltip) | |
737 | calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
738 | source, | |
739 | offset) | |
740 | self.assertIsNotNone(calltip) | |
741 | ||
742 | def test_should_find_calltip_different_package(self): | |
743 | # See issue #74 | |
744 | self.project_file("project/__init__.py", "") | |
745 | source1 = ("class Add:\n" | |
746 | " def add(self, a, b):\n" | |
747 | " return a + b\n") | |
748 | self.project_file("project/add.py", source1) | |
749 | source2, offset = source_and_offset( | |
750 | "from project.add import Add\n" | |
751 | "class Calculator:\n" | |
752 | " def add(self, a, b):\n" | |
753 | " c = Add()\n" | |
754 | " c.add(_|_\n") | |
755 | file2 = self.project_file("project/calculator.py", source2) | |
756 | ||
757 | actual = self.backend.rpc_get_calltip(file2, | |
758 | source2, | |
759 | offset) | |
760 | self.assertEqual(self.ADD_CALLTIP, actual) | |
761 | actual = self.backend.rpc_get_calltip_or_oneline_docstring(file2, | |
762 | source2, | |
763 | offset) | |
764 | actual.pop('kind') | |
765 | self.assertEqual(self.ADD_CALLTIP, actual) | |
766 | ||
767 | def test_should_return_oneline_docstring_if_no_calltip(self): | |
768 | source, offset = source_and_offset( | |
769 | "def foo(a, b):\n fo_|_o(1 + 2)") | |
770 | filename = self.project_file("test.py", source) | |
771 | calltip = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
772 | source, | |
773 | offset) | |
774 | self.assertEqual(calltip['kind'], 'oneline_doc') | |
775 | self.assertEqual(calltip['doc'], 'No documentation') | |
776 | ||
777 | ||
778 | class RPCGetDocstringTests(GenericRPCTests): | |
779 | METHOD = "rpc_get_docstring" | |
780 | ||
781 | def check_docstring(self, docstring): | |
782 | ||
783 | def first_line(s): | |
784 | return s[:s.index("\n")] | |
785 | match = re.match(self.JSON_LOADS_REGEX, | |
786 | first_line(docstring)) | |
787 | self.assertIsNotNone(match) | |
788 | ||
789 | def test_should_get_docstring(self): | |
790 | source, offset = source_and_offset( | |
791 | "import json\njson.loads_|_(") | |
792 | filename = self.project_file("test.py", source) | |
793 | docstring = self.backend.rpc_get_docstring(filename, | |
794 | source, | |
795 | offset) | |
796 | self.check_docstring(docstring) | |
797 | ||
798 | def test_should_return_none_for_bad_identifier(self): | |
799 | source, offset = source_and_offset( | |
800 | "froblgoo_|_(\n") | |
801 | filename = self.project_file("test.py", source) | |
802 | docstring = self.backend.rpc_get_docstring(filename, | |
803 | source, | |
804 | offset) | |
805 | self.assertIsNone(docstring) | |
806 | ||
807 | ||
808 | class RPCGetOnelineDocstringTests(GenericRPCTests): | |
809 | METHOD = "rpc_get_oneline_docstring" | |
810 | ||
811 | def check_docstring(self, docstring): | |
812 | ||
813 | self.assertEqual(docstring['doc'], | |
814 | self.JSON_LOADS_DOCSTRING) | |
815 | ||
816 | def check_module_docstring(self, docstring): | |
817 | ||
818 | self.assertEqual(docstring['doc'], | |
819 | self.JSON_DOCSTRING) | |
820 | ||
821 | def test_should_get_oneline_docstring(self): | |
822 | source, offset = source_and_offset( | |
823 | "import json\njson.loads_|_(") | |
824 | filename = self.project_file("test.py", source) | |
825 | docstring = self.backend.rpc_get_oneline_docstring(filename, | |
826 | source, | |
827 | offset) | |
828 | self.check_docstring(docstring) | |
829 | docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
830 | source, | |
831 | offset) | |
832 | docstring.pop('kind') | |
833 | self.check_docstring(docstring) | |
834 | ||
835 | def test_should_get_oneline_docstring_for_modules(self): | |
836 | source, offset = source_and_offset( | |
837 | "import json_|_\njson.loads(") | |
838 | filename = self.project_file("test.py", source) | |
839 | docstring = self.backend.rpc_get_oneline_docstring(filename, | |
840 | source, | |
841 | offset) | |
842 | self.check_module_docstring(docstring) | |
843 | docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
844 | source, | |
845 | offset) | |
846 | docstring.pop('kind') | |
847 | self.check_module_docstring(docstring) | |
848 | ||
849 | def test_should_return_none_for_bad_identifier(self): | |
850 | source, offset = source_and_offset( | |
851 | "froblgoo_|_(\n") | |
852 | filename = self.project_file("test.py", source) | |
853 | docstring = self.backend.rpc_get_oneline_docstring(filename, | |
854 | source, | |
855 | offset) | |
856 | self.assertIsNone(docstring) | |
857 | docstring = self.backend.rpc_get_calltip_or_oneline_docstring(filename, | |
858 | source, | |
859 | offset) | |
860 | self.assertIsNone(docstring) | |
861 | ||
862 | ||
863 | @unittest.skipIf(not jedibackend.JEDISUP17, | |
864 | "Refactoring not available with jedi<17") | |
865 | @unittest.skipIf(sys.version_info < (3, 6), | |
866 | "Jedi refactoring not available for python < 3.6") | |
867 | class RPCGetRenameDiffTests(object): | |
868 | METHOD = "rpc_get_rename_diff" | |
869 | ||
870 | def test_should_return_rename_diff(self): | |
871 | source, offset = source_and_offset("def foo(a, b):\n" | |
872 | " print(a_|_)\n" | |
873 | " return b") | |
874 | new_name = "c" | |
875 | diff = self.backend.rpc_get_rename_diff("test.py", source, offset, | |
876 | new_name) | |
877 | assert diff['success'] | |
878 | self.assertIn("-def foo(a, b):\n" | |
879 | "- print(a)\n" | |
880 | "+def foo(c, b):\n" | |
881 | "+ print(c)", | |
882 | diff['diff']) | |
883 | ||
884 | def test_should_fail_for_invalid_symbol_at_point(self): | |
885 | source, offset = source_and_offset("def foo(a, b):\n" | |
886 | " print(12_|_)\n" | |
887 | " return b") | |
888 | new_name = "c" | |
889 | diff = self.backend.rpc_get_rename_diff("test.py", source, offset, | |
890 | new_name) | |
891 | self.assertFalse(diff['success']) | |
892 | ||
893 | ||
894 | @unittest.skipIf(not jedibackend.JEDISUP17, | |
895 | "Refactoring not available with jedi<17") | |
896 | @unittest.skipIf(sys.version_info < (3, 6), | |
897 | "Jedi refactoring not available for python < 3.6") | |
898 | class RPCGetExtractFunctionDiffTests(object): | |
899 | METHOD = "rpc_get_extract_function_diff" | |
900 | ||
901 | def test_should_return_function_extraction_diff(self): | |
902 | source, offset = source_and_offset("print(a)\n" | |
903 | "return b_|_\n") | |
904 | new_name = "foo" | |
905 | diff = self.backend.rpc_get_extract_function_diff( | |
906 | "test.py", source, offset, | |
907 | new_name, | |
908 | line_beg=1, line_end=2, | |
909 | col_beg=0, col_end=8) | |
910 | assert diff['success'] | |
911 | self.assertIn('-print(a)\n' | |
912 | '-return b\n' | |
913 | '+def foo(a, b):\n' | |
914 | '+ print(a)\n' | |
915 | '+ return b\n', | |
916 | diff['diff']) | |
917 | ||
918 | ||
919 | @unittest.skipIf(not jedibackend.JEDISUP17, | |
920 | "Refactoring not available with jedi<17") | |
921 | @unittest.skipIf(sys.version_info < (3, 6), | |
922 | "Jedi refactoring not available for python < 3.6") | |
923 | class RPCGetExtractVariableDiffTests(object): | |
924 | METHOD = "rpc_get_extract_variable_diff" | |
925 | ||
926 | def test_should_return_variable_extraction_diff(self): | |
927 | source, offset = source_and_offset("b = 12\n" | |
928 | "a = 2\n" | |
929 | "print_|_(a + 1 + b/2)\n") | |
930 | new_name = "c" | |
931 | diff = self.backend.rpc_get_extract_variable_diff( | |
932 | "test.py", source, offset, | |
933 | new_name, | |
934 | line_beg=3, line_end=3, | |
935 | col_beg=7, col_end=16) | |
936 | assert diff['success'] | |
937 | self.assertIn("-print(a + 1 + b/2)\n+c = a + 1 + b/2\n+print(c)\n", | |
938 | diff['diff']) | |
939 | ||
940 | ||
941 | @unittest.skipIf(not jedibackend.JEDISUP17, | |
942 | "Refactoring not available with jedi<17") | |
943 | @unittest.skipIf(sys.version_info < (3, 6), | |
944 | "Jedi refactoring not available for python < 3.6") | |
945 | class RPCGetInlineDiffTests(object): | |
946 | METHOD = "rpc_get_inline_diff" | |
947 | ||
948 | def test_should_return_inline_diff(self): | |
949 | source, offset = source_and_offset("foo = 3.1\n" | |
950 | "bar = foo + 1\n" | |
951 | "x = int(ba_|_r)\n") | |
952 | diff = self.backend.rpc_get_inline_diff("test.py", source, | |
953 | offset) | |
954 | assert diff['success'] | |
955 | self.assertIn("-bar = foo + 1\n-x = int(bar)\n+x = int(foo + 1)", | |
956 | diff['diff']) | |
957 | ||
958 | def test_should_error_on_refactoring_failure(self): | |
959 | source, offset = source_and_offset("foo = 3.1\n" | |
960 | "bar = foo + 1\n" | |
961 | "x = in_|_t(bar)\n") | |
962 | diff = self.backend.rpc_get_inline_diff("test.py", source, | |
963 | offset) | |
964 | self.assertFalse(diff['success']) | |
965 | ||
966 | ||
967 | class RPCGetNamesTests(GenericRPCTests): | |
968 | METHOD = "rpc_get_names" | |
969 | ||
970 | def test_shouldreturn_names_in_same_file(self): | |
971 | filename = self.project_file("test.py", "") | |
972 | source, offset = source_and_offset( | |
973 | "def foo(x, y):\n" | |
974 | " return x + y\n" | |
975 | "c = _|_foo(5, 2)\n") | |
976 | ||
977 | names = self.backend.rpc_get_names(filename, | |
978 | source, | |
979 | offset) | |
980 | ||
981 | self.assertEqual(names, | |
982 | [{'name': 'foo', | |
983 | 'filename': filename, | |
984 | 'offset': 4}, | |
985 | {'name': 'x', | |
986 | 'filename': filename, | |
987 | 'offset': 8}, | |
988 | {'name': 'y', | |
989 | 'filename': filename, | |
990 | 'offset': 11}, | |
991 | {'name': 'x', | |
992 | 'filename': filename, | |
993 | 'offset': 26}, | |
994 | {'name': 'y', | |
995 | 'filename': filename, | |
996 | 'offset': 30}, | |
997 | {'name': 'c', | |
998 | 'filename': filename, | |
999 | 'offset': 32}, | |
1000 | {'name': 'foo', | |
1001 | 'filename': filename, | |
1002 | 'offset': 36}]) | |
1003 | ||
1004 | def test_should_not_fail_without_symbol(self): | |
1005 | filename = self.project_file("test.py", "") | |
1006 | ||
1007 | names = self.backend.rpc_get_names(filename, | |
1008 | "", | |
1009 | 0) | |
1010 | ||
1011 | self.assertEqual(names, []) | |
1012 | ||
1013 | ||
1014 | class RPCGetUsagesTests(GenericRPCTests): | |
1015 | METHOD = "rpc_get_usages" | |
1016 | ||
1017 | def test_should_return_uses_in_same_file(self): | |
1018 | filename = self.project_file("test.py", "") | |
1019 | source, offset = source_and_offset( | |
1020 | "def foo(x):\n" | |
1021 | " return _|_x + x\n") | |
1022 | ||
1023 | usages = self.backend.rpc_get_usages(filename, | |
1024 | source, | |
1025 | offset) | |
1026 | ||
1027 | self.assertEqual(usages, | |
1028 | [{'name': 'x', | |
1029 | 'offset': 8, | |
1030 | 'filename': filename}, | |
1031 | {'name': 'x', | |
1032 | 'filename': filename, | |
1033 | 'offset': 23}, | |
1034 | {'name': u'x', | |
1035 | 'filename': filename, | |
1036 | 'offset': 27}]) | |
1037 | ||
1038 | def test_should_return_uses_in_other_file(self): | |
1039 | file1 = self.project_file("file1.py", "") | |
1040 | file2 = self.project_file("file2.py", "\n\n\n\n\nx = 5") | |
1041 | source, offset = source_and_offset( | |
1042 | "import file2\n" | |
1043 | "file2._|_x\n") | |
1044 | ||
1045 | usages = self.backend.rpc_get_usages(file1, | |
1046 | source, | |
1047 | offset) | |
1048 | ||
1049 | self.assertEqual(usages, | |
1050 | [{'name': 'x', | |
1051 | 'filename': file1, | |
1052 | 'offset': 19}, | |
1053 | {'name': 'x', | |
1054 | 'filename': file2, | |
1055 | 'offset': 5}]) | |
1056 | ||
1057 | def test_should_not_fail_without_symbol(self): | |
1058 | filename = self.project_file("file.py", "") | |
1059 | ||
1060 | usages = self.backend.rpc_get_usages(filename, | |
1061 | "", | |
1062 | 0) | |
1063 | ||
1064 | self.assertEqual(usages, []) | |
1065 | ||
1066 | ||
1067 | def source_and_offset(source): | |
1068 | """Return a source and offset from a source description. | |
1069 | ||
1070 | >>> source_and_offset("hello, _|_world") | |
1071 | ("hello, world", 7) | |
1072 | >>> source_and_offset("_|_hello, world") | |
1073 | ("hello, world", 0) | |
1074 | >>> source_and_offset("hello, world_|_") | |
1075 | ("hello, world", 12) | |
1076 | """ | |
1077 | offset = source.index("_|_") | |
1078 | return source[:offset] + source[offset + 3:], offset |