blob: 06a9149374aeab26dc18e339ce486c2c5251e122 [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
Martin Panterf00c49d2016-06-14 01:16:16 +000012from test.support import import_module, unlink, TESTFN
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
Martin Panter056f76d2016-06-14 05:45:31 +000018is_editline = readline.__doc__ and "libedit" in readline.__doc__
19
Martin Panterf00c49d2016-06-14 01:16:16 +000020@unittest.skipUnless(hasattr(readline, "clear_history"),
21 "The history update test cannot be run because the "
22 "clear_history method is not available.")
Ronald Oussoren2efd9242009-09-20 14:53:22 +000023class TestHistoryManipulation (unittest.TestCase):
Victor Stinnera3c80ce2014-07-24 12:23:56 +020024 """
25 These tests were added to check that the libedit emulation on OSX and the
26 "real" readline have the same interface for history manipulation. That's
27 why the tests cover only a small subset of the interface.
28 """
R David Murrayf8b9dfd2011-03-14 17:10:22 -040029
Ronald Oussoren2efd9242009-09-20 14:53:22 +000030 def testHistoryUpdates(self):
Georg Brandl7b250a52012-08-11 11:02:14 +020031 readline.clear_history()
Ronald Oussoren2efd9242009-09-20 14:53:22 +000032
33 readline.add_history("first line")
34 readline.add_history("second line")
35
36 self.assertEqual(readline.get_history_item(0), None)
37 self.assertEqual(readline.get_history_item(1), "first line")
38 self.assertEqual(readline.get_history_item(2), "second line")
39
40 readline.replace_history_item(0, "replaced line")
41 self.assertEqual(readline.get_history_item(0), None)
42 self.assertEqual(readline.get_history_item(1), "replaced line")
43 self.assertEqual(readline.get_history_item(2), "second line")
44
45 self.assertEqual(readline.get_current_history_length(), 2)
46
47 readline.remove_history_item(0)
48 self.assertEqual(readline.get_history_item(0), None)
49 self.assertEqual(readline.get_history_item(1), "second line")
50
51 self.assertEqual(readline.get_current_history_length(), 1)
52
Ned Deily8007cbc2014-11-26 13:02:33 -080053 @unittest.skipUnless(hasattr(readline, "append_history_file"),
Benjamin Petersond1e22ba2014-11-26 14:35:12 -060054 "append_history not available")
Benjamin Peterson33f8f152014-11-26 13:58:16 -060055 def test_write_read_append(self):
56 hfile = tempfile.NamedTemporaryFile(delete=False)
57 hfile.close()
58 hfilename = hfile.name
59 self.addCleanup(unlink, hfilename)
60
61 # test write-clear-read == nop
62 readline.clear_history()
63 readline.add_history("first line")
64 readline.add_history("second line")
65 readline.write_history_file(hfilename)
66
67 readline.clear_history()
68 self.assertEqual(readline.get_current_history_length(), 0)
69
70 readline.read_history_file(hfilename)
71 self.assertEqual(readline.get_current_history_length(), 2)
72 self.assertEqual(readline.get_history_item(1), "first line")
73 self.assertEqual(readline.get_history_item(2), "second line")
74
75 # test append
76 readline.append_history_file(1, hfilename)
77 readline.clear_history()
78 readline.read_history_file(hfilename)
79 self.assertEqual(readline.get_current_history_length(), 3)
80 self.assertEqual(readline.get_history_item(1), "first line")
81 self.assertEqual(readline.get_history_item(2), "second line")
82 self.assertEqual(readline.get_history_item(3), "second line")
83
84 # test 'no such file' behaviour
85 os.unlink(hfilename)
86 with self.assertRaises(FileNotFoundError):
87 readline.append_history_file(1, hfilename)
88
89 # write_history_file can create the target
90 readline.write_history_file(hfilename)
91
Martin Panterf00c49d2016-06-14 01:16:16 +000092 def test_nonascii_history(self):
93 readline.clear_history()
94 try:
95 readline.add_history("entrée 1")
96 except UnicodeEncodeError as err:
97 self.skipTest("Locale cannot encode test data: " + format(err))
98 readline.add_history("entrée 2")
99 readline.replace_history_item(1, "entrée 22")
100 readline.write_history_file(TESTFN)
101 self.addCleanup(os.remove, TESTFN)
102 readline.clear_history()
103 readline.read_history_file(TESTFN)
Martin Panter056f76d2016-06-14 05:45:31 +0000104 if is_editline:
105 # An add_history() call seems to be required for get_history_
106 # item() to register items from the file
107 readline.add_history("dummy")
Martin Panterf00c49d2016-06-14 01:16:16 +0000108 self.assertEqual(readline.get_history_item(1), "entrée 1")
109 self.assertEqual(readline.get_history_item(2), "entrée 22")
110
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000111
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200112class TestReadline(unittest.TestCase):
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100113
Martin Panterc427b8d2016-08-27 03:23:11 +0000114 @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
Antoine Pitrou7e8b8672014-11-04 14:52:10 +0100115 "not supported in this library version")
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200116 def test_init(self):
117 # Issue #19884: Ensure that the ANSI sequence "\033[1034h" is not
118 # written into stdout when the readline module is imported and stdout
119 # is redirected to a pipe.
120 rc, stdout, stderr = assert_python_ok('-c', 'import readline',
121 TERM='xterm-256color')
122 self.assertEqual(stdout, b'')
123
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000124 auto_history_script = """\
125import readline
126readline.set_auto_history({})
127input()
128print("History length:", readline.get_current_history_length())
129"""
130
131 def test_auto_history_enabled(self):
132 output = run_pty(self.auto_history_script.format(True))
133 self.assertIn(b"History length: 1\r\n", output)
134
135 def test_auto_history_disabled(self):
136 output = run_pty(self.auto_history_script.format(False))
137 self.assertIn(b"History length: 0\r\n", output)
138
Martin Panterf00c49d2016-06-14 01:16:16 +0000139 def test_nonascii(self):
140 try:
141 readline.add_history("\xEB\xEF")
142 except UnicodeEncodeError as err:
143 self.skipTest("Locale cannot encode test data: " + format(err))
144
145 script = r"""import readline
146
Martin Panter6afbc652016-06-14 08:45:43 +0000147is_editline = readline.__doc__ and "libedit" in readline.__doc__
148inserted = "[\xEFnserted]"
149macro = "|t\xEB[after]"
150set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
151if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000152 # The insert_line() call via pre_input_hook() does nothing with Editline,
153 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000154 macro = inserted + macro
155
156if is_editline:
157 readline.parse_and_bind(r'bind ^B ed-prev-char')
158 readline.parse_and_bind(r'bind "\t" rl_complete')
159 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000160else:
161 readline.parse_and_bind(r'Control-b: backward-char')
162 readline.parse_and_bind(r'"\t": complete')
163 readline.parse_and_bind(r'set disable-completion off')
164 readline.parse_and_bind(r'set show-all-if-ambiguous off')
165 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000166 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000167
168def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000169 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000170 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000171if set_pre_input_hook:
172 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000173
174def completer(text, state):
175 if text == "t\xEB":
176 if state == 0:
177 print("text", ascii(text))
178 print("line", ascii(readline.get_line_buffer()))
179 print("indexes", readline.get_begidx(), readline.get_endidx())
180 return "t\xEBnt"
181 if state == 1:
182 return "t\xEBxt"
183 if text == "t\xEBx" and state == 0:
184 return "t\xEBxt"
185 return None
186readline.set_completer(completer)
187
188def display(substitution, matches, longest_match_length):
189 print("substitution", ascii(substitution))
190 print("matches", ascii(matches))
191readline.set_completion_display_matches_hook(display)
192
193print("result", ascii(input()))
194print("history", ascii(readline.get_history_item(1)))
195"""
196
197 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
198 input += b"\x02" * len("[after]") # Move cursor back
199 input += b"\t\t" # Display possible completions
200 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
201 input += b"\r"
202 output = run_pty(script, input)
203 self.assertIn(b"text 't\\xeb'\r\n", output)
204 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
205 self.assertIn(b"indexes 11 13\r\n", output)
Martin Pantera8cadb22016-06-14 11:29:31 +0000206 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000207 self.assertIn(b"substitution 't\\xeb'\r\n", output)
208 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000209 expected = br"'[\xefnserted]|t\xebxt[after]'"
210 self.assertIn(b"result " + expected + b"\r\n", output)
211 self.assertIn(b"history " + expected + b"\r\n", output)
212
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000213
214def run_pty(script, input=b"dummy input\r"):
215 pty = import_module('pty')
216 output = bytearray()
217 [master, slave] = pty.openpty()
218 args = (sys.executable, '-c', script)
219 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
220 os.close(slave)
Martin Panter37126862016-05-15 03:05:36 +0000221 with ExitStack() as cleanup:
222 cleanup.enter_context(proc)
Martin Panter79f561d2016-05-15 15:04:58 +0000223 def terminate(proc):
224 try:
225 proc.terminate()
226 except ProcessLookupError:
227 # Workaround for Open/Net BSD bug (Issue 16762)
228 pass
229 cleanup.callback(terminate, proc)
Martin Panter37126862016-05-15 03:05:36 +0000230 cleanup.callback(os.close, master)
Martin Panter79f561d2016-05-15 15:04:58 +0000231 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
232 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
233 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
234 # either (Issue 20472). Hopefully the file descriptor is low enough
235 # to use with select().
236 sel = cleanup.enter_context(selectors.SelectSelector())
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000237 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
238 os.set_blocking(master, False)
239 while True:
240 for [_, events] in sel.select():
241 if events & selectors.EVENT_READ:
242 try:
243 chunk = os.read(master, 0x10000)
244 except OSError as err:
Martin Panterf00c49d2016-06-14 01:16:16 +0000245 # Linux raises EIO when slave is closed (Issue 5380)
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000246 if err.errno != EIO:
247 raise
248 chunk = b""
249 if not chunk:
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000250 return output
251 output.extend(chunk)
252 if events & selectors.EVENT_WRITE:
Martin Panterf00c49d2016-06-14 01:16:16 +0000253 try:
254 input = input[os.write(master, input):]
255 except OSError as err:
256 # Apparently EIO means the slave was closed
257 if err.errno != EIO:
258 raise
259 input = b"" # Stop writing
Martin Panterf0dbf7a2016-05-15 01:26:25 +0000260 if not input:
261 sel.modify(master, selectors.EVENT_READ)
262
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200263
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000264if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500265 unittest.main()