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.

369 lines
14 KiB

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