test_calltip.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. "Test calltip, coverage 76%"
  2. from idlelib import calltip
  3. import unittest
  4. from unittest.mock import Mock
  5. import textwrap
  6. import types
  7. import re
  8. from idlelib.idle_test.mock_tk import Text
  9. # Test Class TC is used in multiple get_argspec test methods
  10. class TC:
  11. 'doc'
  12. tip = "(ai=None, *b)"
  13. def __init__(self, ai=None, *b): 'doc'
  14. __init__.tip = "(self, ai=None, *b)"
  15. def t1(self): 'doc'
  16. t1.tip = "(self)"
  17. def t2(self, ai, b=None): 'doc'
  18. t2.tip = "(self, ai, b=None)"
  19. def t3(self, ai, *args): 'doc'
  20. t3.tip = "(self, ai, *args)"
  21. def t4(self, *args): 'doc'
  22. t4.tip = "(self, *args)"
  23. def t5(self, ai, b=None, *args, **kw): 'doc'
  24. t5.tip = "(self, ai, b=None, *args, **kw)"
  25. def t6(no, self): 'doc'
  26. t6.tip = "(no, self)"
  27. def __call__(self, ci): 'doc'
  28. __call__.tip = "(self, ci)"
  29. def nd(self): pass # No doc.
  30. # attaching .tip to wrapped methods does not work
  31. @classmethod
  32. def cm(cls, a): 'doc'
  33. @staticmethod
  34. def sm(b): 'doc'
  35. tc = TC()
  36. default_tip = calltip._default_callable_argspec
  37. get_spec = calltip.get_argspec
  38. class Get_argspecTest(unittest.TestCase):
  39. # The get_spec function must return a string, even if blank.
  40. # Test a variety of objects to be sure that none cause it to raise
  41. # (quite aside from getting as correct an answer as possible).
  42. # The tests of builtins may break if inspect or the docstrings change,
  43. # but a red buildbot is better than a user crash (as has happened).
  44. # For a simple mismatch, change the expected output to the actual.
  45. def test_builtins(self):
  46. def tiptest(obj, out):
  47. self.assertEqual(get_spec(obj), out)
  48. # Python class that inherits builtin methods
  49. class List(list): "List() doc"
  50. # Simulate builtin with no docstring for default tip test
  51. class SB: __call__ = None
  52. if List.__doc__ is not None:
  53. tiptest(List,
  54. f'(iterable=(), /)'
  55. f'\n{List.__doc__}')
  56. tiptest(list.__new__,
  57. '(*args, **kwargs)\n'
  58. 'Create and return a new object. '
  59. 'See help(type) for accurate signature.')
  60. tiptest(list.__init__,
  61. '(self, /, *args, **kwargs)\n'
  62. 'Initialize self. See help(type(self)) for accurate signature.')
  63. append_doc = "\nAppend object to the end of the list."
  64. tiptest(list.append, '(self, object, /)' + append_doc)
  65. tiptest(List.append, '(self, object, /)' + append_doc)
  66. tiptest([].append, '(object, /)' + append_doc)
  67. tiptest(types.MethodType,
  68. '(function, instance, /)\n'
  69. 'Create a bound instance method object.')
  70. tiptest(SB(), default_tip)
  71. p = re.compile('')
  72. tiptest(re.sub, '''\
  73. (pattern, repl, string, count=0, flags=0)
  74. Return the string obtained by replacing the leftmost
  75. non-overlapping occurrences of the pattern in string by the
  76. replacement repl. repl can be either a string or a callable;
  77. if a string, backslash escapes in it are processed. If it is
  78. a callable, it's passed the Match object and must return''')
  79. tiptest(p.sub, '''\
  80. (repl, string, count=0)
  81. Return the string obtained by replacing the leftmost \
  82. non-overlapping occurrences o...''')
  83. def test_signature_wrap(self):
  84. if textwrap.TextWrapper.__doc__ is not None:
  85. self.assertEqual(get_spec(textwrap.TextWrapper), '''\
  86. (width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
  87. replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
  88. drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
  89. placeholder=' [...]')
  90. Object for wrapping/filling text. The public interface consists of
  91. the wrap() and fill() methods; the other methods are just there for
  92. subclasses to override in order to tweak the default behaviour.
  93. If you want to completely replace the main wrapping algorithm,
  94. you\'ll probably have to override _wrap_chunks().''')
  95. def test_properly_formatted(self):
  96. def foo(s='a'*100):
  97. pass
  98. def bar(s='a'*100):
  99. """Hello Guido"""
  100. pass
  101. def baz(s='a'*100, z='b'*100):
  102. pass
  103. indent = calltip._INDENT
  104. sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
  105. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
  106. "aaaaaaaaaa')"
  107. sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
  108. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
  109. "aaaaaaaaaa')\nHello Guido"
  110. sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
  111. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
  112. "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
  113. "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
  114. "bbbbbbbbbbbbbbbbbbbbbb')"
  115. for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
  116. with self.subTest(func=func, doc=doc):
  117. self.assertEqual(get_spec(func), doc)
  118. def test_docline_truncation(self):
  119. def f(): pass
  120. f.__doc__ = 'a'*300
  121. self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
  122. def test_multiline_docstring(self):
  123. # Test fewer lines than max.
  124. self.assertEqual(get_spec(range),
  125. "range(stop) -> range object\n"
  126. "range(start, stop[, step]) -> range object")
  127. # Test max lines
  128. self.assertEqual(get_spec(bytes), '''\
  129. bytes(iterable_of_ints) -> bytes
  130. bytes(string, encoding[, errors]) -> bytes
  131. bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
  132. bytes(int) -> bytes object of size given by the parameter initialized with null bytes
  133. bytes() -> empty bytes object''')
  134. # Test more than max lines
  135. def f(): pass
  136. f.__doc__ = 'a\n' * 15
  137. self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
  138. def test_functions(self):
  139. def t1(): 'doc'
  140. t1.tip = "()"
  141. def t2(a, b=None): 'doc'
  142. t2.tip = "(a, b=None)"
  143. def t3(a, *args): 'doc'
  144. t3.tip = "(a, *args)"
  145. def t4(*args): 'doc'
  146. t4.tip = "(*args)"
  147. def t5(a, b=None, *args, **kw): 'doc'
  148. t5.tip = "(a, b=None, *args, **kw)"
  149. doc = '\ndoc' if t1.__doc__ is not None else ''
  150. for func in (t1, t2, t3, t4, t5, TC):
  151. with self.subTest(func=func):
  152. self.assertEqual(get_spec(func), func.tip + doc)
  153. def test_methods(self):
  154. doc = '\ndoc' if TC.__doc__ is not None else ''
  155. for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
  156. with self.subTest(meth=meth):
  157. self.assertEqual(get_spec(meth), meth.tip + doc)
  158. self.assertEqual(get_spec(TC.cm), "(a)" + doc)
  159. self.assertEqual(get_spec(TC.sm), "(b)" + doc)
  160. def test_bound_methods(self):
  161. # test that first parameter is correctly removed from argspec
  162. doc = '\ndoc' if TC.__doc__ is not None else ''
  163. for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"),
  164. (tc.t6, "(self)"), (tc.__call__, '(ci)'),
  165. (tc, '(ci)'), (TC.cm, "(a)"),):
  166. with self.subTest(meth=meth, mtip=mtip):
  167. self.assertEqual(get_spec(meth), mtip + doc)
  168. def test_starred_parameter(self):
  169. # test that starred first parameter is *not* removed from argspec
  170. class C:
  171. def m1(*args): pass
  172. c = C()
  173. for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
  174. with self.subTest(meth=meth, mtip=mtip):
  175. self.assertEqual(get_spec(meth), mtip)
  176. def test_invalid_method_get_spec(self):
  177. class C:
  178. def m2(**kwargs): pass
  179. class Test:
  180. def __call__(*, a): pass
  181. mtip = calltip._invalid_method
  182. self.assertEqual(get_spec(C().m2), mtip)
  183. self.assertEqual(get_spec(Test()), mtip)
  184. def test_non_ascii_name(self):
  185. # test that re works to delete a first parameter name that
  186. # includes non-ascii chars, such as various forms of A.
  187. uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
  188. assert calltip._first_param.sub('', uni) == '(a)'
  189. def test_no_docstring(self):
  190. for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")):
  191. with self.subTest(meth=meth, mtip=mtip):
  192. self.assertEqual(get_spec(meth), mtip)
  193. def test_buggy_getattr_class(self):
  194. class NoCall:
  195. def __getattr__(self, name): # Not invoked for class attribute.
  196. raise IndexError # Bug.
  197. class CallA(NoCall):
  198. def __call__(self, ci): # Bug does not matter.
  199. pass
  200. class CallB(NoCall):
  201. def __call__(oui, a, b, c): # Non-standard 'self'.
  202. pass
  203. for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
  204. (NoCall(), ''), (CallA(), '(ci)'),
  205. (CallB(), '(a, b, c)')):
  206. with self.subTest(meth=meth, mtip=mtip):
  207. self.assertEqual(get_spec(meth), mtip)
  208. def test_metaclass_class(self): # Failure case for issue 38689.
  209. class Type(type): # Type() requires 3 type args, returns class.
  210. __class__ = property({}.__getitem__, {}.__setitem__)
  211. class Object(metaclass=Type):
  212. __slots__ = '__class__'
  213. for meth, mtip in ((Type, get_spec(type)), (Object, default_tip),
  214. (Object(), '')):
  215. with self.subTest(meth=meth, mtip=mtip):
  216. self.assertEqual(get_spec(meth), mtip)
  217. def test_non_callables(self):
  218. for obj in (0, 0.0, '0', b'0', [], {}):
  219. with self.subTest(obj=obj):
  220. self.assertEqual(get_spec(obj), '')
  221. class Get_entityTest(unittest.TestCase):
  222. def test_bad_entity(self):
  223. self.assertIsNone(calltip.get_entity('1/0'))
  224. def test_good_entity(self):
  225. self.assertIs(calltip.get_entity('int'), int)
  226. # Test the 9 Calltip methods.
  227. # open_calltip is about half the code; the others are fairly trivial.
  228. # The default mocks are what are needed for open_calltip.
  229. class mock_Shell:
  230. "Return mock sufficient to pass to hyperparser."
  231. def __init__(self, text):
  232. text.tag_prevrange = Mock(return_value=None)
  233. self.text = text
  234. self.prompt_last_line = ">>> "
  235. self.indentwidth = 4
  236. self.tabwidth = 8
  237. class mock_TipWindow:
  238. def __init__(self):
  239. pass
  240. def showtip(self, text, parenleft, parenright):
  241. self.args = parenleft, parenright
  242. self.parenline, self.parencol = map(int, parenleft.split('.'))
  243. class WrappedCalltip(calltip.Calltip):
  244. def _make_tk_calltip_window(self):
  245. return mock_TipWindow()
  246. def remove_calltip_window(self, event=None):
  247. if self.active_calltip: # Setup to None.
  248. self.active_calltip = None
  249. self.tips_removed += 1 # Setup to 0.
  250. def fetch_tip(self, expression):
  251. return 'tip'
  252. class CalltipTest(unittest.TestCase):
  253. @classmethod
  254. def setUpClass(cls):
  255. cls.text = Text()
  256. cls.ct = WrappedCalltip(mock_Shell(cls.text))
  257. def setUp(self):
  258. self.text.delete('1.0', 'end') # Insert and call
  259. self.ct.active_calltip = None
  260. # Test .active_calltip, +args
  261. self.ct.tips_removed = 0
  262. def open_close(self, testfunc):
  263. # Open-close template with testfunc called in between.
  264. opentip = self.ct.open_calltip
  265. self.text.insert(1.0, 'f(')
  266. opentip(False)
  267. self.tip = self.ct.active_calltip
  268. testfunc(self) ###
  269. self.text.insert('insert', ')')
  270. opentip(False)
  271. self.assertIsNone(self.ct.active_calltip, None)
  272. def test_open_close(self):
  273. def args(self):
  274. self.assertEqual(self.tip.args, ('1.1', '1.end'))
  275. self.open_close(args)
  276. def test_repeated_force(self):
  277. def force(self):
  278. for char in 'abc':
  279. self.text.insert('insert', 'a')
  280. self.ct.open_calltip(True)
  281. self.ct.open_calltip(True)
  282. self.assertIs(self.ct.active_calltip, self.tip)
  283. self.open_close(force)
  284. def test_repeated_parens(self):
  285. def parens(self):
  286. for context in "a", "'":
  287. with self.subTest(context=context):
  288. self.text.insert('insert', context)
  289. for char in '(()())':
  290. self.text.insert('insert', char)
  291. self.assertIs(self.ct.active_calltip, self.tip)
  292. self.text.insert('insert', "'")
  293. self.open_close(parens)
  294. def test_comment_parens(self):
  295. def comment(self):
  296. self.text.insert('insert', "# ")
  297. for char in '(()())':
  298. self.text.insert('insert', char)
  299. self.assertIs(self.ct.active_calltip, self.tip)
  300. self.text.insert('insert', "\n")
  301. self.open_close(comment)
  302. if __name__ == '__main__':
  303. unittest.main(verbosity=2)