Format: text/x-python
⬇ Download File (66.4 KB)
   1  # encoding: utf-8
   2  """Tests for the IPython tab-completion machinery."""
   3  
   4  # Copyright (c) IPython Development Team.
   5  # Distributed under the terms of the Modified BSD License.
   6  
   7  import os
   8  import pytest
   9  import sys
  10  import textwrap
  11  import unittest
  12  import random
  13  
  14  from importlib.metadata import version
  15  
  16  from contextlib import contextmanager
  17  
  18  from traitlets.config.loader import Config
  19  from IPython import get_ipython
  20  from IPython.core import completer
  21  from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
  22  from IPython.utils.generics import complete_object
  23  from IPython.testing import decorators as dec
  24  from IPython.core.latex_symbols import latex_symbols
  25  
  26  from IPython.core.completer import (
  27      Completion,
  28      provisionalcompleter,
  29      match_dict_keys,
  30      _deduplicate_completions,
  31      _match_number_in_dict_key_prefix,
  32      completion_matcher,
  33      SimpleCompletion,
  34      CompletionContext,
  35      _unicode_name_compute,
  36      _UNICODE_RANGES,
  37  )
  38  
  39  from packaging.version import parse
  40  
  41  
  42  @contextmanager
  43  def jedi_status(status: bool):
  44      completer = get_ipython().Completer
  45      try:
  46          old = completer.use_jedi
  47          completer.use_jedi = status
  48          yield
  49      finally:
  50          completer.use_jedi = old
  51  
  52  
  53  # -----------------------------------------------------------------------------
  54  # Test functions
  55  # -----------------------------------------------------------------------------
  56  
  57  
  58  def recompute_unicode_ranges():
  59      """
  60      utility to recompute the largest unicode range without any characters
  61  
  62      use to recompute the gap in the global _UNICODE_RANGES of completer.py
  63      """
  64      import itertools
  65      import unicodedata
  66  
  67      valid = []
  68      for c in range(0, 0x10FFFF + 1):
  69          try:
  70              unicodedata.name(chr(c))
  71          except ValueError:
  72              continue
  73          valid.append(c)
  74  
  75      def ranges(i):
  76          for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
  77              b = list(b)
  78              yield b[0][1], b[-1][1]
  79  
  80      rg = list(ranges(valid))
  81      lens = []
  82      gap_lens = []
  83      _pstart, pstop = 0, 0
  84      for start, stop in rg:
  85          lens.append(stop - start)
  86          gap_lens.append(
  87              (
  88                  start - pstop,
  89                  hex(pstop + 1),
  90                  hex(start),
  91                  f"{round((start - pstop)/0xe01f0*100)}%",
  92              )
  93          )
  94          _pstart, pstop = start, stop
  95  
  96      return sorted(gap_lens)[-1]
  97  
  98  
  99  def test_unicode_range():
 100      """
 101      Test that the ranges we test for unicode names give the same number of
 102      results than testing the full length.
 103      """
 104  
 105      expected_list = _unicode_name_compute([(0, 0x110000)])
 106      test = _unicode_name_compute(_UNICODE_RANGES)
 107      len_exp = len(expected_list)
 108      len_test = len(test)
 109  
 110      # do not inline the len() or on error pytest will try to print the 130 000 +
 111      # elements.
 112      message = None
 113      if len_exp != len_test or len_exp > 131808:
 114          size, start, stop, prct = recompute_unicode_ranges()
 115          message = f"""_UNICODE_RANGES likely wrong and need updating. This is
 116          likely due to a new release of Python. We've find that the biggest gap
 117          in unicode characters has reduces in size to be {size} characters
 118          ({prct}), from {start}, to {stop}. In completer.py likely update to
 119  
 120              _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
 121  
 122          And update the assertion below to use
 123  
 124              len_exp <= {len_exp}
 125          """
 126      assert len_exp == len_test, message
 127  
 128      # fail if new unicode symbols have been added.
 129      assert len_exp <= 143668, message
 130  
 131  
 132  @contextmanager
 133  def greedy_completion():
 134      ip = get_ipython()
 135      greedy_original = ip.Completer.greedy
 136      try:
 137          ip.Completer.greedy = True
 138          yield
 139      finally:
 140          ip.Completer.greedy = greedy_original
 141  
 142  
 143  @contextmanager
 144  def evaluation_policy(evaluation: str):
 145      ip = get_ipython()
 146      evaluation_original = ip.Completer.evaluation
 147      try:
 148          ip.Completer.evaluation = evaluation
 149          yield
 150      finally:
 151          ip.Completer.evaluation = evaluation_original
 152  
 153  
 154  @contextmanager
 155  def custom_matchers(matchers):
 156      ip = get_ipython()
 157      try:
 158          ip.Completer.custom_matchers.extend(matchers)
 159          yield
 160      finally:
 161          ip.Completer.custom_matchers.clear()
 162  
 163  
 164  if sys.platform == "win32":
 165      pairs = [
 166          ("abc", "abc"),
 167          (" abc", '" abc"'),
 168          ("a bc", '"a bc"'),
 169          ("a  bc", '"a  bc"'),
 170          ("  bc", '"  bc"'),
 171      ]
 172  else:
 173      pairs = [
 174          ("abc", "abc"),
 175          (" abc", r"\ abc"),
 176          ("a bc", r"a\ bc"),
 177          ("a  bc", r"a\ \ bc"),
 178          ("  bc", r"\ \ bc"),
 179          # On posix, we also protect parens and other special characters.
 180          ("a(bc", r"a\(bc"),
 181          ("a)bc", r"a\)bc"),
 182          ("a( )bc", r"a\(\ \)bc"),
 183          ("a[1]bc", r"a\[1\]bc"),
 184          ("a{1}bc", r"a\{1\}bc"),
 185          ("a#bc", r"a\#bc"),
 186          ("a?bc", r"a\?bc"),
 187          ("a=bc", r"a\=bc"),
 188          ("a\\bc", r"a\\bc"),
 189          ("a|bc", r"a\|bc"),
 190          ("a;bc", r"a\;bc"),
 191          ("a:bc", r"a\:bc"),
 192          ("a'bc", r"a\'bc"),
 193          ("a*bc", r"a\*bc"),
 194          ('a"bc', r"a\"bc"),
 195          ("a^bc", r"a\^bc"),
 196          ("a&bc", r"a\&bc"),
 197      ]
 198  
 199  
 200  @pytest.mark.parametrize("s1,expected", pairs)
 201  def test_protect_filename(s1, expected):
 202      assert completer.protect_filename(s1) == expected
 203  
 204  
 205  def check_line_split(splitter, test_specs):
 206      for part1, part2, split in test_specs:
 207          cursor_pos = len(part1)
 208          line = part1 + part2
 209          out = splitter.split_line(line, cursor_pos)
 210          assert out == split
 211  
 212  def test_line_split():
 213      """Basic line splitter test with default specs."""
 214      sp = completer.CompletionSplitter()
 215      # The format of the test specs is: part1, part2, expected answer.  Parts 1
 216      # and 2 are joined into the 'line' sent to the splitter, as if the cursor
 217      # was at the end of part1.  So an empty part2 represents someone hitting
 218      # tab at the end of the line, the most common case.
 219      t = [
 220          ("run some/script", "", "some/script"),
 221          ("run scripts/er", "ror.py foo", "scripts/er"),
 222          ("echo $HOM", "", "HOM"),
 223          ("print sys.pa", "", "sys.pa"),
 224          ("print(sys.pa", "", "sys.pa"),
 225          ("execfile('scripts/er", "", "scripts/er"),
 226          ("a[x.", "", "x."),
 227          ("a[x.", "y", "x."),
 228          ('cd "some_file/', "", "some_file/"),
 229      ]
 230      check_line_split(sp, t)
 231      # Ensure splitting works OK with unicode by re-running the tests with
 232      # all inputs turned into unicode
 233      check_line_split(sp, [map(str, p) for p in t])
 234  
 235  
 236  class NamedInstanceClass:
 237      instances = {}
 238  
 239      def __init__(self, name):
 240          self.instances[name] = self
 241  
 242      @classmethod
 243      def _ipython_key_completions_(cls):
 244          return cls.instances.keys()
 245  
 246  
 247  class KeyCompletable:
 248      def __init__(self, things=()):
 249          self.things = things
 250  
 251      def _ipython_key_completions_(self):
 252          return list(self.things)
 253  
 254  
 255  class TestCompleter(unittest.TestCase):
 256      def setUp(self):
 257          """
 258          We want to silence all PendingDeprecationWarning when testing the completer
 259          """
 260          self._assertwarns = self.assertWarns(PendingDeprecationWarning)
 261          self._assertwarns.__enter__()
 262  
 263      def tearDown(self):
 264          try:
 265              self._assertwarns.__exit__(None, None, None)
 266          except AssertionError:
 267              pass
 268  
 269      def test_custom_completion_error(self):
 270          """Test that errors from custom attribute completers are silenced."""
 271          ip = get_ipython()
 272  
 273          class A:
 274              pass
 275  
 276          ip.user_ns["x"] = A()
 277  
 278          @complete_object.register(A)
 279          def complete_A(a, existing_completions):
 280              raise TypeError("this should be silenced")
 281  
 282          ip.complete("x.")
 283  
 284      def test_custom_completion_ordering(self):
 285          """Test that errors from custom attribute completers are silenced."""
 286          ip = get_ipython()
 287  
 288          _, matches = ip.complete('in')
 289          assert matches.index('input') < matches.index('int')
 290  
 291          def complete_example(a):
 292              return ['example2', 'example1']
 293  
 294          ip.Completer.custom_completers.add_re('ex*', complete_example)
 295          _, matches = ip.complete('ex')
 296          assert matches.index('example2') < matches.index('example1')
 297  
 298      def test_unicode_completions(self):
 299          ip = get_ipython()
 300          # Some strings that trigger different types of completion.  Check them both
 301          # in str and unicode forms
 302          s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
 303          for t in s + list(map(str, s)):
 304              # We don't need to check exact completion values (they may change
 305              # depending on the state of the namespace, but at least no exceptions
 306              # should be thrown and the return value should be a pair of text, list
 307              # values.
 308              text, matches = ip.complete(t)
 309              self.assertIsInstance(text, str)
 310              self.assertIsInstance(matches, list)
 311  
 312      def test_latex_completions(self):
 313  
 314          ip = get_ipython()
 315          # Test some random unicode symbols
 316          keys = random.sample(sorted(latex_symbols), 10)
 317          for k in keys:
 318              text, matches = ip.complete(k)
 319              self.assertEqual(text, k)
 320              self.assertEqual(matches, [latex_symbols[k]])
 321          # Test a more complex line
 322          text, matches = ip.complete("print(\\alpha")
 323          self.assertEqual(text, "\\alpha")
 324          self.assertEqual(matches[0], latex_symbols["\\alpha"])
 325          # Test multiple matching latex symbols
 326          text, matches = ip.complete("\\al")
 327          self.assertIn("\\alpha", matches)
 328          self.assertIn("\\aleph", matches)
 329  
 330      def test_latex_no_results(self):
 331          """
 332          forward latex should really return nothing in either field if nothing is found.
 333          """
 334          ip = get_ipython()
 335          text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
 336          self.assertEqual(text, "")
 337          self.assertEqual(matches, ())
 338  
 339      def test_back_latex_completion(self):
 340          ip = get_ipython()
 341  
 342          # do not return more than 1 matches for \beta, only the latex one.
 343          name, matches = ip.complete("\\β")
 344          self.assertEqual(matches, ["\\beta"])
 345  
 346      def test_back_unicode_completion(self):
 347          ip = get_ipython()
 348  
 349          name, matches = ip.complete("\\Ⅴ")
 350          self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"])
 351  
 352      def test_forward_unicode_completion(self):
 353          ip = get_ipython()
 354  
 355          name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
 356          self.assertEqual(matches, ["Ⅴ"])  # This is not a V
 357          self.assertEqual(matches, ["\u2164"])  # same as above but explicit.
 358  
 359      def test_delim_setting(self):
 360          sp = completer.CompletionSplitter()
 361          sp.delims = " "
 362          self.assertEqual(sp.delims, " ")
 363          self.assertEqual(sp._delim_expr, r"[\ ]")
 364  
 365      def test_spaces(self):
 366          """Test with only spaces as split chars."""
 367          sp = completer.CompletionSplitter()
 368          sp.delims = " "
 369          t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
 370          check_line_split(sp, t)
 371  
 372      def test_has_open_quotes1(self):
 373          for s in ["'", "'''", "'hi' '"]:
 374              self.assertEqual(completer.has_open_quotes(s), "'")
 375  
 376      def test_has_open_quotes2(self):
 377          for s in ['"', '"""', '"hi" "']:
 378              self.assertEqual(completer.has_open_quotes(s), '"')
 379  
 380      def test_has_open_quotes3(self):
 381          for s in ["''", "''' '''", "'hi' 'ipython'"]:
 382              self.assertFalse(completer.has_open_quotes(s))
 383  
 384      def test_has_open_quotes4(self):
 385          for s in ['""', '""" """', '"hi" "ipython"']:
 386              self.assertFalse(completer.has_open_quotes(s))
 387  
 388      @pytest.mark.xfail(
 389          sys.platform == "win32", reason="abspath completions fail on Windows"
 390      )
 391      def test_abspath_file_completions(self):
 392          ip = get_ipython()
 393          with TemporaryDirectory() as tmpdir:
 394              prefix = os.path.join(tmpdir, "foo")
 395              suffixes = ["1", "2"]
 396              names = [prefix + s for s in suffixes]
 397              for n in names:
 398                  open(n, "w", encoding="utf-8").close()
 399  
 400              # Check simple completion
 401              c = ip.complete(prefix)[1]
 402              self.assertEqual(c, names)
 403  
 404              # Now check with a function call
 405              cmd = 'a = f("%s' % prefix
 406              c = ip.complete(prefix, cmd)[1]
 407              comp = [prefix + s for s in suffixes]
 408              self.assertEqual(c, comp)
 409  
 410      def test_local_file_completions(self):
 411          ip = get_ipython()
 412          with TemporaryWorkingDirectory():
 413              prefix = "./foo"
 414              suffixes = ["1", "2"]
 415              names = [prefix + s for s in suffixes]
 416              for n in names:
 417                  open(n, "w", encoding="utf-8").close()
 418  
 419              # Check simple completion
 420              c = ip.complete(prefix)[1]
 421              self.assertEqual(c, names)
 422  
 423              # Now check with a function call
 424              cmd = 'a = f("%s' % prefix
 425              c = ip.complete(prefix, cmd)[1]
 426              comp = {prefix + s for s in suffixes}
 427              self.assertTrue(comp.issubset(set(c)))
 428  
 429      def test_quoted_file_completions(self):
 430          ip = get_ipython()
 431  
 432          def _(text):
 433              return ip.Completer._complete(
 434                  cursor_line=0, cursor_pos=len(text), full_text=text
 435              )["IPCompleter.file_matcher"]["completions"]
 436  
 437          with TemporaryWorkingDirectory():
 438              name = "foo'bar"
 439              open(name, "w", encoding="utf-8").close()
 440  
 441              # Don't escape Windows
 442              escaped = name if sys.platform == "win32" else "foo\\'bar"
 443  
 444              # Single quote matches embedded single quote
 445              c = _("open('foo")[0]
 446              self.assertEqual(c.text, escaped)
 447  
 448              # Double quote requires no escape
 449              c = _('open("foo')[0]
 450              self.assertEqual(c.text, name)
 451  
 452              # No quote requires an escape
 453              c = _("%ls foo")[0]
 454              self.assertEqual(c.text, escaped)
 455  
 456      @pytest.mark.xfail(
 457          sys.version_info.releaselevel in ("alpha",),
 458          reason="Parso does not yet parse 3.13",
 459      )
 460      def test_all_completions_dups(self):
 461          """
 462          Make sure the output of `IPCompleter.all_completions` does not have
 463          duplicated prefixes.
 464          """
 465          ip = get_ipython()
 466          c = ip.Completer
 467          ip.ex("class TestClass():\n\ta=1\n\ta1=2")
 468          for jedi_status in [True, False]:
 469              with provisionalcompleter():
 470                  ip.Completer.use_jedi = jedi_status
 471                  matches = c.all_completions("TestCl")
 472                  assert matches == ["TestClass"], (jedi_status, matches)
 473                  matches = c.all_completions("TestClass.")
 474                  assert len(matches) > 2, (jedi_status, matches)
 475                  matches = c.all_completions("TestClass.a")
 476                  if jedi_status:
 477                      assert matches == ["TestClass.a", "TestClass.a1"], jedi_status
 478                  else:
 479                      assert matches == [".a", ".a1"], jedi_status
 480  
 481      @pytest.mark.xfail(
 482          sys.version_info.releaselevel in ("alpha",),
 483          reason="Parso does not yet parse 3.13",
 484      )
 485      def test_jedi(self):
 486          """
 487          A couple of issue we had with Jedi
 488          """
 489          ip = get_ipython()
 490  
 491          def _test_complete(reason, s, comp, start=None, end=None):
 492              l = len(s)
 493              start = start if start is not None else l
 494              end = end if end is not None else l
 495              with provisionalcompleter():
 496                  ip.Completer.use_jedi = True
 497                  completions = set(ip.Completer.completions(s, l))
 498                  ip.Completer.use_jedi = False
 499                  assert Completion(start, end, comp) in completions, reason
 500  
 501          def _test_not_complete(reason, s, comp):
 502              l = len(s)
 503              with provisionalcompleter():
 504                  ip.Completer.use_jedi = True
 505                  completions = set(ip.Completer.completions(s, l))
 506                  ip.Completer.use_jedi = False
 507                  assert Completion(l, l, comp) not in completions, reason
 508  
 509          import jedi
 510  
 511          jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
 512          if jedi_version > (0, 10):
 513              _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
 514          _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
 515          _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
 516          _test_complete("cover duplicate completions", "im", "import", 0, 2)
 517  
 518          _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
 519  
 520      @pytest.mark.xfail(
 521          sys.version_info.releaselevel in ("alpha",),
 522          reason="Parso does not yet parse 3.13",
 523      )
 524      def test_completion_have_signature(self):
 525          """
 526          Lets make sure jedi is capable of pulling out the signature of the function we are completing.
 527          """
 528          ip = get_ipython()
 529          with provisionalcompleter():
 530              ip.Completer.use_jedi = True
 531              completions = ip.Completer.completions("ope", 3)
 532              c = next(completions)  # should be `open`
 533              ip.Completer.use_jedi = False
 534          assert "file" in c.signature, "Signature of function was not found by completer"
 535          assert (
 536              "encoding" in c.signature
 537          ), "Signature of function was not found by completer"
 538  
 539      @pytest.mark.xfail(
 540          sys.version_info.releaselevel in ("alpha",),
 541          reason="Parso does not yet parse 3.13",
 542      )
 543      def test_completions_have_type(self):
 544          """
 545          Lets make sure matchers provide completion type.
 546          """
 547          ip = get_ipython()
 548          with provisionalcompleter():
 549              ip.Completer.use_jedi = False
 550              completions = ip.Completer.completions("%tim", 3)
 551              c = next(completions)  # should be `%time` or similar
 552          assert c.type == "magic", "Type of magic was not assigned by completer"
 553  
 554      @pytest.mark.xfail(
 555          parse(version("jedi")) <= parse("0.18.0"),
 556          reason="Known failure on jedi<=0.18.0",
 557          strict=True,
 558      )
 559      def test_deduplicate_completions(self):
 560          """
 561          Test that completions are correctly deduplicated (even if ranges are not the same)
 562          """
 563          ip = get_ipython()
 564          ip.ex(
 565              textwrap.dedent(
 566                  """
 567          class Z:
 568              zoo = 1
 569          """
 570              )
 571          )
 572          with provisionalcompleter():
 573              ip.Completer.use_jedi = True
 574              l = list(
 575                  _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
 576              )
 577              ip.Completer.use_jedi = False
 578  
 579          assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
 580          assert l[0].text == "zoo"  # and not `it.accumulate`
 581  
 582      @pytest.mark.xfail(
 583          sys.version_info.releaselevel in ("alpha",),
 584          reason="Parso does not yet parse 3.13",
 585      )
 586      def test_greedy_completions(self):
 587          """
 588          Test the capability of the Greedy completer.
 589  
 590          Most of the test here does not really show off the greedy completer, for proof
 591          each of the text below now pass with Jedi. The greedy completer is capable of more.
 592  
 593          See the :any:`test_dict_key_completion_contexts`
 594  
 595          """
 596          ip = get_ipython()
 597          ip.ex("a=list(range(5))")
 598          ip.ex("d = {'a b': str}")
 599          _, c = ip.complete(".", line="a[0].")
 600          self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
 601  
 602          def _(line, cursor_pos, expect, message, completion):
 603              with greedy_completion(), provisionalcompleter():
 604                  ip.Completer.use_jedi = False
 605                  _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
 606                  self.assertIn(expect, c, message % c)
 607  
 608                  ip.Completer.use_jedi = True
 609                  with provisionalcompleter():
 610                      completions = ip.Completer.completions(line, cursor_pos)
 611                  self.assertIn(completion, list(completions))
 612  
 613          with provisionalcompleter():
 614              _(
 615                  "a[0].",
 616                  5,
 617                  ".real",
 618                  "Should have completed on a[0].: %s",
 619                  Completion(5, 5, "real"),
 620              )
 621              _(
 622                  "a[0].r",
 623                  6,
 624                  ".real",
 625                  "Should have completed on a[0].r: %s",
 626                  Completion(5, 6, "real"),
 627              )
 628              _(
 629                  "a[0].from_",
 630                  10,
 631                  ".from_bytes",
 632                  "Should have completed on a[0].from_: %s",
 633                  Completion(5, 10, "from_bytes"),
 634              )
 635              _(
 636                  "assert str.star",
 637                  14,
 638                  ".startswith",
 639                  "Should have completed on `assert str.star`: %s",
 640                  Completion(11, 14, "startswith"),
 641              )
 642              _(
 643                  "d['a b'].str",
 644                  12,
 645                  ".strip",
 646                  "Should have completed on `d['a b'].str`: %s",
 647                  Completion(9, 12, "strip"),
 648              )
 649              _(
 650                  "a.app",
 651                  4,
 652                  ".append",
 653                  "Should have completed on `a.app`: %s",
 654                  Completion(2, 4, "append"),
 655              )
 656  
 657      def test_omit__names(self):
 658          # also happens to test IPCompleter as a configurable
 659          ip = get_ipython()
 660          ip._hidden_attr = 1
 661          ip._x = {}
 662          c = ip.Completer
 663          ip.ex("ip=get_ipython()")
 664          cfg = Config()
 665          cfg.IPCompleter.omit__names = 0
 666          c.update_config(cfg)
 667          with provisionalcompleter():
 668              c.use_jedi = False
 669              s, matches = c.complete("ip.")
 670              self.assertIn(".__str__", matches)
 671              self.assertIn("._hidden_attr", matches)
 672  
 673              # c.use_jedi = True
 674              # completions = set(c.completions('ip.', 3))
 675              # self.assertIn(Completion(3, 3, '__str__'), completions)
 676              # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
 677  
 678          cfg = Config()
 679          cfg.IPCompleter.omit__names = 1
 680          c.update_config(cfg)
 681          with provisionalcompleter():
 682              c.use_jedi = False
 683              s, matches = c.complete("ip.")
 684              self.assertNotIn(".__str__", matches)
 685              # self.assertIn('ip._hidden_attr', matches)
 686  
 687              # c.use_jedi = True
 688              # completions = set(c.completions('ip.', 3))
 689              # self.assertNotIn(Completion(3,3,'__str__'), completions)
 690              # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
 691  
 692          cfg = Config()
 693          cfg.IPCompleter.omit__names = 2
 694          c.update_config(cfg)
 695          with provisionalcompleter():
 696              c.use_jedi = False
 697              s, matches = c.complete("ip.")
 698              self.assertNotIn(".__str__", matches)
 699              self.assertNotIn("._hidden_attr", matches)
 700  
 701              # c.use_jedi = True
 702              # completions = set(c.completions('ip.', 3))
 703              # self.assertNotIn(Completion(3,3,'__str__'), completions)
 704              # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
 705  
 706          with provisionalcompleter():
 707              c.use_jedi = False
 708              s, matches = c.complete("ip._x.")
 709              self.assertIn(".keys", matches)
 710  
 711              # c.use_jedi = True
 712              # completions = set(c.completions('ip._x.', 6))
 713              # self.assertIn(Completion(6,6, "keys"), completions)
 714  
 715          del ip._hidden_attr
 716          del ip._x
 717  
 718      def test_limit_to__all__False_ok(self):
 719          """
 720          Limit to all is deprecated, once we remove it this test can go away.
 721          """
 722          ip = get_ipython()
 723          c = ip.Completer
 724          c.use_jedi = False
 725          ip.ex("class D: x=24")
 726          ip.ex("d=D()")
 727          cfg = Config()
 728          cfg.IPCompleter.limit_to__all__ = False
 729          c.update_config(cfg)
 730          s, matches = c.complete("d.")
 731          self.assertIn(".x", matches)
 732  
 733      def test_get__all__entries_ok(self):
 734          class A:
 735              __all__ = ["x", 1]
 736  
 737          words = completer.get__all__entries(A())
 738          self.assertEqual(words, ["x"])
 739  
 740      def test_get__all__entries_no__all__ok(self):
 741          class A:
 742              pass
 743  
 744          words = completer.get__all__entries(A())
 745          self.assertEqual(words, [])
 746  
 747      def test_completes_globals_as_args_of_methods(self):
 748          ip = get_ipython()
 749          c = ip.Completer
 750          c.use_jedi = False
 751          ip.ex("long_variable_name = 1")
 752          ip.ex("a = []")
 753          s, matches = c.complete(None, "a.sort(lo")
 754          self.assertIn("long_variable_name", matches)
 755  
 756      def test_completes_attributes_in_fstring_expressions(self):
 757          ip = get_ipython()
 758          c = ip.Completer
 759          c.use_jedi = False
 760  
 761          class CustomClass:
 762              def method_one(self):
 763                  pass
 764  
 765          ip.user_ns["custom_obj"] = CustomClass()
 766  
 767          # Test completion inside f-string expressions
 768          s, matches = c.complete(None, "f'{custom_obj.meth")
 769          self.assertIn(".method_one", matches)
 770  
 771      def test_completes_in_dict_expressions(self):
 772          ip = get_ipython()
 773          c = ip.Completer
 774          c.use_jedi = False
 775          ip.ex("class Test: pass")
 776          ip.ex("test_obj = Test()")
 777          ip.ex("test_obj.attribute = 'value'")
 778  
 779          # Test completion in dictionary expressions
 780          s, matches = c.complete(None, "d = {'key': test_obj.attr")
 781          self.assertIn(".attribute", matches)
 782  
 783          # Test global completion in dictionary expressions with dots
 784          s, matches = c.complete(None, "d = {'k.e.y': Te")
 785          self.assertIn("Test", matches)
 786  
 787      def test_func_kw_completions(self):
 788          ip = get_ipython()
 789          c = ip.Completer
 790          c.use_jedi = False
 791          ip.ex("def myfunc(a=1,b=2): return a+b")
 792          s, matches = c.complete(None, "myfunc(1,b")
 793          self.assertIn("b=", matches)
 794          # Simulate completing with cursor right after b (pos==10):
 795          s, matches = c.complete(None, "myfunc(1,b)", 10)
 796          self.assertIn("b=", matches)
 797          s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
 798          self.assertIn("b=", matches)
 799          # builtin function
 800          s, matches = c.complete(None, "min(k, k")
 801          self.assertIn("key=", matches)
 802  
 803      def test_default_arguments_from_docstring(self):
 804          ip = get_ipython()
 805          c = ip.Completer
 806          kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
 807          self.assertEqual(kwd, ["key"])
 808          # with cython type etc
 809          kwd = c._default_arguments_from_docstring(
 810              "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
 811          )
 812          self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
 813          # white spaces
 814          kwd = c._default_arguments_from_docstring(
 815              "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
 816          )
 817          self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
 818  
 819      def test_line_magics(self):
 820          ip = get_ipython()
 821          c = ip.Completer
 822          s, matches = c.complete(None, "lsmag")
 823          self.assertIn("%lsmagic", matches)
 824          s, matches = c.complete(None, "%lsmag")
 825          self.assertIn("%lsmagic", matches)
 826  
 827      def test_cell_magics(self):
 828          from IPython.core.magic import register_cell_magic
 829  
 830          @register_cell_magic
 831          def _foo_cellm(line, cell):
 832              pass
 833  
 834          ip = get_ipython()
 835          c = ip.Completer
 836  
 837          s, matches = c.complete(None, "_foo_ce")
 838          self.assertIn("%%_foo_cellm", matches)
 839          s, matches = c.complete(None, "%%_foo_ce")
 840          self.assertIn("%%_foo_cellm", matches)
 841  
 842      def test_line_cell_magics(self):
 843          from IPython.core.magic import register_line_cell_magic
 844  
 845          @register_line_cell_magic
 846          def _bar_cellm(line, cell):
 847              pass
 848  
 849          ip = get_ipython()
 850          c = ip.Completer
 851  
 852          # The policy here is trickier, see comments in completion code.  The
 853          # returned values depend on whether the user passes %% or not explicitly,
 854          # and this will show a difference if the same name is both a line and cell
 855          # magic.
 856          s, matches = c.complete(None, "_bar_ce")
 857          self.assertIn("%_bar_cellm", matches)
 858          self.assertIn("%%_bar_cellm", matches)
 859          s, matches = c.complete(None, "%_bar_ce")
 860          self.assertIn("%_bar_cellm", matches)
 861          self.assertIn("%%_bar_cellm", matches)
 862          s, matches = c.complete(None, "%%_bar_ce")
 863          self.assertNotIn("%_bar_cellm", matches)
 864          self.assertIn("%%_bar_cellm", matches)
 865  
 866      def test_magic_completion_order(self):
 867          ip = get_ipython()
 868          c = ip.Completer
 869  
 870          # Test ordering of line and cell magics.
 871          text, matches = c.complete("timeit")
 872          self.assertEqual(matches, ["%timeit", "%%timeit"])
 873  
 874      def test_magic_completion_shadowing(self):
 875          ip = get_ipython()
 876          c = ip.Completer
 877          c.use_jedi = False
 878  
 879          # Before importing matplotlib, %matplotlib magic should be the only option.
 880          text, matches = c.complete("mat")
 881          self.assertEqual(matches, ["%matplotlib"])
 882  
 883          # The newly introduced name should shadow the magic.
 884          ip.run_cell("matplotlib = 1")
 885          text, matches = c.complete("mat")
 886          self.assertEqual(matches, ["matplotlib"])
 887  
 888          # After removing matplotlib from namespace, the magic should again be
 889          # the only option.
 890          del ip.user_ns["matplotlib"]
 891          text, matches = c.complete("mat")
 892          self.assertEqual(matches, ["%matplotlib"])
 893  
 894      def test_magic_completion_shadowing_explicit(self):
 895          """
 896          If the user try to complete a shadowed magic, and explicit % start should
 897          still return the completions.
 898          """
 899          ip = get_ipython()
 900          c = ip.Completer
 901  
 902          # Before importing matplotlib, %matplotlib magic should be the only option.
 903          text, matches = c.complete("%mat")
 904          self.assertEqual(matches, ["%matplotlib"])
 905  
 906          ip.run_cell("matplotlib = 1")
 907  
 908          # After removing matplotlib from namespace, the magic should still be
 909          # the only option.
 910          text, matches = c.complete("%mat")
 911          self.assertEqual(matches, ["%matplotlib"])
 912  
 913      def test_magic_config(self):
 914          ip = get_ipython()
 915          c = ip.Completer
 916  
 917          s, matches = c.complete(None, "conf")
 918          self.assertIn("%config", matches)
 919          s, matches = c.complete(None, "conf")
 920          self.assertNotIn("AliasManager", matches)
 921          s, matches = c.complete(None, "config ")
 922          self.assertIn("AliasManager", matches)
 923          s, matches = c.complete(None, "%config ")
 924          self.assertIn("AliasManager", matches)
 925          s, matches = c.complete(None, "config Ali")
 926          self.assertListEqual(["AliasManager"], matches)
 927          s, matches = c.complete(None, "%config Ali")
 928          self.assertListEqual(["AliasManager"], matches)
 929          s, matches = c.complete(None, "config AliasManager")
 930          self.assertListEqual(["AliasManager"], matches)
 931          s, matches = c.complete(None, "%config AliasManager")
 932          self.assertListEqual(["AliasManager"], matches)
 933          s, matches = c.complete(None, "config AliasManager.")
 934          self.assertIn("AliasManager.default_aliases", matches)
 935          s, matches = c.complete(None, "%config AliasManager.")
 936          self.assertIn("AliasManager.default_aliases", matches)
 937          s, matches = c.complete(None, "config AliasManager.de")
 938          self.assertListEqual(["AliasManager.default_aliases"], matches)
 939          s, matches = c.complete(None, "config AliasManager.de")
 940          self.assertListEqual(["AliasManager.default_aliases"], matches)
 941  
 942      def test_magic_color(self):
 943          ip = get_ipython()
 944          c = ip.Completer
 945  
 946          s, matches = c.complete(None, "colo")
 947          self.assertIn("%colors", matches)
 948          s, matches = c.complete(None, "colo")
 949          self.assertNotIn("NoColor", matches)
 950          s, matches = c.complete(None, "%colors")  # No trailing space
 951          self.assertNotIn("NoColor", matches)
 952          s, matches = c.complete(None, "colors ")
 953          self.assertIn("NoColor", matches)
 954          s, matches = c.complete(None, "%colors ")
 955          self.assertIn("NoColor", matches)
 956          s, matches = c.complete(None, "colors NoCo")
 957          self.assertListEqual(["NoColor"], matches)
 958          s, matches = c.complete(None, "%colors NoCo")
 959          self.assertListEqual(["NoColor"], matches)
 960  
 961      def test_match_dict_keys(self):
 962          """
 963          Test that match_dict_keys works on a couple of use case does return what
 964          expected, and does not crash
 965          """
 966          delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
 967  
 968          def match(*args, **kwargs):
 969              quote, offset, matches = match_dict_keys(*args, delims=delims, **kwargs)
 970              return quote, offset, list(matches)
 971  
 972          keys = ["foo", b"far"]
 973          assert match(keys, "b'") == ("'", 2, ["far"])
 974          assert match(keys, "b'f") == ("'", 2, ["far"])
 975          assert match(keys, 'b"') == ('"', 2, ["far"])
 976          assert match(keys, 'b"f') == ('"', 2, ["far"])
 977  
 978          assert match(keys, "'") == ("'", 1, ["foo"])
 979          assert match(keys, "'f") == ("'", 1, ["foo"])
 980          assert match(keys, '"') == ('"', 1, ["foo"])
 981          assert match(keys, '"f') == ('"', 1, ["foo"])
 982  
 983          # Completion on first item of tuple
 984          keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, "test")]
 985          assert match(keys, "'f") == ("'", 1, ["foo"])
 986          assert match(keys, "33") == ("", 0, ["3333"])
 987  
 988          # Completion on numbers
 989          keys = [
 990              0xDEADBEEF,
 991              1111,
 992              1234,
 993              "1999",
 994              0b10101,
 995              22,
 996          ]  # 0xDEADBEEF = 3735928559; 0b10101 = 21
 997          assert match(keys, "0xdead") == ("", 0, ["0xdeadbeef"])
 998          assert match(keys, "1") == ("", 0, ["1111", "1234"])
 999          assert match(keys, "2") == ("", 0, ["21", "22"])
