blob: b4c25dee9d3c20a9f0e3e4986bc32677ed7ecd70 [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 Stinnera3c80ce2014-07-24 12:23:56 +02006import os
Martin Panterf0dbf7a2016-05-15 01:26:25 +00007import selectors
8import subprocess
9import sys
Benjamin Peterson33f8f152014-11-26 13:58:16 -060010import tempfile
Ronald Oussoren2efd9242009-09-20 14:53:22 +000011import unittest
Victor Stinner1881bef2017-07-07 16:06:58 +020012from test.support import import_module, unlink, temp_dir, TESTFN, verbose
Berker Peksagce643912015-05-06 06:33:17 +030013from test.support.script_helper import assert_python_ok
Ronald Oussoren2efd9242009-09-20 14:53:22 +000014
Mark Dickinson2d7062e2009-10-26 12:01:06 +000015# Skip tests if there is no readline module
16readline = import_module('readline')
Ronald Oussoren2efd9242009-09-20 14:53:22 +000017
Victor Stinner1881bef2017-07-07 16:06:58 +020018if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
19 is_editline = ("EditLine wrapper" in readline._READLINE_LIBRARY_VERSION)
20else:
21 is_editline = (readline.__doc__ and "libedit" in readline.__doc__)
22
23
24def setUpModule():
25 if verbose:
26 # Python implementations other than CPython may not have
27 # these private attributes
28 if hasattr(readline, "_READLINE_VERSION"):
29 print(f"readline version: {readline._READLINE_VERSION:#x}")
30 print(f"readline runtime version: {readline._READLINE_RUNTIME_VERSION:#x}")
31 if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
32 print(f"readline library version: {readline._READLINE_LIBRARY_VERSION!r}")
33 print(f"use libedit emulation? {is_editline}")
34
Martin Panter056f76d2016-06-14 05:45:31 +000035
Martin Panterf00c49d2016-06-14 01:16:16 +000036@unittest.skipUnless(hasattr(readline, "clear_history"),
37 "The history update test cannot be run because the "
38 "clear_history method is not available.")
Ronald Oussoren2efd9242009-09-20 14:53:22 +000039class TestHistoryManipulation (unittest.TestCase):
Victor Stinnera3c80ce2014-07-24 12:23:56 +020040 """
41 These tests were added to check that the libedit emulation on OSX and the
42 "real" readline have the same interface for history manipulation. That's
43 why the tests cover only a small subset of the interface.
44 """
R David Murrayf8b9dfd2011-03-14 17:10:22 -040045
Ronald Oussoren2efd9242009-09-20 14:53:22 +000046 def testHistoryUpdates(self):
Georg Brandl7b250a52012-08-11 11:02:14 +020047 readline.clear_history()
Ronald Oussoren2efd9242009-09-20 14:53:22 +000048
49 readline.add_history("first line")
50 readline.add_history("second line")
51
52 self.assertEqual(readline.get_history_item(0), None)
53 self.assertEqual(readline.get_history_item(1), "first line")
54 self.assertEqual(readline.get_history_item(2), "second line")
55
56 readline.replace_history_item(0, "replaced line")
57 self.assertEqual(readline.get_history_item(0), None)
58 self.assertEqual(readline.get_history_item(1), "replaced line")
59 self.assertEqual(readline.get_history_item(2), "second line")
60
61 self.assertEqual(readline.get_current_history_length(), 2)
62
63 readline.remove_history_item(0)
64 self.assertEqual(readline.get_history_item(0), None)
65 self.assertEqual(readline.get_history_item(1), "second line")
66
67 self.assertEqual(readline.get_current_history_length(), 1)
68
Ned Deily8007cbc2014-11-26 13:02:33 -080069 @unittest.skipUnless(hasattr(readline, "append_history_file"),
Benjamin Petersond1e22ba2014-11-26 14:35:12 -060070 "append_history not available")
Benjamin Peterson33f8f152014-11-26 13:58:16 -060071 def test_write_read_append(self):
72 hfile = tempfile.NamedTemporaryFile(delete=False)
73 hfile.close()
74 hfilename = hfile.name
75 self.addCleanup(unlink, hfilename)
76
77 # test write-clear-read == nop
78 readline.clear_history()
79 readline.add_history("first line")
80 readline.add_history("second line")
81 readline.write_history_file(hfilename)
82
83 readline.clear_history()
84 self.assertEqual(readline.get_current_history_length(), 0)
85
86 readline.read_history_file(hfilename)
87 self.assertEqual(readline.get_current_history_length(), 2)
88 self.assertEqual(readline.get_history_item(1), "first line")
89 self.assertEqual(readline.get_history_item(2), "second line")
90
91 # test append
92 readline.append_history_file(1, hfilename)
93 readline.clear_history()
94 readline.read_history_file(hfilename)
95 self.assertEqual(readline.get_current_history_length(), 3)
96 self.assertEqual(readline.get_history_item(1), "first line")
97 self.assertEqual(readline.get_history_item(2), "second line")
98 self.assertEqual(readline.get_history_item(3), "second line")
99
100 # test 'no such file' behaviour
101 os.unlink(hfilename)
102 with self.assertRaises(FileNotFoundError):
103 readline.append_history_file(1, hfilename)
104
105 # write_history_file can create the target
106 readline.write_history_file(hfilename)
107
Martin Panterf00c49d2016-06-14 01:16:16 +0000108 def test_nonascii_history(self):
109 readline.clear_history()
110 try:
111 readline.add_history("entrée 1")
112 except UnicodeEncodeError as err:
113 self.skipTest("Locale cannot encode test data: " + format(err))
114 readline.add_history("entrée 2")
115 readline.replace_history_item(1, "entrée 22")
116 readline.write_history_file(TESTFN)
117 self.addCleanup(os.remove, TESTFN)
118 readline.clear_history()
119 readline.read_history_file(TESTFN)
Martin Panter056f76d2016-06-14 05:45:31 +0000120 if is_editline:
121 # An add_history() call seems to be required for get_history_
122 # item() to register items from the file
123 readline.add_history("dummy")
Martin Panterf00c49d2016-06-14 01:16:16 +0000124 self.assertEqual(readline.get_history_item(1), "entrée 1")
125 self.assertEqual(readline.get_history_item(2), "entrée 22")
126
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000127
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200128class TestReadline(unittest.TestCase):
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100129
Martin Panterc427b8d2016-08-27 03:23:11 +0000130 @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100131 "not supported in this library version")
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200132 def test_init(self):
133 # Issue #19884: Ensure that the ANSI sequence "\033[1034h" is not
134 # written into stdout when the readline module is imported and stdout
135 # is redirected to a pipe.
136 rc, stdout, stderr = assert_python_ok('-c', 'import readline',
137 TERM='xterm-256color')
138 self.assertEqual(stdout, b'')
139
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000140 auto_history_script = """\
141import readline
142readline.set_auto_history({})
143input()
144print("History length:", readline.get_current_history_length())
145"""
146
147 def test_auto_history_enabled(self):
148 output = run_pty(self.auto_history_script.format(True))
149 self.assertIn(b"History length: 1\r\n", output)
150
151 def test_auto_history_disabled(self):
152 output = run_pty(self.auto_history_script.format(False))
153 self.assertIn(b"History length: 0\r\n", output)
154
Martin Panterf00c49d2016-06-14 01:16:16 +0000155 def test_nonascii(self):
156 try:
157 readline.add_history("\xEB\xEF")
158 except UnicodeEncodeError as err:
159 self.skipTest("Locale cannot encode test data: " + format(err))
160
161 script = r"""import readline
162
Martin Panter6afbc652016-06-14 08:45:43 +0000163is_editline = readline.__doc__ and "libedit" in readline.__doc__
164inserted = "[\xEFnserted]"
165macro = "|t\xEB[after]"
166set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
167if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000168 # The insert_line() call via pre_input_hook() does nothing with Editline,
169 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000170 macro = inserted + macro
171
172if is_editline:
173 readline.parse_and_bind(r'bind ^B ed-prev-char')
174 readline.parse_and_bind(r'bind "\t" rl_complete')
175 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000176else:
177 readline.parse_and_bind(r'Control-b: backward-char')
178 readline.parse_and_bind(r'"\t": complete')
179 readline.parse_and_bind(r'set disable-completion off')
180 readline.parse_and_bind(r'set show-all-if-ambiguous off')
181 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000182 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000183
184def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000185 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000186 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000187if set_pre_input_hook:
188 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000189
190def completer(text, state):
191 if text == "t\xEB":
192 if state == 0:
193 print("text", ascii(text))
194 print("line", ascii(readline.get_line_buffer()))
195 print("indexes", readline.get_begidx(), readline.get_endidx())
196 return "t\xEBnt"
197 if state == 1:
198 return "t\xEBxt"
199 if text == "t\xEBx" and state == 0:
200 return "t\xEBxt"
201 return None
202readline.set_completer(completer)
203
204def display(substitution, matches, longest_match_length):
205 print("substitution", ascii(substitution))
206 print("matches", ascii(matches))
207readline.set_completion_display_matches_hook(display)
208
209print("result", ascii(input()))
210print("history", ascii(readline.get_history_item(1)))
211"""
212
213 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
214 input += b"\x02" * len("[after]") # Move cursor back
215 input += b"\t\t" # Display possible completions
216 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
217 input += b"\r"
218 output = run_pty(script, input)
219 self.assertIn(b"text 't\\xeb'\r\n", output)
220 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
221 self.assertIn(b"indexes 11 13\r\n", output)
Martin Pantera8cadb22016-06-14 11:29:31 +0000222 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000223 self.assertIn(b"substitution 't\\xeb'\r\n", output)
224 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000225 expected = br"'[\xefnserted]|t\xebxt[after]'"
226 self.assertIn(b"result " + expected + b"\r\n", output)
227 self.assertIn(b"history " + expected + b"\r\n", output)
228
Nir Sofferaa6a4d62017-07-08 17:34:27 +0300229 # We have 2 reasons to skip this test:
230 # - readline: history size was added in 6.0
231 # See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
232 # - editline: history size is broken on OS X 10.11.6.
233 # Newer versions were not tested yet.
234 @unittest.skipIf(readline._READLINE_VERSION < 0x600,
235 "this readline version does not support history-size")
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300236 @unittest.skipIf(is_editline,
237 "editline history size configuration is broken")
238 def test_history_size(self):
239 history_size = 10
240 with temp_dir() as test_dir:
241 inputrc = os.path.join(test_dir, "inputrc")
242 with open(inputrc, "wb") as f:
243 f.write(b"set history-size %d\n" % history_size)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000244
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300245 history_file = os.path.join(test_dir, "history")
246 with open(history_file, "wb") as f:
247 # history_size * 2 items crashes readline
248 data = b"".join(b"item %d\n" % i
249 for i in range(history_size * 2))
250 f.write(data)
251
252 script = """
253import os
254import readline
255
256history_file = os.environ["HISTORY_FILE"]
257readline.read_history_file(history_file)
258input()
259readline.write_history_file(history_file)
260"""
261
262 env = dict(os.environ)
263 env["INPUTRC"] = inputrc
264 env["HISTORY_FILE"] = history_file
265
266 run_pty(script, input=b"last input\r", env=env)
267
268 with open(history_file, "rb") as f:
269 lines = f.readlines()
270 self.assertEqual(len(lines), history_size)
271 self.assertEqual(lines[-1].strip(), b"last input")
272
273
274def run_pty(script, input=b"dummy input\r", env=None):
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000275 pty = import_module('pty')
276 output = bytearray()
277 [master, slave] = pty.openpty()
278 args = (sys.executable, '-c', script)
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300279 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000280 os.close(slave)
Martin Panter37126862016-05-15 03:05:36 +0000281 with ExitStack() as cleanup:
282 cleanup.enter_context(proc)
Martin Panter79f561d2016-05-15 15:04:58 +0000283 def terminate(proc):
284 try:
285 proc.terminate()
286 except ProcessLookupError:
287 # Workaround for Open/Net BSD bug (Issue 16762)
288 pass
289 cleanup.callback(terminate, proc)
Martin Panter37126862016-05-15 03:05:36 +0000290 cleanup.callback(os.close, master)
Martin Panter79f561d2016-05-15 15:04:58 +0000291 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
292 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
293 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
294 # either (Issue 20472). Hopefully the file descriptor is low enough
295 # to use with select().
296 sel = cleanup.enter_context(selectors.SelectSelector())
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000297 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
298 os.set_blocking(master, False)
299 while True:
300 for [_, events] in sel.select():
301 if events & selectors.EVENT_READ:
302 try:
303 chunk = os.read(master, 0x10000)
304 except OSError as err:
Martin Panterf00c49d2016-06-14 01:16:16 +0000305 # Linux raises EIO when slave is closed (Issue 5380)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000306 if err.errno != EIO:
307 raise
308 chunk = b""
309 if not chunk:
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000310 return output
311 output.extend(chunk)
312 if events & selectors.EVENT_WRITE:
Martin Panterf00c49d2016-06-14 01:16:16 +0000313 try:
314 input = input[os.write(master, input):]
315 except OSError as err:
316 # Apparently EIO means the slave was closed
317 if err.errno != EIO:
318 raise
319 input = b"" # Stop writing
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000320 if not input:
321 sel.modify(master, selectors.EVENT_READ)
322
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200323
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000324if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500325 unittest.main()