1 "Test calltip, coverage 76%"
2
3 from idlelib import calltip
4 import unittest
5 from unittest.mock import Mock
6 import textwrap
7 import types
8 import re
9 from idlelib.idle_test.mock_tk import Text
10
11
12 # Test Class TC is used in multiple get_argspec test methods
13 class ESC[4;38;5;81mTC:
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'
19 t1.tip = "(self)"
20 def t2(self, ai, b=None): 'doc'
21 t2.tip = "(self, ai, b=None)"
22 def t3(self, ai, *args): 'doc'
23 t3.tip = "(self, ai, *args)"
24 def t4(self, *args): 'doc'
25 t4.tip = "(self, *args)"
26 def t5(self, ai, b=None, *args, **kw): 'doc'
27 t5.tip = "(self, ai, b=None, *args, **kw)"
28 def t6(no, self): 'doc'
29 t6.tip = "(no, self)"
30 def __call__(self, ci): 'doc'
31 __call__.tip = "(self, ci)"
32 def nd(self): pass # No doc.
33 # attaching .tip to wrapped methods does not work
34 @classmethod
35 def cm(cls, a): 'doc'
36 @staticmethod
37 def sm(b): 'doc'
38
39
40 tc = TC()
41 default_tip = calltip._default_callable_argspec
42 get_spec = calltip.get_argspec
43
44
45 class ESC[4;38;5;81mGet_argspecTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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.
52
53 def test_builtins(self):
54
55 def tiptest(obj, out):
56 self.assertEqual(get_spec(obj), out)
57
58 # Python class that inherits builtin methods
59 class ESC[4;38;5;81mList(ESC[4;38;5;149mlist): "List() doc"
60
61 # Simulate builtin with no docstring for default tip test
62 class ESC[4;38;5;81mSB: __call__ = None
63
64 if List.__doc__ is not None:
65 tiptest(List,
66 f'(iterable=(), /)'
67 f'\n{List.__doc__}')
68 tiptest(list.__new__,
69 '(*args, **kwargs)\n'
70 'Create and return a new object. '
71 'See help(type) for accurate signature.')
72 tiptest(list.__init__,
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."
76 tiptest(list.append, '(self, object, /)' + append_doc)
77 tiptest(List.append, '(self, object, /)' + append_doc)
78 tiptest([].append, '(object, /)' + append_doc)
79
80 tiptest(types.MethodType,
81 '(function, instance, /)\n'
82 'Create a bound instance method object.')
83 tiptest(SB(), default_tip)
84
85 p = re.compile('')
86 tiptest(re.sub, '''\
87 (pattern, repl, string, count=0, flags=0)
88 Return the string obtained by replacing the leftmost
89 non-overlapping occurrences of the pattern in string by the
90 replacement repl. repl can be either a string or a callable;
91 if a string, backslash escapes in it are processed. If it is
92 a callable, it's passed the Match object and must return''')
93 tiptest(p.sub, '''\
94 (repl, string, count=0)
95 Return the string obtained by replacing the leftmost \
96 non-overlapping occurrences o...''')
97
98 def test_signature_wrap(self):
99 if textwrap.TextWrapper.__doc__ is not None:
100 self.assertEqual(get_spec(textwrap.TextWrapper), '''\
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=' [...]')
105 Object for wrapping/filling text. The public interface consists of
106 the wrap() and fill() methods; the other methods are just there for
107 subclasses to override in order to tweak the default behaviour.
108 If you want to completely replace the main wrapping algorithm,
109 you\'ll probably have to override _wrap_chunks().''')
110
111 def test_properly_formatted(self):
112
113 def foo(s='a'*100):
114 pass
115
116 def bar(s='a'*100):
117 """Hello Guido"""
118 pass
119
120 def baz(s='a'*100, z='b'*100):
121 pass
122
123 indent = calltip._INDENT
124
125 sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
126 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
127 "aaaaaaaaaa')"
128 sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
129 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
130 "aaaaaaaaaa')\nHello Guido"
131 sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
132 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
133 "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
134 "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
135 "bbbbbbbbbbbbbbbbbbbbbb')"
136
137 for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
138 with self.subTest(func=func, doc=doc):
139 self.assertEqual(get_spec(func), doc)
140
141 def test_docline_truncation(self):
142 def f(): pass
143 f.__doc__ = 'a'*300
144 self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
145
146 def test_multiline_docstring(self):
147 # Test fewer lines than max.
148 self.assertEqual(get_spec(range),
149 "range(stop) -> range object\n"
150 "range(start, stop[, step]) -> range object")
151
152 # Test max lines
153 self.assertEqual(get_spec(bytes), '''\
154 bytes(iterable_of_ints) -> bytes
155 bytes(string, encoding[, errors]) -> bytes
156 bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
157 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
158 bytes() -> empty bytes object''')
159
160 # Test more than max lines
161 def f(): pass
162 f.__doc__ = 'a\n' * 15
163 self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
164
165 def test_functions(self):
166 def t1(): 'doc'
167 t1.tip = "()"
168 def t2(a, b=None): 'doc'
169 t2.tip = "(a, b=None)"
170 def t3(a, *args): 'doc'
171 t3.tip = "(a, *args)"
172 def t4(*args): 'doc'
173 t4.tip = "(*args)"
174 def t5(a, b=None, *args, **kw): 'doc'
175 t5.tip = "(a, b=None, *args, **kw)"
176
177 doc = '\ndoc' if t1.__doc__ is not None else ''
178 for func in (t1, t2, t3, t4, t5, TC):
179 with self.subTest(func=func):
180 self.assertEqual(get_spec(func), func.tip + doc)
181
182 def test_methods(self):
183 doc = '\ndoc' if TC.__doc__ is not None else ''
184 for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
185 with self.subTest(meth=meth):
186 self.assertEqual(get_spec(meth), meth.tip + doc)
187 self.assertEqual(get_spec(TC.cm), "(a)" + doc)
188 self.assertEqual(get_spec(TC.sm), "(b)" + doc)
189
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 ''
193 for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"),
194 (tc.t6, "(self)"), (tc.__call__, '(ci)'),
195 (tc, '(ci)'), (TC.cm, "(a)"),):
196 with self.subTest(meth=meth, mtip=mtip):
197 self.assertEqual(get_spec(meth), mtip + doc)
198
199 def test_starred_parameter(self):
200 # test that starred first parameter is *not* removed from argspec
201 class ESC[4;38;5;81mC:
202 def m1(*args): pass
203 c = C()
204 for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
205 with self.subTest(meth=meth, mtip=mtip):
206 self.assertEqual(get_spec(meth), mtip)
207
208 def test_invalid_method_get_spec(self):
209 class ESC[4;38;5;81mC:
210 def m2(**kwargs): pass
211 class ESC[4;38;5;81mTest:
212 def __call__(*, a): pass
213
214 mtip = calltip._invalid_method
215 self.assertEqual(get_spec(C().m2), mtip)
216 self.assertEqual(get_spec(Test()), mtip)
217
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)"
222 assert calltip._first_param.sub('', uni) == '(a)'
223
224 def test_no_docstring(self):
225 for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")):
226 with self.subTest(meth=meth, mtip=mtip):
227 self.assertEqual(get_spec(meth), mtip)
228
229 def test_buggy_getattr_class(self):
230 class ESC[4;38;5;81mNoCall:
231 def __getattr__(self, name): # Not invoked for class attribute.
232 raise IndexError # Bug.
233 class ESC[4;38;5;81mCallA(ESC[4;38;5;149mNoCall):
234 def __call__(self, ci): # Bug does not matter.
235 pass
236 class ESC[4;38;5;81mCallB(ESC[4;38;5;149mNoCall):
237 def __call__(oui, a, b, c): # Non-standard 'self'.
238 pass
239
240 for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
241 (NoCall(), ''), (CallA(), '(ci)'),
242 (CallB(), '(a, b, c)')):
243 with self.subTest(meth=meth, mtip=mtip):
244 self.assertEqual(get_spec(meth), mtip)
245
246 def test_metaclass_class(self): # Failure case for issue 38689.
247 class ESC[4;38;5;81mType(ESC[4;38;5;149mtype): # Type() requires 3 type args, returns class.
248 __class__ = property({}.__getitem__, {}.__setitem__)
249 class ESC[4;38;5;81mObject(metaclass=ESC[4;38;5;149mType):
250 __slots__ = '__class__'
251 for meth, mtip in ((Type, get_spec(type)), (Object, default_tip),
252 (Object(), '')):
253 with self.subTest(meth=meth, mtip=mtip):
254 self.assertEqual(get_spec(meth), mtip)
255
256 def test_non_callables(self):
257 for obj in (0, 0.0, '0', b'0', [], {}):
258 with self.subTest(obj=obj):
259 self.assertEqual(get_spec(obj), '')
260
261
262 class ESC[4;38;5;81mGet_entityTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
263 def test_bad_entity(self):
264 self.assertIsNone(calltip.get_entity('1/0'))
265 def test_good_entity(self):
266 self.assertIs(calltip.get_entity('int'), int)
267
268
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.
272
273 class ESC[4;38;5;81mmock_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
281
282
283 class ESC[4;38;5;81mmock_TipWindow:
284 def __init__(self):
285 pass
286
287 def showtip(self, text, parenleft, parenright):
288 self.args = parenleft, parenright
289 self.parenline, self.parencol = map(int, parenleft.split('.'))
290
291
292 class ESC[4;38;5;81mWrappedCalltip(ESC[4;38;5;149mcalltipESC[4;38;5;149m.ESC[4;38;5;149mCalltip):
293 def _make_tk_calltip_window(self):
294 return mock_TipWindow()
295
296 def remove_calltip_window(self, event=None):
297 if self.active_calltip: # Setup to None.
298 self.active_calltip = None
299 self.tips_removed += 1 # Setup to 0.
300
301 def fetch_tip(self, expression):
302 return 'tip'
303
304
305 class ESC[4;38;5;81mCalltipTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
306
307 @classmethod
308 def setUpClass(cls):
309 cls.text = Text()
310 cls.ct = WrappedCalltip(mock_Shell(cls.text))
311
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
317
318 def open_close(self, testfunc):
319 # Open-close template with testfunc called in between.
320 opentip = self.ct.open_calltip
321 self.text.insert(1.0, 'f(')
322 opentip(False)
323 self.tip = self.ct.active_calltip
324 testfunc(self) ###
325 self.text.insert('insert', ')')
326 opentip(False)
327 self.assertIsNone(self.ct.active_calltip, None)
328
329 def test_open_close(self):
330 def args(self):
331 self.assertEqual(self.tip.args, ('1.1', '1.end'))
332 self.open_close(args)
333
334 def test_repeated_force(self):
335 def force(self):
336 for char in 'abc':
337 self.text.insert('insert', 'a')
338 self.ct.open_calltip(True)
339 self.ct.open_calltip(True)
340 self.assertIs(self.ct.active_calltip, self.tip)
341 self.open_close(force)
342
343 def test_repeated_parens(self):
344 def parens(self):
345 for context in "a", "'":
346 with self.subTest(context=context):
347 self.text.insert('insert', context)
348 for char in '(()())':
349 self.text.insert('insert', char)
350 self.assertIs(self.ct.active_calltip, self.tip)
351 self.text.insert('insert', "'")
352 self.open_close(parens)
353
354 def test_comment_parens(self):
355 def comment(self):
356 self.text.insert('insert', "# ")
357 for char in '(()())':
358 self.text.insert('insert', char)
359 self.assertIs(self.ct.active_calltip, self.tip)
360 self.text.insert('insert', "\n")
361 self.open_close(comment)
362
363
364 if __name__ == '__main__':
365 unittest.main(verbosity=2)