blob: de573bef9f9632fb8516a633fe73b9d0f4c75fda [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)
105 with self.assertRaises(FileNotFoundError):
106 readline.append_history_file(1, hfilename)
107
108 # write_history_file can create the target
109 readline.write_history_file(hfilename)
110
Martin Panterf00c49d2016-06-14 01:16:16 +0000111 def test_nonascii_history(self):
112 readline.clear_history()
113 try:
114 readline.add_history("entrée 1")
115 except UnicodeEncodeError as err:
116 self.skipTest("Locale cannot encode test data: " + format(err))
117 readline.add_history("entrée 2")
118 readline.replace_history_item(1, "entrée 22")
119 readline.write_history_file(TESTFN)
120 self.addCleanup(os.remove, TESTFN)
121 readline.clear_history()
122 readline.read_history_file(TESTFN)
Martin Panter056f76d2016-06-14 05:45:31 +0000123 if is_editline:
124 # An add_history() call seems to be required for get_history_
125 # item() to register items from the file
126 readline.add_history("dummy")
Martin Panterf00c49d2016-06-14 01:16:16 +0000127 self.assertEqual(readline.get_history_item(1), "entrée 1")
128 self.assertEqual(readline.get_history_item(2), "entrée 22")
129
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000130
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200131class TestReadline(unittest.TestCase):
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100132
Martin Panterc427b8d2016-08-27 03:23:11 +0000133 @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100134 "not supported in this library version")
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200135 def test_init(self):
136 # Issue #19884: Ensure that the ANSI sequence "\033[1034h" is not
137 # written into stdout when the readline module is imported and stdout
138 # is redirected to a pipe.
139 rc, stdout, stderr = assert_python_ok('-c', 'import readline',
140 TERM='xterm-256color')
141 self.assertEqual(stdout, b'')
142
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000143 auto_history_script = """\
144import readline
145readline.set_auto_history({})
146input()
147print("History length:", readline.get_current_history_length())
148"""
149
150 def test_auto_history_enabled(self):
151 output = run_pty(self.auto_history_script.format(True))
152 self.assertIn(b"History length: 1\r\n", output)
153
154 def test_auto_history_disabled(self):
155 output = run_pty(self.auto_history_script.format(False))
156 self.assertIn(b"History length: 0\r\n", output)
157
Martin Panterf00c49d2016-06-14 01:16:16 +0000158 def test_nonascii(self):
Victor Stinnerc495e792018-01-16 17:34:34 +0100159 loc = locale.setlocale(locale.LC_CTYPE, None)
160 if loc in ('C', 'POSIX'):
161 # bpo-29240: On FreeBSD, if the LC_CTYPE locale is C or POSIX,
162 # writing and reading non-ASCII bytes into/from a TTY works, but
163 # readline or ncurses ignores non-ASCII bytes on read.
164 self.skipTest(f"the LC_CTYPE locale is {loc!r}")
165
Martin Panterf00c49d2016-06-14 01:16:16 +0000166 try:
167 readline.add_history("\xEB\xEF")
168 except UnicodeEncodeError as err:
169 self.skipTest("Locale cannot encode test data: " + format(err))
170
171 script = r"""import readline
172
Martin Panter6afbc652016-06-14 08:45:43 +0000173is_editline = readline.__doc__ and "libedit" in readline.__doc__
174inserted = "[\xEFnserted]"
175macro = "|t\xEB[after]"
176set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
177if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000178 # The insert_line() call via pre_input_hook() does nothing with Editline,
179 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000180 macro = inserted + macro
181
182if is_editline:
183 readline.parse_and_bind(r'bind ^B ed-prev-char')
184 readline.parse_and_bind(r'bind "\t" rl_complete')
185 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000186else:
187 readline.parse_and_bind(r'Control-b: backward-char')
188 readline.parse_and_bind(r'"\t": complete')
189 readline.parse_and_bind(r'set disable-completion off')
190 readline.parse_and_bind(r'set show-all-if-ambiguous off')
191 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000192 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000193
194def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000195 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000196 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000197if set_pre_input_hook:
198 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000199
200def completer(text, state):
201 if text == "t\xEB":
202 if state == 0:
203 print("text", ascii(text))
204 print("line", ascii(readline.get_line_buffer()))
205 print("indexes", readline.get_begidx(), readline.get_endidx())
206 return "t\xEBnt"
207 if state == 1:
208 return "t\xEBxt"
209 if text == "t\xEBx" and state == 0:
210 return "t\xEBxt"
211 return None
212readline.set_completer(completer)
213
214def display(substitution, matches, longest_match_length):
215 print("substitution", ascii(substitution))
216 print("matches", ascii(matches))
217readline.set_completion_display_matches_hook(display)
218
219print("result", ascii(input()))
220print("history", ascii(readline.get_history_item(1)))
221"""
222
223 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
224 input += b"\x02" * len("[after]") # Move cursor back
225 input += b"\t\t" # Display possible completions
226 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
227 input += b"\r"
228 output = run_pty(script, input)
229 self.assertIn(b"text 't\\xeb'\r\n", output)
230 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
231 self.assertIn(b"indexes 11 13\r\n", output)
Martin Pantera8cadb22016-06-14 11:29:31 +0000232 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000233 self.assertIn(b"substitution 't\\xeb'\r\n", output)
234 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000235 expected = br"'[\xefnserted]|t\xebxt[after]'"
236 self.assertIn(b"result " + expected + b"\r\n", output)
237 self.assertIn(b"history " + expected + b"\r\n", output)
238
Nir Sofferaa6a4d62017-07-08 17:34:27 +0300239 # We have 2 reasons to skip this test:
240 # - readline: history size was added in 6.0
241 # See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
242 # - editline: history size is broken on OS X 10.11.6.
243 # Newer versions were not tested yet.
244 @unittest.skipIf(readline._READLINE_VERSION < 0x600,
245 "this readline version does not support history-size")
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300246 @unittest.skipIf(is_editline,
247 "editline history size configuration is broken")
248 def test_history_size(self):
249 history_size = 10
250 with temp_dir() as test_dir:
251 inputrc = os.path.join(test_dir, "inputrc")
252 with open(inputrc, "wb") as f:
253 f.write(b"set history-size %d\n" % history_size)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000254
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300255 history_file = os.path.join(test_dir, "history")
256 with open(history_file, "wb") as f:
257 # history_size * 2 items crashes readline
258 data = b"".join(b"item %d\n" % i
259 for i in range(history_size * 2))
260 f.write(data)
261
262 script = """
263import os
264import readline
265
266history_file = os.environ["HISTORY_FILE"]
267readline.read_history_file(history_file)
268input()
269readline.write_history_file(history_file)
270"""
271
272 env = dict(os.environ)
273 env["INPUTRC"] = inputrc
274 env["HISTORY_FILE"] = history_file
275
276 run_pty(script, input=b"last input\r", env=env)
277
278 with open(history_file, "rb") as f:
279 lines = f.readlines()
280 self.assertEqual(len(lines), history_size)
281 self.assertEqual(lines[-1].strip(), b"last input")
282
283
284def run_pty(script, input=b"dummy input\r", env=None):
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000285 pty = import_module('pty')
286 output = bytearray()
287 [master, slave] = pty.openpty()
288 args = (sys.executable, '-c', script)
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300289 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000290 os.close(slave)
Martin Panter37126862016-05-15 03:05:36 +0000291 with ExitStack() as cleanup:
292 cleanup.enter_context(proc)
Martin Panter79f561d2016-05-15 15:04:58 +0000293 def terminate(proc):
294 try:
295 proc.terminate()
296 except ProcessLookupError:
297 # Workaround for Open/Net BSD bug (Issue 16762)
298 pass
299 cleanup.callback(terminate, proc)
Martin Panter37126862016-05-15 03:05:36 +0000300 cleanup.callback(os.close, master)
Martin Panter79f561d2016-05-15 15:04:58 +0000301 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
302 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
303 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
304 # either (Issue 20472). Hopefully the file descriptor is low enough
305 # to use with select().
306 sel = cleanup.enter_context(selectors.SelectSelector())
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000307 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
308 os.set_blocking(master, False)
309 while True:
310 for [_, events] in sel.select():
311 if events & selectors.EVENT_READ:
312 try:
313 chunk = os.read(master, 0x10000)
314 except OSError as err:
Martin Panterf00c49d2016-06-14 01:16:16 +0000315 # Linux raises EIO when slave is closed (Issue 5380)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000316 if err.errno != EIO:
317 raise
318 chunk = b""
319 if not chunk:
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000320 return output
321 output.extend(chunk)
322 if events & selectors.EVENT_WRITE:
Martin Panterf00c49d2016-06-14 01:16:16 +0000323 try:
324 input = input[os.write(master, input):]
325 except OSError as err:
326 # Apparently EIO means the slave was closed
327 if err.errno != EIO:
328 raise
329 input = b"" # Stop writing
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000330 if not input:
331 sel.modify(master, selectors.EVENT_READ)
332
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200333
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000334if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500335 unittest.main()