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