blob: 28ea38b747ed53e7e94d8af664371f949586ea7b [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
Victor Stinner424315f2017-12-22 00:09:26 +0100155 @unittest.skipIf(True,
156 "FIXME: test broken by bpo-29240")
Martin Panterf00c49d2016-06-14 01:16:16 +0000157 def test_nonascii(self):
158 try:
159 readline.add_history("\xEB\xEF")
160 except UnicodeEncodeError as err:
161 self.skipTest("Locale cannot encode test data: " + format(err))
162
163 script = r"""import readline
164
Martin Panter6afbc652016-06-14 08:45:43 +0000165is_editline = readline.__doc__ and "libedit" in readline.__doc__
166inserted = "[\xEFnserted]"
167macro = "|t\xEB[after]"
168set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
169if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000170 # The insert_line() call via pre_input_hook() does nothing with Editline,
171 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000172 macro = inserted + macro
173
174if is_editline:
175 readline.parse_and_bind(r'bind ^B ed-prev-char')
176 readline.parse_and_bind(r'bind "\t" rl_complete')
177 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000178else:
179 readline.parse_and_bind(r'Control-b: backward-char')
180 readline.parse_and_bind(r'"\t": complete')
181 readline.parse_and_bind(r'set disable-completion off')
182 readline.parse_and_bind(r'set show-all-if-ambiguous off')
183 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000184 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000185
186def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000187 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000188 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000189if set_pre_input_hook:
190 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000191
192def completer(text, state):
193 if text == "t\xEB":
194 if state == 0:
195 print("text", ascii(text))
196 print("line", ascii(readline.get_line_buffer()))
197 print("indexes", readline.get_begidx(), readline.get_endidx())
198 return "t\xEBnt"
199 if state == 1:
200 return "t\xEBxt"
201 if text == "t\xEBx" and state == 0:
202 return "t\xEBxt"
203 return None
204readline.set_completer(completer)
205
206def display(substitution, matches, longest_match_length):
207 print("substitution", ascii(substitution))
208 print("matches", ascii(matches))
209readline.set_completion_display_matches_hook(display)
210
211print("result", ascii(input()))
212print("history", ascii(readline.get_history_item(1)))
213"""
214
215 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
216 input += b"\x02" * len("[after]") # Move cursor back
217 input += b"\t\t" # Display possible completions
218 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
219 input += b"\r"
220 output = run_pty(script, input)
221 self.assertIn(b"text 't\\xeb'\r\n", output)
222 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
223 self.assertIn(b"indexes 11 13\r\n", output)
Martin Pantera8cadb22016-06-14 11:29:31 +0000224 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000225 self.assertIn(b"substitution 't\\xeb'\r\n", output)
226 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000227 expected = br"'[\xefnserted]|t\xebxt[after]'"
228 self.assertIn(b"result " + expected + b"\r\n", output)
229 self.assertIn(b"history " + expected + b"\r\n", output)
230
Nir Sofferaa6a4d62017-07-08 17:34:27 +0300231 # We have 2 reasons to skip this test:
232 # - readline: history size was added in 6.0
233 # See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
234 # - editline: history size is broken on OS X 10.11.6.
235 # Newer versions were not tested yet.
236 @unittest.skipIf(readline._READLINE_VERSION < 0x600,
237 "this readline version does not support history-size")
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300238 @unittest.skipIf(is_editline,
239 "editline history size configuration is broken")
240 def test_history_size(self):
241 history_size = 10
242 with temp_dir() as test_dir:
243 inputrc = os.path.join(test_dir, "inputrc")
244 with open(inputrc, "wb") as f:
245 f.write(b"set history-size %d\n" % history_size)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000246
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300247 history_file = os.path.join(test_dir, "history")
248 with open(history_file, "wb") as f:
249 # history_size * 2 items crashes readline
250 data = b"".join(b"item %d\n" % i
251 for i in range(history_size * 2))
252 f.write(data)
253
254 script = """
255import os
256import readline
257
258history_file = os.environ["HISTORY_FILE"]
259readline.read_history_file(history_file)
260input()
261readline.write_history_file(history_file)
262"""
263
264 env = dict(os.environ)
265 env["INPUTRC"] = inputrc
266 env["HISTORY_FILE"] = history_file
267
268 run_pty(script, input=b"last input\r", env=env)
269
270 with open(history_file, "rb") as f:
271 lines = f.readlines()
272 self.assertEqual(len(lines), history_size)
273 self.assertEqual(lines[-1].strip(), b"last input")
274
275
276def run_pty(script, input=b"dummy input\r", env=None):
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000277 pty = import_module('pty')
278 output = bytearray()
279 [master, slave] = pty.openpty()
280 args = (sys.executable, '-c', script)
Nir Sofferfae8f4a2017-07-07 09:10:46 +0300281 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000282 os.close(slave)
Martin Panter37126862016-05-15 03:05:36 +0000283 with ExitStack() as cleanup:
284 cleanup.enter_context(proc)
Martin Panter79f561d2016-05-15 15:04:58 +0000285 def terminate(proc):
286 try:
287 proc.terminate()
288 except ProcessLookupError:
289 # Workaround for Open/Net BSD bug (Issue 16762)
290 pass
291 cleanup.callback(terminate, proc)
Martin Panter37126862016-05-15 03:05:36 +0000292 cleanup.callback(os.close, master)
Martin Panter79f561d2016-05-15 15:04:58 +0000293 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
294 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
295 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
296 # either (Issue 20472). Hopefully the file descriptor is low enough
297 # to use with select().
298 sel = cleanup.enter_context(selectors.SelectSelector())
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000299 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
300 os.set_blocking(master, False)
301 while True:
302 for [_, events] in sel.select():
303 if events & selectors.EVENT_READ:
304 try:
305 chunk = os.read(master, 0x10000)
306 except OSError as err:
Martin Panterf00c49d2016-06-14 01:16:16 +0000307 # Linux raises EIO when slave is closed (Issue 5380)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000308 if err.errno != EIO:
309 raise
310 chunk = b""
311 if not chunk:
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000312 return output
313 output.extend(chunk)
314 if events & selectors.EVENT_WRITE:
Martin Panterf00c49d2016-06-14 01:16:16 +0000315 try:
316 input = input[os.write(master, input):]
317 except OSError as err:
318 # Apparently EIO means the slave was closed
319 if err.errno != EIO:
320 raise
321 input = b"" # Stop writing
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000322 if not input:
323 sel.modify(master, selectors.EVENT_READ)
324
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200325
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000326if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500327 unittest.main()