1000          assert match(keys, "0b101") == ("", 0, ["0b10101", "0b10110"])
1001  
1002          # Should yield on variables
1003          assert match(keys, "a_variable") == ("", 0, [])
1004  
1005          # Should pass over invalid literals
1006          assert match(keys, "'' ''") == ("", 0, [])
1007  
1008      def test_match_dict_keys_tuple(self):
1009          """
1010          Test that match_dict_keys called with extra prefix works on a couple of use case,
1011          does return what expected, and does not crash.
1012          """
1013          delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
1014  
1015          keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
1016  
1017          def match(*args, extra=None, **kwargs):
1018              quote, offset, matches = match_dict_keys(
1019                  *args, delims=delims, extra_prefix=extra, **kwargs
1020              )
1021              return quote, offset, list(matches)
1022  
1023          # Completion on first key == "foo"
1024          assert match(keys, "'", extra=("foo",)) == ("'", 1, ["bar", "oof"])
1025          assert match(keys, '"', extra=("foo",)) == ('"', 1, ["bar", "oof"])
1026          assert match(keys, "'o", extra=("foo",)) == ("'", 1, ["oof"])
1027          assert match(keys, '"o', extra=("foo",)) == ('"', 1, ["oof"])
1028          assert match(keys, "b'", extra=("foo",)) == ("'", 2, ["bar"])
1029          assert match(keys, 'b"', extra=("foo",)) == ('"', 2, ["bar"])
1030          assert match(keys, "b'b", extra=("foo",)) == ("'", 2, ["bar"])
1031          assert match(keys, 'b"b', extra=("foo",)) == ('"', 2, ["bar"])
1032  
1033          # No Completion
1034          assert match(keys, "'", extra=("no_foo",)) == ("'", 1, [])
1035          assert match(keys, "'", extra=("fo",)) == ("'", 1, [])
1036  
1037          keys = [("foo1", "foo2", "foo3", "foo4"), ("foo1", "foo2", "bar", "foo4")]
1038          assert match(keys, "'foo", extra=("foo1",)) == ("'", 1, ["foo2"])
1039          assert match(keys, "'foo", extra=("foo1", "foo2")) == ("'", 1, ["foo3"])
1040          assert match(keys, "'foo", extra=("foo1", "foo2", "foo3")) == ("'", 1, ["foo4"])
1041          assert match(keys, "'foo", extra=("foo1", "foo2", "foo3", "foo4")) == (
1042              "'",
1043              1,
1044              [],
1045          )
1046  
1047          keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)]
1048          assert match(keys, "'", extra=("foo",)) == ("'", 1, ["2222"])
1049          assert match(keys, "", extra=("foo",)) == ("", 0, ["1111", "'2222'"])
1050          assert match(keys, "'", extra=(3333,)) == ("'", 1, ["bar"])
1051          assert match(keys, "", extra=(3333,)) == ("", 0, ["'bar'", "4444"])
1052          assert match(keys, "'", extra=("3333",)) == ("'", 1, [])
1053          assert match(keys, "33") == ("", 0, ["3333"])
1054  
1055      def test_dict_key_completion_closures(self):
1056          ip = get_ipython()
1057          complete = ip.Completer.complete
1058          ip.Completer.auto_close_dict_keys = True
1059  
1060          ip.user_ns["d"] = {
1061              # tuple only
1062              ("aa", 11): None,
1063              # tuple and non-tuple
1064              ("bb", 22): None,
1065              "bb": None,
1066              # non-tuple only
1067              "cc": None,
1068              # numeric tuple only
1069              (77, "x"): None,
1070              # numeric tuple and non-tuple
1071              (88, "y"): None,
1072              88: None,
1073              # numeric non-tuple only
1074              99: None,
1075          }
1076  
1077          _, matches = complete(line_buffer="d[")
1078          # should append `, ` if matches a tuple only
1079          self.assertIn("'aa', ", matches)
1080          # should not append anything if matches a tuple and an item
1081          self.assertIn("'bb'", matches)
1082          # should append `]` if matches and item only
1083          self.assertIn("'cc']", matches)
1084  
1085          # should append `, ` if matches a tuple only
1086          self.assertIn("77, ", matches)
1087          # should not append anything if matches a tuple and an item
1088          self.assertIn("88", matches)
1089          # should append `]` if matches and item only
1090          self.assertIn("99]", matches)
1091  
1092          _, matches = complete(line_buffer="d['aa', ")
1093          # should restrict matches to those matching tuple prefix
1094          self.assertIn("11]", matches)
1095          self.assertNotIn("'bb'", matches)
1096          self.assertNotIn("'bb', ", matches)
1097          self.assertNotIn("'bb']", matches)
1098          self.assertNotIn("'cc'", matches)
1099          self.assertNotIn("'cc', ", matches)
1100          self.assertNotIn("'cc']", matches)
1101          ip.Completer.auto_close_dict_keys = False
1102  
1103      def test_dict_key_completion_string(self):
1104          """Test dictionary key completion for string keys"""
1105          ip = get_ipython()
1106          complete = ip.Completer.complete
1107  
1108          ip.user_ns["d"] = {"abc": None}
1109  
1110          # check completion at different stages
1111          _, matches = complete(line_buffer="d[")
1112          self.assertIn("'abc'", matches)
1113          self.assertNotIn("'abc']", matches)
1114  
1115          _, matches = complete(line_buffer="d['")
1116          self.assertIn("abc", matches)
1117          self.assertNotIn("abc']", matches)
1118  
1119          _, matches = complete(line_buffer="d['a")
1120          self.assertIn("abc", matches)
1121          self.assertNotIn("abc']", matches)
1122  
1123          # check use of different quoting
1124          _, matches = complete(line_buffer='d["')
1125          self.assertIn("abc", matches)
1126          self.assertNotIn('abc"]', matches)
1127  
1128          _, matches = complete(line_buffer='d["a')
1129          self.assertIn("abc", matches)
1130          self.assertNotIn('abc"]', matches)
1131  
1132          # check sensitivity to following context
1133          _, matches = complete(line_buffer="d[]", cursor_pos=2)
1134          self.assertIn("'abc'", matches)
1135  
1136          _, matches = complete(line_buffer="d['']", cursor_pos=3)
1137          self.assertIn("abc", matches)
1138          self.assertNotIn("abc'", matches)
1139          self.assertNotIn("abc']", matches)
1140  
1141          # check multiple solutions are correctly returned and that noise is not
1142          ip.user_ns["d"] = {
1143              "abc": None,
1144              "abd": None,
1145              "bad": None,
1146              object(): None,
1147              5: None,
1148              ("abe", None): None,
1149              (None, "abf"): None
1150          }
1151  
1152          _, matches = complete(line_buffer="d['a")
1153          self.assertIn("abc", matches)
1154          self.assertIn("abd", matches)
1155          self.assertNotIn("bad", matches)
1156          self.assertNotIn("abe", matches)
1157          self.assertNotIn("abf", matches)
1158          assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1159  
1160          # check escaping and whitespace
1161          ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
1162          _, matches = complete(line_buffer="d['a")
1163          self.assertIn("a\\nb", matches)
1164          self.assertIn("a\\'b", matches)
1165          self.assertIn('a"b', matches)
1166          self.assertIn("a word", matches)
1167          assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1168  
1169          # - can complete on non-initial word of the string
1170          _, matches = complete(line_buffer="d['a w")
1171          self.assertIn("word", matches)
1172  
1173          # - understands quote escaping
1174          _, matches = complete(line_buffer="d['a\\'")
1175          self.assertIn("b", matches)
1176  
1177          # - default quoting should work like repr
1178          _, matches = complete(line_buffer="d[")
1179          self.assertIn('"a\'b"', matches)
1180  
1181          # - when opening quote with ", possible to match with unescaped apostrophe
1182          _, matches = complete(line_buffer="d[\"a'")
1183          self.assertIn("b", matches)
1184  
1185          # need to not split at delims that readline won't split at
1186          if "-" not in ip.Completer.splitter.delims:
1187              ip.user_ns["d"] = {"before-after": None}
1188              _, matches = complete(line_buffer="d['before-af")
1189              self.assertIn("before-after", matches)
1190  
1191          # check completion on tuple-of-string keys at different stage - on first key
1192          ip.user_ns["d"] = {('foo', 'bar'): None}
1193          _, matches = complete(line_buffer="d[")
1194          self.assertIn("'foo'", matches)
1195          self.assertNotIn("'foo']", matches)
1196          self.assertNotIn("'bar'", matches)
1197          self.assertNotIn("foo", matches)
1198          self.assertNotIn("bar", matches)
1199  
1200          # - match the prefix
1201          _, matches = complete(line_buffer="d['f")
1202          self.assertIn("foo", matches)
1203          self.assertNotIn("foo']", matches)
1204          self.assertNotIn('foo"]', matches)
1205          _, matches = complete(line_buffer="d['foo")
1206          self.assertIn("foo", matches)
1207  
1208          # - can complete on second key
1209          _, matches = complete(line_buffer="d['foo', ")
1210          self.assertIn("'bar'", matches)
1211          _, matches = complete(line_buffer="d['foo', 'b")
1212          self.assertIn("bar", matches)
1213          self.assertNotIn("foo", matches)
1214  
1215          # - does not propose missing keys
1216          _, matches = complete(line_buffer="d['foo', 'f")
1217          self.assertNotIn("bar", matches)
1218          self.assertNotIn("foo", matches)
1219  
1220          # check sensitivity to following context
1221          _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
1222          self.assertIn("'bar'", matches)
1223          self.assertNotIn("bar", matches)
1224          self.assertNotIn("'foo'", matches)
1225          self.assertNotIn("foo", matches)
1226  
1227          _, matches = complete(line_buffer="d['']", cursor_pos=3)
1228          self.assertIn("foo", matches)
1229          assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1230  
1231          _, matches = complete(line_buffer='d[""]', cursor_pos=3)
1232          self.assertIn("foo", matches)
1233          assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1234  
1235          _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
1236          self.assertIn("bar", matches)
1237          assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1238  
1239          _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1240          self.assertIn("'bar'", matches)
1241          self.assertNotIn("bar", matches)
1242  
1243          # Can complete with longer tuple keys
1244          ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1245  
1246          # - can complete second key
1247          _, matches = complete(line_buffer="d['foo', 'b")
1248          self.assertIn("bar", matches)
1249          self.assertNotIn("foo", matches)
1250          self.assertNotIn("foobar", matches)
1251  
1252          # - can complete third key
1253          _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1254          self.assertIn("foobar", matches)
1255          self.assertNotIn("foo", matches)
1256          self.assertNotIn("bar", matches)
1257  
1258      def test_dict_key_completion_numbers(self):
1259          ip = get_ipython()
1260          complete = ip.Completer.complete
1261  
1262          ip.user_ns["d"] = {
1263              0xDEADBEEF: None,  # 3735928559
1264              1111: None,
1265              1234: None,
1266              "1999": None,
1267              0b10101: None,  # 21
1268              22: None,
1269          }
1270          _, matches = complete(line_buffer="d[1")
1271          self.assertIn("1111", matches)
1272          self.assertIn("1234", matches)
1273          self.assertNotIn("1999", matches)
1274          self.assertNotIn("'1999'", matches)
1275  
1276          _, matches = complete(line_buffer="d[0xdead")
1277          self.assertIn("0xdeadbeef", matches)
1278  
1279          _, matches = complete(line_buffer="d[2")
1280          self.assertIn("21", matches)
1281          self.assertIn("22", matches)
1282  
1283          _, matches = complete(line_buffer="d[0b101")
1284          self.assertIn("0b10101", matches)
1285          self.assertIn("0b10110", matches)
1286  
1287      def test_dict_key_completion_contexts(self):
1288          """Test expression contexts in which dict key completion occurs"""
1289          ip = get_ipython()
1290          complete = ip.Completer.complete
1291          d = {"abc": None}
1292          ip.user_ns["d"] = d
1293  
1294          class C:
1295              data = d
1296  
1297          ip.user_ns["C"] = C
1298          ip.user_ns["get"] = lambda: d
1299          ip.user_ns["nested"] = {"x": d}
1300  
1301          def assert_no_completion(**kwargs):
1302              _, matches = complete(**kwargs)
1303              self.assertNotIn("abc", matches)
1304              self.assertNotIn("abc'", matches)
1305              self.assertNotIn("abc']", matches)
1306              self.assertNotIn("'abc'", matches)
1307              self.assertNotIn("'abc']", matches)
1308  
1309          def assert_completion(**kwargs):
1310              _, matches = complete(**kwargs)
1311              self.assertIn("'abc'", matches)
1312              self.assertNotIn("'abc']", matches)
1313  
1314          # no completion after string closed, even if reopened
1315          assert_no_completion(line_buffer="d['a'")
1316          assert_no_completion(line_buffer='d["a"')
1317          assert_no_completion(line_buffer="d['a' + ")
1318          assert_no_completion(line_buffer="d['a' + '")
1319  
1320          # completion in non-trivial expressions
1321          assert_completion(line_buffer="+ d[")
1322          assert_completion(line_buffer="(d[")
1323          assert_completion(line_buffer="C.data[")
1324  
1325          # nested dict completion
1326          assert_completion(line_buffer="nested['x'][")
1327  
1328          with evaluation_policy("minimal"):
1329              with pytest.raises(AssertionError):
1330                  assert_completion(line_buffer="nested['x'][")
1331  
1332          # greedy flag
1333          def assert_completion(**kwargs):
1334              _, matches = complete(**kwargs)
1335              self.assertIn("get()['abc']", matches)
1336  
1337          assert_no_completion(line_buffer="get()[")
1338          with greedy_completion():
1339              assert_completion(line_buffer="get()[")
1340              assert_completion(line_buffer="get()['")
1341              assert_completion(line_buffer="get()['a")
1342              assert_completion(line_buffer="get()['ab")
1343              assert_completion(line_buffer="get()['abc")
1344  
1345      def test_dict_key_completion_bytes(self):
1346          """Test handling of bytes in dict key completion"""
1347          ip = get_ipython()
1348          complete = ip.Completer.complete
1349  
1350          ip.user_ns["d"] = {"abc": None, b"abd": None}
1351  
1352          _, matches = complete(line_buffer="d[")
1353          self.assertIn("'abc'", matches)
1354          self.assertIn("b'abd'", matches)
1355  
1356          if False:  # not currently implemented
1357              _, matches = complete(line_buffer="d[b")
1358              self.assertIn("b'abd'", matches)
1359              self.assertNotIn("b'abc'", matches)
1360  
1361              _, matches = complete(line_buffer="d[b'")
1362              self.assertIn("abd", matches)
1363              self.assertNotIn("abc", matches)
1364  
1365              _, matches = complete(line_buffer="d[B'")
1366              self.assertIn("abd", matches)
1367              self.assertNotIn("abc", matches)
1368  
1369              _, matches = complete(line_buffer="d['")
1370              self.assertIn("abc", matches)
1371              self.assertNotIn("abd", matches)
1372  
1373      def test_dict_key_completion_unicode_py3(self):
1374          """Test handling of unicode in dict key completion"""
1375          ip = get_ipython()
1376          complete = ip.Completer.complete
1377  
1378          ip.user_ns["d"] = {"a\u05d0": None}
1379  
1380          # query using escape
1381          if sys.platform != "win32":
1382              # Known failure on Windows
1383              _, matches = complete(line_buffer="d['a\\u05d0")
1384              self.assertIn("u05d0", matches)  # tokenized after \\
1385  
1386          # query using character
1387          _, matches = complete(line_buffer="d['a\u05d0")
1388          self.assertIn("a\u05d0", matches)
1389  
1390          with greedy_completion():
1391              # query using escape
1392              _, matches = complete(line_buffer="d['a\\u05d0")
1393              self.assertIn("d['a\\u05d0']", matches)  # tokenized after \\
1394  
1395              # query using character
1396              _, matches = complete(line_buffer="d['a\u05d0")
1397              self.assertIn("d['a\u05d0']", matches)
1398  
1399      @dec.skip_without("numpy")
1400      def test_struct_array_key_completion(self):
1401          """Test dict key completion applies to numpy struct arrays"""
1402          import numpy
1403  
1404          ip = get_ipython()
1405          complete = ip.Completer.complete
1406          ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1407          _, matches = complete(line_buffer="d['")
1408          self.assertIn("hello", matches)
1409          self.assertIn("world", matches)
1410          # complete on the numpy struct itself
1411          dt = numpy.dtype(
1412              [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1413          )
1414          x = numpy.zeros(2, dtype=dt)
1415          ip.user_ns["d"] = x[1]
1416          _, matches = complete(line_buffer="d['")
1417          self.assertIn("my_head", matches)
1418          self.assertIn("my_data", matches)
1419  
1420          def completes_on_nested():
1421              ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1422              _, matches = complete(line_buffer="d[1]['my_head']['")
1423              self.assertTrue(any(["my_dt" in m for m in matches]))
1424              self.assertTrue(any(["my_df" in m for m in matches]))
1425          # complete on a nested level
1426          with greedy_completion():
1427              completes_on_nested()
1428  
1429          with evaluation_policy("limited"):
1430              completes_on_nested()
1431  
1432          with evaluation_policy("minimal"):
1433              with pytest.raises(AssertionError):
1434                  completes_on_nested()
1435  
1436      @dec.skip_without("pandas")
1437      def test_dataframe_key_completion(self):
1438          """Test dict key completion applies to pandas DataFrames"""
1439          import pandas
1440  
1441          ip = get_ipython()
1442          complete = ip.Completer.complete
1443          ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1444          _, matches = complete(line_buffer="d['")
1445          self.assertIn("hello", matches)
1446          self.assertIn("world", matches)
1447          _, matches = complete(line_buffer="d.loc[:, '")
1448          self.assertIn("hello", matches)
1449          self.assertIn("world", matches)
1450          _, matches = complete(line_buffer="d.loc[1:, '")
1451          self.assertIn("hello", matches)
1452          _, matches = complete(line_buffer="d.loc[1:1, '")
1453          self.assertIn("hello", matches)
1454          _, matches = complete(line_buffer="d.loc[1:1:-1, '")
1455          self.assertIn("hello", matches)
1456          _, matches = complete(line_buffer="d.loc[::, '")
1457          self.assertIn("hello", matches)
1458  
1459      def test_dict_key_completion_invalids(self):
1460          """Smoke test cases dict key completion can't handle"""
1461          ip = get_ipython()
1462          complete = ip.Completer.complete
1463  
1464          ip.user_ns["no_getitem"] = None
1465          ip.user_ns["no_keys"] = []
1466          ip.user_ns["cant_call_keys"] = dict
1467          ip.user_ns["empty"] = {}
1468          ip.user_ns["d"] = {"abc": 5}
1469  
1470          _, matches = complete(line_buffer="no_getitem['")
1471          _, matches = complete(line_buffer="no_keys['")
1472          _, matches = complete(line_buffer="cant_call_keys['")
1473          _, matches = complete(line_buffer="empty['")
1474          _, matches = complete(line_buffer="name_error['")
1475          _, matches = complete(line_buffer="d['\\")  # incomplete escape
1476  
1477      def test_object_key_completion(self):
1478          ip = get_ipython()
1479          ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1480  
1481          _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1482          self.assertIn("qwerty", matches)
1483          self.assertIn("qwick", matches)
1484  
1485      def test_class_key_completion(self):
1486          ip = get_ipython()
1487          NamedInstanceClass("qwerty")
1488          NamedInstanceClass("qwick")
1489          ip.user_ns["named_instance_class"] = NamedInstanceClass
1490  
1491          _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1492          self.assertIn("qwerty", matches)
1493          self.assertIn("qwick", matches)
1494  
1495      def test_tryimport(self):
1496          """
1497          Test that try-import don't crash on trailing dot, and import modules before
1498          """
1499          from IPython.core.completerlib import try_import
1500  
1501          assert try_import("IPython.")
1502  
1503      def test_aimport_module_completer(self):
1504          ip = get_ipython()
1505          _, matches = ip.complete("i", "%aimport i")
1506          self.assertIn("io", matches)
1507          self.assertNotIn("int", matches)
1508  
1509      def test_nested_import_module_completer(self):
1510          ip = get_ipython()
1511          _, matches = ip.complete(None, "import IPython.co", 17)
1512          self.assertIn("IPython.core", matches)
1513          self.assertNotIn("import IPython.core", matches)
1514          self.assertNotIn("IPython.display", matches)
1515  
1516      def test_import_module_completer(self):
1517          ip = get_ipython()
1518          _, matches = ip.complete("i", "import i")
1519          self.assertIn("io", matches)
1520          self.assertNotIn("int", matches)
1521  
1522      def test_from_module_completer(self):
1523          ip = get_ipython()
1524          _, matches = ip.complete("B", "from io import B", 16)
1525          self.assertIn("BytesIO", matches)
1526          self.assertNotIn("BaseException", matches)
1527  
1528      def test_snake_case_completion(self):
1529          ip = get_ipython()
1530          ip.Completer.use_jedi = False
1531          ip.user_ns["some_three"] = 3
1532          ip.user_ns["some_four"] = 4
1533          _, matches = ip.complete("s_", "print(s_f")
1534          self.assertIn("some_three", matches)
1535          self.assertIn("some_four", matches)
1536  
1537      def test_mix_terms(self):
1538          ip = get_ipython()
1539          from textwrap import dedent
1540  
1541          ip.Completer.use_jedi = False
1542          ip.ex(
1543              dedent(
1544                  """
1545              class Test:
1546                  def meth(self, meth_arg1):
1547                      print("meth")
1548  
1549                  def meth_1(self, meth1_arg1, meth1_arg2):
1550                      print("meth1")
1551  
1552                  def meth_2(self, meth2_arg1, meth2_arg2):
1553                      print("meth2")
1554              test = Test()
1555              """
1556              )
1557          )
1558          _, matches = ip.complete(None, "test.meth(")
1559          self.assertIn("meth_arg1=", matches)
1560          self.assertNotIn("meth2_arg1=", matches)
1561  
1562      def test_percent_symbol_restrict_to_magic_completions(self):
1563          ip = get_ipython()
1564          completer = ip.Completer
1565          text = "%a"
1566  
1567          with provisionalcompleter():
1568              completer.use_jedi = True
1569              completions = completer.completions(text, len(text))
1570              for c in completions:
1571                  self.assertEqual(c.text[0], "%")
1572  
1573      def test_fwd_unicode_restricts(self):
1574          ip = get_ipython()
1575          completer = ip.Completer
1576          text = "\\ROMAN NUMERAL FIVE"
1577  
1578          with provisionalcompleter():
1579              completer.use_jedi = True
1580              completions = [
1581                  completion.text for completion in completer.completions(text, len(text))
1582              ]
1583              self.assertEqual(completions, ["\u2164"])
1584  
1585      def test_dict_key_restrict_to_dicts(self):
1586          """Test that dict key suppresses non-dict completion items"""
1587          ip = get_ipython()
1588          c = ip.Completer
1589          d = {"abc": None}
1590          ip.user_ns["d"] = d
1591  
1592          text = 'd["a'
1593  
1594          def _():
1595              with provisionalcompleter():
1596                  c.use_jedi = True
1597                  return [
1598                      completion.text for completion in c.completions(text, len(text))
1599                  ]
1600  
1601          completions = _()
1602          self.assertEqual(completions, ["abc"])
1603  
1604          # check that it can be disabled in granular manner:
1605          cfg = Config()
1606          cfg.IPCompleter.suppress_competing_matchers = {
1607              "IPCompleter.dict_key_matcher": False
1608          }
1609          c.update_config(cfg)
1610  
1611          completions = _()
1612          self.assertIn("abc", completions)
1613          self.assertGreater(len(completions), 1)
1614  
1615      def test_matcher_suppression(self):
1616          @completion_matcher(identifier="a_matcher")
1617          def a_matcher(text):
1618              return ["completion_a"]
1619  
1620          @completion_matcher(identifier="b_matcher", api_version=2)
1621          def b_matcher(context: CompletionContext):
1622              text = context.token
1623              result = {"completions": [SimpleCompletion("completion_b")]}
1624  
1625              if text == "suppress c":
1626                  result["suppress"] = {"c_matcher"}
1627  
1628              if text.startswith("suppress all"):
1629                  result["suppress"] = True
1630                  if text == "suppress all but c":
1631                      result["do_not_suppress"] = {"c_matcher"}
1632                  if text == "suppress all but a":
1633                      result["do_not_suppress"] = {"a_matcher"}
1634  
1635              return result
1636  
1637          @completion_matcher(identifier="c_matcher")
1638          def c_matcher(text):
1639              return ["completion_c"]
1640  
1641          with custom_matchers([a_matcher, b_matcher, c_matcher]):
1642              ip = get_ipython()
1643              c = ip.Completer
1644  
1645              def _(text, expected):
1646                  c.use_jedi = False
1647                  s, matches = c.complete(text)
1648                  self.assertEqual(expected, matches)
1649  
1650              _("do not suppress", ["completion_a", "completion_b", "completion_c"])
1651              _("suppress all", ["completion_b"])
1652              _("suppress all but a", ["completion_a", "completion_b"])
1653              _("suppress all but c", ["completion_b", "completion_c"])
1654  
1655              def configure(suppression_config):
1656                  cfg = Config()
1657                  cfg.IPCompleter.suppress_competing_matchers = suppression_config
1658                  c.update_config(cfg)
1659  
1660              # test that configuration takes priority over the run-time decisions
1661  
1662              configure(False)
1663              _("suppress all", ["completion_a", "completion_b", "completion_c"])
1664  
1665              configure({"b_matcher": False})
1666              _("suppress all", ["completion_a", "completion_b", "completion_c"])
1667  
1668              configure({"a_matcher": False})
1669              _("suppress all", ["completion_b"])
1670  
1671              configure({"b_matcher": True})
1672              _("do not suppress", ["completion_b"])
1673  
1674              configure(True)
1675              _("do not suppress", ["completion_a"])
1676  
1677      def test_matcher_suppression_with_iterator(self):
1678          @completion_matcher(identifier="matcher_returning_iterator")
1679          def matcher_returning_iterator(text):
1680              return iter(["completion_iter"])
1681  
1682          @completion_matcher(identifier="matcher_returning_list")
1683          def matcher_returning_list(text):
1684              return ["completion_list"]
1685  
1686          with custom_matchers([matcher_returning_iterator, matcher_returning_list]):
1687              ip = get_ipython()
1688              c = ip.Completer
1689  
1690              def _(text, expected):
1691                  c.use_jedi = False
1692                  s, matches = c.complete(text)
1693                  self.assertEqual(expected, matches)
1694  
1695              def configure(suppression_config):
1696                  cfg = Config()
1697                  cfg.IPCompleter.suppress_competing_matchers = suppression_config
1698                  c.update_config(cfg)
1699  
1700              configure(False)
1701              _("---", ["completion_iter", "completion_list"])
1702  
1703              configure(True)
1704              _("---", ["completion_iter"])
1705  
1706              configure(None)
1707              _("--", ["completion_iter", "completion_list"])
1708  
1709      @pytest.mark.xfail(
1710          sys.version_info.releaselevel in ("alpha",),
1711          reason="Parso does not yet parse 3.13",
1712      )
1713      def test_matcher_suppression_with_jedi(self):
1714          ip = get_ipython()
1715          c = ip.Completer
1716          c.use_jedi = True
1717  
1718          def configure(suppression_config):
1719              cfg = Config()
1720              cfg.IPCompleter.suppress_competing_matchers = suppression_config
1721              c.update_config(cfg)
1722  
1723          def _():
1724              with provisionalcompleter():
1725                  matches = [completion.text for completion in c.completions("dict.", 5)]
1726                  self.assertIn("keys", matches)
1727  
1728          configure(False)
1729          _()
1730  
1731          configure(True)
1732          _()
1733  
1734          configure(None)
1735          _()
1736  
1737      def test_matcher_disabling(self):
1738          @completion_matcher(identifier="a_matcher")
1739          def a_matcher(text):
1740              return ["completion_a"]
1741  
1742          @completion_matcher(identifier="b_matcher")
1743          def b_matcher(text):
1744              return ["completion_b"]
1745  
1746          def _(expected):
1747              s, matches = c.complete("completion_")
1748              self.assertEqual(expected, matches)
1749  
1750          with custom_matchers([a_matcher, b_matcher]):
1751              ip = get_ipython()
1752              c = ip.Completer
1753  
1754              _(["completion_a", "completion_b"])
1755  
1756              cfg = Config()
1757              cfg.IPCompleter.disable_matchers = ["b_matcher"]
1758              c.update_config(cfg)
1759  
1760              _(["completion_a"])
1761  
1762              cfg.IPCompleter.disable_matchers = []
1763              c.update_config(cfg)
1764  
1765      def test_matcher_priority(self):
1766          @completion_matcher(identifier="a_matcher", priority=0, api_version=2)
1767          def a_matcher(text):
1768              return {"completions": [SimpleCompletion("completion_a")], "suppress": True}
1769  
1770          @completion_matcher(identifier="b_matcher", priority=2, api_version=2)
1771          def b_matcher(text):
1772              return {"completions": [SimpleCompletion("completion_b")], "suppress": True}
1773  
1774          def _(expected):
1775              s, matches = c.complete("completion_")
1776              self.assertEqual(expected, matches)
1777  
1778          with custom_matchers([a_matcher, b_matcher]):
1779              ip = get_ipython()
1780              c = ip.Completer
1781  
1782              _(["completion_b"])
1783              a_matcher.matcher_priority = 3
1784              _(["completion_a"])
1785  
1786  
1787  @pytest.mark.parametrize(
1788      "line,expected",
1789      [
1790          # Basic test cases
1791          ("np.", "attribute"),
1792          ("np.ran", "attribute"),
1793          ("np.random.rand(np.random.ran", "attribute"),
1794          ("np.random.rand(n", "global"),
1795          ("d['k.e.y.'](ran", "global"),
1796          ("d[0].k", "attribute"),
1797          ("a = { 'a': np.ran", "attribute"),
1798          ("n", "global"),
1799          ("", "global"),
1800          # Dots in string literals
1801          ('some_var = "this is a string with a dot.', "global"),
1802          ("text = 'another string with a dot.", "global"),
1803          ('f"greeting {user.na', "attribute"),  # Cursor in f-string expression
1804          ('t"welcome {guest.na', "attribute"),  # Cursor in t-string expression
1805          ('f"hello {name} worl', "global"),  # Cursor in f-string outside expression
1806          ('f"hello {{a.', "global"),
1807          ('f"hello {{{a.', "attribute"),
1808          # Backslash escapes in strings
1809          ('var = "string with \\"escaped quote and a dot.', "global"),
1810          ("escaped = 'single \\'quote\\' with a dot.", "global"),
1811          # Multi-line strings
1812          ('multi = """This is line one\nwith a dot.', "global"),
1813          ("multi_single = '''Another\nmulti-line\nwith a dot.", "global"),
1814          # Inline comments
1815          ("x = 5  # This is a comment", "global"),
1816          ("y = obj.method()  # Comment after dot.method", "global"),
1817          # Hash symbol within string literals should not be treated as comments
1818          ("d['#'] = np.", "attribute"),
1819          # Nested parentheses with dots
1820          ("complex_expr = (func((obj.method(param.attr", "attribute"),
1821          ("multiple_nesting = {key: [value.attr", "attribute"),
1822          # Numbers
1823          ("3.", "global"),
1824          ("3.14", "global"),
1825          ("-42.14", "global"),
1826          ("x = func(3.14", "global"),
1827          ("x = func(a3.", "attribute"),
1828          ("x = func(a3.12", "global"),
1829          ("3.1.", "attribute"),
1830          ("-3.1.", "attribute"),
1831          ("(3).", "attribute"),
1832          # Additional cases
1833          ("", "global"),
1834          ('str_with_code = "x.attr', "global"),
1835          ('f"formatted {obj.attr', "attribute"),
1836          ('f"formatted {obj.attr}', "global"),
1837          ("dict_with_dots = {'key.with.dots': value.attr", "attribute"),
1838          ("d[f'{a}']['{a.", "global"),
1839      ],
1840  )
1841  def test_completion_context(line, expected):
1842      """Test completion context"""
1843      ip = get_ipython()
1844      get_context = ip.Completer._determine_completion_context
1845      result = get_context(line)
1846      assert result.value == expected, f"Failed on input: '{line}'"
1847  
1848  
1849  @pytest.mark.xfail(reason="Completion context not yet supported")
1850  @pytest.mark.parametrize(
1851      "line, expected",
1852      [
1853          ("f'{f'a.", "global"),  # Nested f-string
1854          ("3a.", "global"),  # names starting with numbers or other symbols
1855          ("$).", "global"),  # random things with dot at end
1856      ],
1857  )
1858  def test_unsupported_completion_context(line, expected):
1859      """Test unsupported completion context"""
1860      ip = get_ipython()
1861      get_context = ip.Completer._determine_completion_context
1862      result = get_context(line)
1863      assert result.value == expected, f"Failed on input: '{line}'"
1864  
1865  
1866  @pytest.mark.parametrize(
1867      "setup,code,expected,not_expected",
1868      [
1869          ('a="str"; b=1', "(a, b.", [".bit_count", ".conjugate"], [".count"]),
1870          ('a="str"; b=1', "(a, b).", [".count"], [".bit_count", ".capitalize"]),
1871          ('x="str"; y=1', "x = {1, y.", [".bit_count"], [".count"]),
1872          ('x="str"; y=1', "x = [1, y.", [".bit_count"], [".count"]),
1873          ('x="str"; y=1; fun=lambda x:x', "x = fun(1, y.", [".bit_count"], [".count"]),
1874      ],
1875  )
1876  def test_misc_no_jedi_completions(setup, code, expected, not_expected):
1877      ip = get_ipython()
1878      c = ip.Completer
1879      ip.ex(setup)
1880      with provisionalcompleter(), jedi_status(False):
1881          matches = c.all_completions(code)
1882          assert set(expected) - set(matches) == set(), set(matches)
1883          assert set(matches).intersection(set(not_expected)) == set()
1884  
1885  
1886  @pytest.mark.parametrize(
1887      "code,expected",
1888      [
1889          (" (a, b", "b"),
1890          ("(a, b", "b"),
1891          ("(a, b)", ""),  # trim always start by trimming
1892          (" (a, b)", "(a, b)"),
1893          (" [a, b]", "[a, b]"),
1894          (" a, b", "b"),
1895          ("x = {1, y", "y"),
1896          ("x = [1, y", "y"),
1897          ("x = fun(1, y", "y"),
1898      ],
1899  )
1900  def test_trim_expr(code, expected):
1901      c = get_ipython().Completer
1902      assert c._trim_expr(code) == expected
1903  
1904  
1905  @pytest.mark.parametrize(
1906      "input, expected",
1907      [
1908          ["1.234", "1.234"],
1909          # should match signed numbers
1910          ["+1", "+1"],
1911          ["-1", "-1"],
1912          ["-1.0", "-1.0"],
1913          ["-1.", "-1."],
1914          ["+1.", "+1."],
1915          [".1", ".1"],
1916          # should not match non-numbers
1917          ["1..", None],
1918          ["..", None],
1919          [".1.", None],
1920          # should match after comma
1921          [",1", "1"],
1922          [", 1", "1"],
1923          [", .1", ".1"],
1924          [", +.1", "+.1"],
1925          # should not match after trailing spaces
1926          [".1 ", None],
1927          # some complex cases
1928          ["0b_0011_1111_0100_1110", "0b_0011_1111_0100_1110"],
1929          ["0xdeadbeef", "0xdeadbeef"],
1930          ["0b_1110_0101", "0b_1110_0101"],
1931          # should not match if in an operation
1932          ["1 + 1", None],
1933          [", 1 + 1", None],
1934      ],
1935  )
1936  def test_match_numeric_literal_for_dict_key(input, expected):
1937      assert _match_number_in_dict_key_prefix(input) == expected
1938