1"Test calltip, coverage 76%"
2
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
10
11
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'
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
40tc = TC()
41default_tip = calltip._default_callable_argspec
42get_spec = calltip.get_argspec
43
44
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.
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 List(list): "List() doc"
60
61        # Simulate builtin with no docstring for default tip test
62        class SB:  __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, "method(function, instance)")
81        tiptest(SB(), default_tip)
82
83        p = re.compile('')
84        tiptest(re.sub, '''\
85(pattern, repl, string, count=0, flags=0)
86Return the string obtained by replacing the leftmost
87non-overlapping occurrences of the pattern in string by the
88replacement repl.  repl can be either a string or a callable;
89if a string, backslash escapes in it are processed.  If it is
90a callable, it's passed the Match object and must return''')
91        tiptest(p.sub, '''\
92(repl, string, count=0)
93Return the string obtained by replacing the leftmost \
94non-overlapping occurrences o...''')
95
96    def test_signature_wrap(self):
97        if textwrap.TextWrapper.__doc__ is not None:
98            self.assertEqual(get_spec(textwrap.TextWrapper), '''\
99(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
100    replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
101    drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
102    placeholder=' [...]')
103Object for wrapping/filling text.  The public interface consists of
104the wrap() and fill() methods; the other methods are just there for
105subclasses to override in order to tweak the default behaviour.
106If you want to completely replace the main wrapping algorithm,
107you\'ll probably have to override _wrap_chunks().''')
108
109    def test_properly_formated(self):
110
111        def foo(s='a'*100):
112            pass
113
114        def bar(s='a'*100):
115            """Hello Guido"""
116            pass
117
118        def baz(s='a'*100, z='b'*100):
119            pass
120
121        indent = calltip._INDENT
122
123        sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
124               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
125               "aaaaaaaaaa')"
126        sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
127               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
128               "aaaaaaaaaa')\nHello Guido"
129        sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
130               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
131               "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
132               "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
133               "bbbbbbbbbbbbbbbbbbbbbb')"
134
135        for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
136            with self.subTest(func=func, doc=doc):
137                self.assertEqual(get_spec(func), doc)
138
139    def test_docline_truncation(self):
140        def f(): pass
141        f.__doc__ = 'a'*300
142        self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
143
144    def test_multiline_docstring(self):
145        # Test fewer lines than max.
146        self.assertEqual(get_spec(range),
147                "range(stop) -> range object\n"
148                "range(start, stop[, step]) -> range object")
149
150        # Test max lines
151        self.assertEqual(get_spec(bytes), '''\
152bytes(iterable_of_ints) -> bytes
153bytes(string, encoding[, errors]) -> bytes
154bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
155bytes(int) -> bytes object of size given by the parameter initialized with null bytes
156bytes() -> empty bytes object''')
157
158        # Test more than max lines
159        def f(): pass
160        f.__doc__ = 'a\n' * 15
161        self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
162
163    def test_functions(self):
164        def t1(): 'doc'
165        t1.tip = "()"
166        def t2(a, b=None): 'doc'
167        t2.tip = "(a, b=None)"
168        def t3(a, *args): 'doc'
169        t3.tip = "(a, *args)"
170        def t4(*args): 'doc'
171        t4.tip = "(*args)"
172        def t5(a, b=None, *args, **kw): 'doc'
173        t5.tip = "(a, b=None, *args, **kw)"
174
175        doc = '\ndoc' if t1.__doc__ is not None else ''
176        for func in (t1, t2, t3, t4, t5, TC):
177            with self.subTest(func=func):
178                self.assertEqual(get_spec(func), func.tip + doc)
179
180    def test_methods(self):
181        doc = '\ndoc' if TC.__doc__ is not None else ''
182        for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
183            with self.subTest(meth=meth):
184                self.assertEqual(get_spec(meth), meth.tip + doc)
185        self.assertEqual(get_spec(TC.cm), "(a)" + doc)
186        self.assertEqual(get_spec(TC.sm), "(b)" + doc)
187
188    def test_bound_methods(self):
189        # test that first parameter is correctly removed from argspec
190        doc = '\ndoc' if TC.__doc__ is not None else ''
191        for meth, mtip  in ((tc.t1, "()"), (tc.t4, "(*args)"),
192                            (tc.t6, "(self)"), (tc.__call__, '(ci)'),
193                            (tc, '(ci)'), (TC.cm, "(a)"),):
194            with self.subTest(meth=meth, mtip=mtip):
195                self.assertEqual(get_spec(meth), mtip + doc)
196
197    def test_starred_parameter(self):
198        # test that starred first parameter is *not* removed from argspec
199        class C:
200            def m1(*args): pass
201        c = C()
202        for meth, mtip  in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
203            with self.subTest(meth=meth, mtip=mtip):
204                self.assertEqual(get_spec(meth), mtip)
205
206    def test_invalid_method_get_spec(self):
207        class C:
208            def m2(**kwargs): pass
209        class Test:
210            def __call__(*, a): pass
211
212        mtip = calltip._invalid_method
213        self.assertEqual(get_spec(C().m2), mtip)
214        self.assertEqual(get_spec(Test()), mtip)
215
216    def test_non_ascii_name(self):
217        # test that re works to delete a first parameter name that
218        # includes non-ascii chars, such as various forms of A.
219        uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
220        assert calltip._first_param.sub('', uni) == '(a)'
221
222    def test_no_docstring(self):
223        for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")):
224            with self.subTest(meth=meth, mtip=mtip):
225                self.assertEqual(get_spec(meth), mtip)
226
227    def test_buggy_getattr_class(self):
228        class NoCall:
229            def __getattr__(self, name):  # Not invoked for class attribute.
230                raise IndexError  # Bug.
231        class CallA(NoCall):
232            def __call__(self, ci):  # Bug does not matter.
233                pass
234        class CallB(NoCall):
235            def __call__(oui, a, b, c):  # Non-standard 'self'.
236                pass
237
238        for meth, mtip  in ((NoCall, default_tip), (CallA, default_tip),
239                            (NoCall(), ''), (CallA(), '(ci)'),
240                            (CallB(), '(a, b, c)')):
241            with self.subTest(meth=meth, mtip=mtip):
242                self.assertEqual(get_spec(meth), mtip)
243
244    def test_metaclass_class(self):  # Failure case for issue 38689.
245        class Type(type):  # Type() requires 3 type args, returns class.
246            __class__ = property({}.__getitem__, {}.__setitem__)
247        class Object(metaclass=Type):
248            __slots__ = '__class__'
249        for meth, mtip  in ((Type, get_spec(type)), (Object, default_tip),
250                            (Object(), '')):
251            with self.subTest(meth=meth, mtip=mtip):
252                self.assertEqual(get_spec(meth), mtip)
253
254    def test_non_callables(self):
255        for obj in (0, 0.0, '0', b'0', [], {}):
256            with self.subTest(obj=obj):
257                self.assertEqual(get_spec(obj), '')
258
259
260class Get_entityTest(unittest.TestCase):
261    def test_bad_entity(self):
262        self.assertIsNone(calltip.get_entity('1/0'))
263    def test_good_entity(self):
264        self.assertIs(calltip.get_entity('int'), int)
265
266
267# Test the 9 Calltip methods.
268# open_calltip is about half the code; the others are fairly trivial.
269# The default mocks are what are needed for open_calltip.
270
271class mock_Shell:
272    "Return mock sufficient to pass to hyperparser."
273    def __init__(self, text):
274        text.tag_prevrange = Mock(return_value=None)
275        self.text = text
276        self.prompt_last_line = ">>> "
277        self.indentwidth = 4
278        self.tabwidth = 8
279
280
281class mock_TipWindow:
282    def __init__(self):
283        pass
284
285    def showtip(self, text, parenleft, parenright):
286        self.args = parenleft, parenright
287        self.parenline, self.parencol = map(int, parenleft.split('.'))
288
289
290class WrappedCalltip(calltip.Calltip):
291    def _make_tk_calltip_window(self):
292        return mock_TipWindow()
293
294    def remove_calltip_window(self, event=None):
295        if self.active_calltip:  # Setup to None.
296            self.active_calltip = None
297            self.tips_removed += 1  # Setup to 0.
298
299    def fetch_tip(self, expression):
300        return 'tip'
301
302
303class CalltipTest(unittest.TestCase):
304
305    @classmethod
306    def setUpClass(cls):
307        cls.text = Text()
308        cls.ct = WrappedCalltip(mock_Shell(cls.text))
309
310    def setUp(self):
311        self.text.delete('1.0', 'end')  # Insert and call
312        self.ct.active_calltip = None
313        # Test .active_calltip, +args
314        self.ct.tips_removed = 0
315
316    def open_close(self, testfunc):
317        # Open-close template with testfunc called in between.
318        opentip = self.ct.open_calltip
319        self.text.insert(1.0, 'f(')
320        opentip(False)
321        self.tip = self.ct.active_calltip
322        testfunc(self)  ###
323        self.text.insert('insert', ')')
324        opentip(False)
325        self.assertIsNone(self.ct.active_calltip, None)
326
327    def test_open_close(self):
328        def args(self):
329            self.assertEqual(self.tip.args, ('1.1', '1.end'))
330        self.open_close(args)
331
332    def test_repeated_force(self):
333        def force(self):
334            for char in 'abc':
335                self.text.insert('insert', 'a')
336                self.ct.open_calltip(True)
337                self.ct.open_calltip(True)
338            self.assertIs(self.ct.active_calltip, self.tip)
339        self.open_close(force)
340
341    def test_repeated_parens(self):
342        def parens(self):
343            for context in "a", "'":
344                with self.subTest(context=context):
345                    self.text.insert('insert', context)
346                    for char in '(()())':
347                        self.text.insert('insert', char)
348                    self.assertIs(self.ct.active_calltip, self.tip)
349            self.text.insert('insert', "'")
350        self.open_close(parens)
351
352    def test_comment_parens(self):
353        def comment(self):
354            self.text.insert('insert', "# ")
355            for char in '(()())':
356                self.text.insert('insert', char)
357            self.assertIs(self.ct.active_calltip, self.tip)
358            self.text.insert('insert', "\n")
359        self.open_close(comment)
360
361
362if __name__ == '__main__':
363    unittest.main(verbosity=2)
364