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, 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) 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, '''\ 94(repl, string, count=0) 95Return the string obtained by replacing the leftmost \ 96non-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=' [...]') 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().''') 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), '''\ 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''') 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 C: 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 C: 210 def m2(**kwargs): pass 211 class Test: 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 NoCall: 231 def __getattr__(self, name): # Not invoked for class attribute. 232 raise IndexError # Bug. 233 class CallA(NoCall): 234 def __call__(self, ci): # Bug does not matter. 235 pass 236 class CallB(NoCall): 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 Type(type): # Type() requires 3 type args, returns class. 248 __class__ = property({}.__getitem__, {}.__setitem__) 249 class Object(metaclass=Type): 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 262class Get_entityTest(unittest.TestCase): 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 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 281 282 283class mock_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 292class WrappedCalltip(calltip.Calltip): 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 305class CalltipTest(unittest.TestCase): 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 364if __name__ == '__main__': 365 unittest.main(verbosity=2) 366