blob: f3e404da6f0b87906f238d35bfdad62cfc560420 [file] [log] [blame]
Ronald Oussoren2efd9242009-09-20 14:53:22 +00001"""
2Very minimal unittests for parts of the readline module.
Ronald Oussoren2efd9242009-09-20 14:53:22 +00003"""
Martin Panter37126862016-05-15 03:05:36 +00004from contextlib import ExitStack
Martin Panterf0dbf7a2016-05-15 01:26:25 +00005from errno import EIO
Victor Stinnerc495e792018-01-16 17:34:34 +01006import locale
Victor Stinnera3c80ce2014-07-24 12:23:56 +02007import os
Martin Panterf0dbf7a2016-05-15 01:26:25 +00008import selectors
9import subprocess
10import sys
Benjamin Peterson33f8f152014-11-26 13:58:16 -060011import tempfile
Ronald Oussoren2efd9242009-09-20 14:53:22 +000012import unittest
Hai Shi847f94f2020-06-26 01:17:57 +080013from test.support import verbose
14from test.support.import_helper import import_module
15from test.support.os_helper import unlink, temp_dir, TESTFN
Berker Peksagce643912015-05-06 06:33:17 +030016from test.support.script_helper import assert_python_ok
Ronald Oussoren2efd9242009-09-20 14:53:22 +000017
Mark Dickinson2d7062e2009-10-26 12:01:06 +000018# Skip tests if there is no readline module
19readline = import_module('readline')
Ronald Oussoren2efd9242009-09-20 14:53:22 +000020
Victor Stinner1881bef2017-07-07 16:06:58 +020021if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
22 is_editline = ("EditLine wrapper" in readline._READLINE_LIBRARY_VERSION)
23else:
24 is_editline = (readline.__doc__ and "libedit" in readline.__doc__)
25
26
27def 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
Martin Panter056f76d2016-06-14 05:45:31 +000038
Martin Panterf00c49d2016-06-14 01:16:16 +000039@unittest.skipUnless(hasattr(readline, "clear_history"),
40 "The history update test cannot be run because the "
41 "clear_history method is not available.")
Ronald Oussoren2efd9242009-09-20 14:53:22 +000042class TestHistoryManipulation (unittest.TestCase):
Victor Stinnera3c80ce2014-07-24 12:23:56 +020043 """
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 """
R David Murrayf8b9dfd2011-03-14 17:10:22 -040048
Ronald Oussoren2efd9242009-09-20 14:53:22 +000049 def testHistoryUpdates(self):
Georg Brandl7b250a52012-08-11 11:02:14 +020050 readline.clear_history()
Ronald Oussoren2efd9242009-09-20 14:53:22 +000051
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
Ned Deily8007cbc2014-11-26 13:02:33 -080072 @unittest.skipUnless(hasattr(readline, "append_history_file"),
Benjamin Petersond1e22ba2014-11-26 14:35:12 -060073 "append_history not available")
Benjamin Peterson33f8f152014-11-26 13:58:16 -060074 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)
Gregory P. Smithfd053fd2021-02-12 12:04:46 -0800105 try:
Benjamin Peterson33f8f152014-11-26 13:58:16 -0600106 readline.append_history_file(1, hfilename)
Gregory P. Smithfd053fd2021-02-12 12:04:46 -0800107 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.
Benjamin Peterson33f8f152014-11-26 13:58:16 -0600114
115 # write_history_file can create the target
116 readline.write_history_file(hfilename)
117
Martin Panterf00c49d2016-06-14 01:16:16 +0000118 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)
Martin Panter056f76d2016-06-14 05:45:31 +0000130 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")
Martin Panterf00c49d2016-06-14 01:16:16 +0000134 self.assertEqual(readline.get_history_item(1), "entrée 1")
135 self.assertEqual(readline.get_history_item(2), "entrée 22")
136
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000137
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200138class TestReadline(unittest.TestCase):
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100139
Martin Panterc427b8d2016-08-27 03:23:11 +0000140 @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100141 "not supported in this library version")
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200142 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
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000150 auto_history_script = """\
151import readline
152readline.set_auto_history({})
153input()
154print("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 self.assertIn(b"History length: 1\r\n", output)
160
161 def test_auto_history_disabled(self):
162 output = run_pty(self.auto_history_script.format(False))
163 self.assertIn(b"History length: 0\r\n", output)
164
Martin Panterf00c49d2016-06-14 01:16:16 +0000165 def test_nonascii(self):
Victor Stinnerc495e792018-01-16 17:34:34 +0100166 loc = locale.setlocale(locale.LC_CTYPE, None)
167 if loc in ('C', 'POSIX'):
168 # bpo-29240: On FreeBSD, if the LC_CTYPE locale is C or POSIX,
169 # writing and reading non-ASCII bytes into/from a TTY works, but
170 # readline or ncurses ignores non-ASCII bytes on read.
171 self.skipTest(f"the LC_CTYPE locale is {loc!r}")
172
Martin Panterf00c49d2016-06-14 01:16:16 +0000173 try:
174 readline.add_history("\xEB\xEF")
175 except UnicodeEncodeError as err:
176 self.skipTest("Locale cannot encode test data: " + format(err))
177
178 script = r"""import readline
179
Martin Panter6afbc652016-06-14 08:45:43 +0000180is_editline = readline.__doc__ and "libedit" in readline.__doc__
181inserted = "[\xEFnserted]"
182macro = "|t\xEB[after]"
183set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
184if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000185 # The insert_line() call via pre_input_hook() does nothing with Editline,
186 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000187 macro = inserted + macro
188
189if is_editline:
190 readline.parse_and_bind(r'bind ^B ed-prev-char')
191 readline.parse_and_bind(r'bind "\t" rl_complete')
192 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000193else:
194 readline.parse_and_bind(r'Control-b: backward-char')
195 readline.parse_and_bind(r'"\t": complete')
196 readline.parse_and_bind(r'set disable-completion off')
197 readline.parse_and_bind(r'set show-all-if-ambiguous off')
198 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000199 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000200
201def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000202 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000203 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000204if set_pre_input_hook:
205 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000206
207def completer(text, state):
208 if text == "t\xEB":
209 if state == 0:
210 print("text", ascii(text))
211 print("line", ascii(readline.get_line_buffer()))
212 print("indexes", readline.get_begidx(), readline.get_endidx())
213 return "t\xEBnt"
214 if state == 1:
215 return "t\xEBxt"
216 if text == "t\xEBx" and state == 0:
217 return "t\xEBxt"
218 return None
219readline.set_completer(completer)
220
221def display(substitution, matches, longest_match_length):
222 print("substitution", ascii(substitution))
223 print("matches", ascii(matches))
224readline.set_completion_display_matches_hook(display)
225
226print("result", ascii(input()))
227print("history", ascii(readline.get_history_item(1)))
228"""
229
230 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
231 input += b"\x02" * len("[after]") # Move cursor back
232 input += b"\t\t" # Display possible completions
233 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
234 input += b"\r"
235 output = run_pty(script, input)
236 self.assertIn(b"text 't\\xeb'\r\n", output)
237 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
Gregory P. Smithfd053fd2021-02-12 12:04:46 -0800238 if sys.platform == "darwin" or not is_editline:
239 self.assertIn(b"indexes 11 13\r\n", output)
240 # Non-macOS libedit does not handle non-ASCII bytes
241 # the same way and generates character indices
242 # rather than byte indices via get_begidx() and
243 # get_endidx(). Ex: libedit2 3.1-20191231-2 on Debian
244 # winds up with "indexes 10 12". Stemming from the
245 # start and end values calls back into readline.c's
246 # rl_attempted_completion_function = flex_complete with:
247 # (11, 13) instead of libreadline's (12, 15).
248
Martin Pantera8cadb22016-06-14 11:29:31 +0000249 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000250 self.assertIn(b"substitution 't\\xeb'\r\n", output)
251 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000252 expected = br"'[\xefnserted]|t\xebxt[after]'"
253 self.assertIn(b"result " + expected + b"\r\n", output)
254 self.assertIn(b"history " + expected + b"\r\n", output)
255
Nir Sofferaa6a4d62017-07-08 17:34:27 +0300256 # We have 2 reasons to skip this test:
257 # - readline: history size was added in 6.0
258 # See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
259 # - editline: history size is broken on OS X 10.11.6.
260 # Newer versions were not tested yet.
261 @unittest.skipIf(readline._READLINE_VERSION < 0x600,
262 "this readline version does not support history-size")
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300263 @unittest.skipIf(is_editline,
264 "editline history size configuration is broken")
265 def test_history_size(self):
266 history_size = 10
267 with temp_dir() as test_dir:
268 inputrc = os.path.join(test_dir, "inputrc")
269 with open(inputrc, "wb") as f:
270 f.write(b"set history-size %d\n" % history_size)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000271
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300272 history_file = os.path.join(test_dir, "history")
273 with open(history_file, "wb") as f:
274 # history_size * 2 items crashes readline
275 data = b"".join(b"item %d\n" % i
276 for i in range(history_size * 2))
277 f.write(data)
278
279 script = """
280import os
281import readline
282
283history_file = os.environ["HISTORY_FILE"]
284readline.read_history_file(history_file)
285input()
286readline.write_history_file(history_file)
287"""
288
289 env = dict(os.environ)
290 env["INPUTRC"] = inputrc
291 env["HISTORY_FILE"] = history_file
292
293 run_pty(script, input=b"last input\r", env=env)
294
295 with open(history_file, "rb") as f:
296 lines = f.readlines()
297 self.assertEqual(len(lines), history_size)
298 self.assertEqual(lines[-1].strip(), b"last input")
299
300
301def run_pty(script, input=b"dummy input\r", env=None):
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000302 pty = import_module('pty')
303 output = bytearray()
304 [master, slave] = pty.openpty()
305 args = (sys.executable, '-c', script)
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300306 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000307 os.close(slave)
Martin Panter37126862016-05-15 03:05:36 +0000308 with ExitStack() as cleanup:
309 cleanup.enter_context(proc)
Martin Panter79f561d2016-05-15 15:04:58 +0000310 def terminate(proc):
311 try:
312 proc.terminate()
313 except ProcessLookupError:
314 # Workaround for Open/Net BSD bug (Issue 16762)
315 pass
316 cleanup.callback(terminate, proc)
Martin Panter37126862016-05-15 03:05:36 +0000317 cleanup.callback(os.close, master)
Martin Panter79f561d2016-05-15 15:04:58 +0000318 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
319 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
320 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
321 # either (Issue 20472). Hopefully the file descriptor is low enough
322 # to use with select().
323 sel = cleanup.enter_context(selectors.SelectSelector())
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000324 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
325 os.set_blocking(master, False)
326 while True:
327 for [_, events] in sel.select():
328 if events & selectors.EVENT_READ:
329 try:
330 chunk = os.read(master, 0x10000)
331 except OSError as err:
Martin Panterf00c49d2016-06-14 01:16:16 +0000332 # Linux raises EIO when slave is closed (Issue 5380)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000333 if err.errno != EIO:
334 raise
335 chunk = b""
336 if not chunk:
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000337 return output
338 output.extend(chunk)
339 if events & selectors.EVENT_WRITE:
Martin Panterf00c49d2016-06-14 01:16:16 +0000340 try:
341 input = input[os.write(master, input):]
342 except OSError as err:
343 # Apparently EIO means the slave was closed
344 if err.errno != EIO:
345 raise
346 input = b"" # Stop writing
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000347 if not input:
348 sel.modify(master, selectors.EVENT_READ)
349
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200350
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000351if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500352 unittest.main()