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