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