Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1143 lines
44 KiB

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