(root)/
Python-3.11.7/
Lib/
test/
test_readline.py
       1  """
       2  Very minimal unittests for parts of the readline module.
       3  """
       4  import locale
       5  import os
       6  import sys
       7  import tempfile
       8  import textwrap
       9  import unittest
      10  from test.support import verbose
      11  from test.support.import_helper import import_module
      12  from test.support.os_helper import unlink, temp_dir, TESTFN
      13  from test.support.pty_helper import run_pty
      14  from test.support.script_helper import assert_python_ok
      15  
      16  # Skip tests if there is no readline module
      17  readline = import_module('readline')
      18  
      19  if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
      20      is_editline = ("EditLine wrapper" in readline._READLINE_LIBRARY_VERSION)
      21  else:
      22      is_editline = (readline.__doc__ and "libedit" in readline.__doc__)
      23  
      24  
      25  def setUpModule():
      26      if verbose:
      27          # Python implementations other than CPython may not have
      28          # these private attributes
      29          if hasattr(readline, "_READLINE_VERSION"):
      30              print(f"readline version: {readline._READLINE_VERSION:#x}")
      31              print(f"readline runtime version: {readline._READLINE_RUNTIME_VERSION:#x}")
      32          if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
      33              print(f"readline library version: {readline._READLINE_LIBRARY_VERSION!r}")
      34          print(f"use libedit emulation? {is_editline}")
      35  
      36  
      37  @unittest.skipUnless(hasattr(readline, "clear_history"),
      38                       "The history update test cannot be run because the "
      39                       "clear_history method is not available.")
      40  class ESC[4;38;5;81mTestHistoryManipulation (ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      41      """
      42      These tests were added to check that the libedit emulation on OSX and the
      43      "real" readline have the same interface for history manipulation. That's
      44      why the tests cover only a small subset of the interface.
      45      """
      46  
      47      def testHistoryUpdates(self):
      48          readline.clear_history()
      49  
      50          readline.add_history("first line")
      51          readline.add_history("second line")
      52  
      53          self.assertEqual(readline.get_history_item(0), None)
      54          self.assertEqual(readline.get_history_item(1), "first line")
      55          self.assertEqual(readline.get_history_item(2), "second line")
      56  
      57          readline.replace_history_item(0, "replaced line")
      58          self.assertEqual(readline.get_history_item(0), None)
      59          self.assertEqual(readline.get_history_item(1), "replaced line")
      60          self.assertEqual(readline.get_history_item(2), "second line")
      61  
      62          self.assertEqual(readline.get_current_history_length(), 2)
      63  
      64          readline.remove_history_item(0)
      65          self.assertEqual(readline.get_history_item(0), None)
      66          self.assertEqual(readline.get_history_item(1), "second line")
      67  
      68          self.assertEqual(readline.get_current_history_length(), 1)
      69  
      70      @unittest.skipUnless(hasattr(readline, "append_history_file"),
      71                           "append_history not available")
      72      def test_write_read_append(self):
      73          hfile = tempfile.NamedTemporaryFile(delete=False)
      74          hfile.close()
      75          hfilename = hfile.name
      76          self.addCleanup(unlink, hfilename)
      77  
      78          # test write-clear-read == nop
      79          readline.clear_history()
      80          readline.add_history("first line")
      81          readline.add_history("second line")
      82          readline.write_history_file(hfilename)
      83  
      84          readline.clear_history()
      85          self.assertEqual(readline.get_current_history_length(), 0)
      86  
      87          readline.read_history_file(hfilename)
      88          self.assertEqual(readline.get_current_history_length(), 2)
      89          self.assertEqual(readline.get_history_item(1), "first line")
      90          self.assertEqual(readline.get_history_item(2), "second line")
      91  
      92          # test append
      93          readline.append_history_file(1, hfilename)
      94          readline.clear_history()
      95          readline.read_history_file(hfilename)
      96          self.assertEqual(readline.get_current_history_length(), 3)
      97          self.assertEqual(readline.get_history_item(1), "first line")
      98          self.assertEqual(readline.get_history_item(2), "second line")
      99          self.assertEqual(readline.get_history_item(3), "second line")
     100  
     101          # test 'no such file' behaviour
     102          os.unlink(hfilename)
     103          try:
     104              readline.append_history_file(1, hfilename)
     105          except FileNotFoundError:
     106              pass  # Some implementations return this error (libreadline).
     107          else:
     108              os.unlink(hfilename)  # Some create it anyways (libedit).
     109              # If the file wasn't created, unlink will fail.
     110          # We're just testing that one of the two expected behaviors happens
     111          # instead of an incorrect error.
     112  
     113          # write_history_file can create the target
     114          readline.write_history_file(hfilename)
     115  
     116      def test_nonascii_history(self):
     117          readline.clear_history()
     118          try:
     119              readline.add_history("entrée 1")
     120          except UnicodeEncodeError as err:
     121              self.skipTest("Locale cannot encode test data: " + format(err))
     122          readline.add_history("entrée 2")
     123          readline.replace_history_item(1, "entrée 22")
     124          readline.write_history_file(TESTFN)
     125          self.addCleanup(os.remove, TESTFN)
     126          readline.clear_history()
     127          readline.read_history_file(TESTFN)
     128          if is_editline:
     129              # An add_history() call seems to be required for get_history_
     130              # item() to register items from the file
     131              readline.add_history("dummy")
     132          self.assertEqual(readline.get_history_item(1), "entrée 1")
     133          self.assertEqual(readline.get_history_item(2), "entrée 22")
     134  
     135  
     136  class ESC[4;38;5;81mTestReadline(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     137  
     138      @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
     139                       "not supported in this library version")
     140      def test_init(self):
     141          # Issue #19884: Ensure that the ANSI sequence "\033[1034h" is not
     142          # written into stdout when the readline module is imported and stdout
     143          # is redirected to a pipe.
     144          rc, stdout, stderr = assert_python_ok('-c', 'import readline',
     145                                                TERM='xterm-256color')
     146          self.assertEqual(stdout, b'')
     147  
     148      auto_history_script = """\
     149  import readline
     150  readline.set_auto_history({})
     151  input()
     152  print("History length:", readline.get_current_history_length())
     153  """
     154  
     155      def test_auto_history_enabled(self):
     156          output = run_pty(self.auto_history_script.format(True))
     157          # bpo-44949: Sometimes, the newline character is not written at the
     158          # end, so don't expect it in the output.
     159          self.assertIn(b"History length: 1", output)
     160  
     161      def test_auto_history_disabled(self):
     162          output = run_pty(self.auto_history_script.format(False))
     163          # bpo-44949: Sometimes, the newline character is not written at the
     164          # end, so don't expect it in the output.
     165          self.assertIn(b"History length: 0", output)
     166  
     167      def test_set_complete_delims(self):
     168          script = textwrap.dedent("""
     169              import readline
     170              def complete(text, state):
     171                  if state == 0 and text == "$":
     172                      return "$complete"
     173                  return None
     174              if "libedit" in getattr(readline, "__doc__", ""):
     175                  readline.parse_and_bind(r'bind "\\t" rl_complete')
     176              else:
     177                  readline.parse_and_bind(r'"\\t": complete')
     178              readline.set_completer_delims(" \\t\\n")
     179              readline.set_completer(complete)
     180              print(input())
     181          """)
     182  
     183          output = run_pty(script, input=b"$\t\n")
     184          self.assertIn(b"$complete", output)
     185  
     186      def test_nonascii(self):
     187          loc = locale.setlocale(locale.LC_CTYPE, None)
     188          if loc in ('C', 'POSIX'):
     189              # bpo-29240: On FreeBSD, if the LC_CTYPE locale is C or POSIX,
     190              # writing and reading non-ASCII bytes into/from a TTY works, but
     191              # readline or ncurses ignores non-ASCII bytes on read.
     192              self.skipTest(f"the LC_CTYPE locale is {loc!r}")
     193  
     194          try:
     195              readline.add_history("\xEB\xEF")
     196          except UnicodeEncodeError as err:
     197              self.skipTest("Locale cannot encode test data: " + format(err))
     198  
     199          script = r"""import readline
     200  
     201  is_editline = readline.__doc__ and "libedit" in readline.__doc__
     202  inserted = "[\xEFnserted]"
     203  macro = "|t\xEB[after]"
     204  set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
     205  if is_editline or not set_pre_input_hook:
     206      # The insert_line() call via pre_input_hook() does nothing with Editline,
     207      # so include the extra text that would have been inserted here
     208      macro = inserted + macro
     209  
     210  if is_editline:
     211      readline.parse_and_bind(r'bind ^B ed-prev-char')
     212      readline.parse_and_bind(r'bind "\t" rl_complete')
     213      readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
     214  else:
     215      readline.parse_and_bind(r'Control-b: backward-char')
     216      readline.parse_and_bind(r'"\t": complete')
     217      readline.parse_and_bind(r'set disable-completion off')
     218      readline.parse_and_bind(r'set show-all-if-ambiguous off')
     219      readline.parse_and_bind(r'set show-all-if-unmodified off')
     220      readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
     221  
     222  def pre_input_hook():
     223      readline.insert_text(inserted)
     224      readline.redisplay()
     225  if set_pre_input_hook:
     226      set_pre_input_hook(pre_input_hook)
     227  
     228  def completer(text, state):
     229      if text == "t\xEB":
     230          if state == 0:
     231              print("text", ascii(text))
     232              print("line", ascii(readline.get_line_buffer()))
     233              print("indexes", readline.get_begidx(), readline.get_endidx())
     234              return "t\xEBnt"
     235          if state == 1:
     236              return "t\xEBxt"
     237      if text == "t\xEBx" and state == 0:
     238          return "t\xEBxt"
     239      return None
     240  readline.set_completer(completer)
     241  
     242  def display(substitution, matches, longest_match_length):
     243      print("substitution", ascii(substitution))
     244      print("matches", ascii(matches))
     245  readline.set_completion_display_matches_hook(display)
     246  
     247  print("result", ascii(input()))
     248  print("history", ascii(readline.get_history_item(1)))
     249  """
     250  
     251          input = b"\x01"  # Ctrl-A, expands to "|t\xEB[after]"
     252          input += b"\x02" * len("[after]")  # Move cursor back
     253          input += b"\t\t"  # Display possible completions
     254          input += b"x\t"  # Complete "t\xEBx" -> "t\xEBxt"
     255          input += b"\r"
     256          output = run_pty(script, input)
     257          self.assertIn(b"text 't\\xeb'\r\n", output)
     258          self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
     259          if sys.platform == "darwin" or not is_editline:
     260              self.assertIn(b"indexes 11 13\r\n", output)
     261              # Non-macOS libedit does not handle non-ASCII bytes
     262              # the same way and generates character indices
     263              # rather than byte indices via get_begidx() and
     264              # get_endidx().  Ex: libedit2 3.1-20191231-2 on Debian
     265              # winds up with "indexes 10 12".  Stemming from the
     266              # start and end values calls back into readline.c's
     267              # rl_attempted_completion_function = flex_complete with:
     268              # (11, 13) instead of libreadline's (12, 15).
     269  
     270          if not is_editline and hasattr(readline, "set_pre_input_hook"):
     271              self.assertIn(b"substitution 't\\xeb'\r\n", output)
     272              self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
     273          expected = br"'[\xefnserted]|t\xebxt[after]'"
     274          self.assertIn(b"result " + expected + b"\r\n", output)
     275          # bpo-45195: Sometimes, the newline character is not written at the
     276          # end, so don't expect it in the output.
     277          self.assertIn(b"history " + expected, output)
     278  
     279      # We have 2 reasons to skip this test:
     280      # - readline: history size was added in 6.0
     281      #   See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
     282      # - editline: history size is broken on OS X 10.11.6.
     283      #   Newer versions were not tested yet.
     284      @unittest.skipIf(readline._READLINE_VERSION < 0x600,
     285                       "this readline version does not support history-size")
     286      @unittest.skipIf(is_editline,
     287                       "editline history size configuration is broken")
     288      def test_history_size(self):
     289          history_size = 10
     290          with temp_dir() as test_dir:
     291              inputrc = os.path.join(test_dir, "inputrc")
     292              with open(inputrc, "wb") as f:
     293                  f.write(b"set history-size %d\n" % history_size)
     294  
     295              history_file = os.path.join(test_dir, "history")
     296              with open(history_file, "wb") as f:
     297                  # history_size * 2 items crashes readline
     298                  data = b"".join(b"item %d\n" % i
     299                                  for i in range(history_size * 2))
     300                  f.write(data)
     301  
     302              script = """
     303  import os
     304  import readline
     305  
     306  history_file = os.environ["HISTORY_FILE"]
     307  readline.read_history_file(history_file)
     308  input()
     309  readline.write_history_file(history_file)
     310  """
     311  
     312              env = dict(os.environ)
     313              env["INPUTRC"] = inputrc
     314              env["HISTORY_FILE"] = history_file
     315  
     316              run_pty(script, input=b"last input\r", env=env)
     317  
     318              with open(history_file, "rb") as f:
     319                  lines = f.readlines()
     320              self.assertEqual(len(lines), history_size)
     321              self.assertEqual(lines[-1].strip(), b"last input")
     322  
     323  
     324  if __name__ == "__main__":
     325      unittest.main()