(root)/
Python-3.12.0/
Lib/
idlelib/
idle_test/
test_calltip.py
       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)