blob: 2c73df291f0c5ab8ebc16656e215660e81351e38 [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 Panterf00c49d2016-06-14 01:16:16 +00004from contextlib import ExitStack
5from errno import EIO
Victor Stinnera3c80ce2014-07-24 12:23:56 +02006import os
Martin Panterf00c49d2016-06-14 01:16:16 +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 Panterf00c49d2016-06-14 01:16:16 +0000124 def test_nonascii(self):
125 try:
126 readline.add_history("\xEB\xEF")
127 except UnicodeEncodeError as err:
128 self.skipTest("Locale cannot encode test data: " + format(err))
129
130 script = r"""import readline
131
Martin Panter6afbc652016-06-14 08:45:43 +0000132is_editline = readline.__doc__ and "libedit" in readline.__doc__
133inserted = "[\xEFnserted]"
134macro = "|t\xEB[after]"
135set_pre_input_hook = getattr(readline, "set_pre_input_hook", None)
136if is_editline or not set_pre_input_hook:
Martin Panter056f76d2016-06-14 05:45:31 +0000137 # The insert_line() call via pre_input_hook() does nothing with Editline,
138 # so include the extra text that would have been inserted here
Martin Panter6afbc652016-06-14 08:45:43 +0000139 macro = inserted + macro
140
141if is_editline:
142 readline.parse_and_bind(r'bind ^B ed-prev-char')
143 readline.parse_and_bind(r'bind "\t" rl_complete')
144 readline.parse_and_bind(r'bind -s ^A "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000145else:
146 readline.parse_and_bind(r'Control-b: backward-char')
147 readline.parse_and_bind(r'"\t": complete')
148 readline.parse_and_bind(r'set disable-completion off')
149 readline.parse_and_bind(r'set show-all-if-ambiguous off')
150 readline.parse_and_bind(r'set show-all-if-unmodified off')
Martin Panter6afbc652016-06-14 08:45:43 +0000151 readline.parse_and_bind(r'Control-a: "{}"'.format(macro))
Martin Panterf00c49d2016-06-14 01:16:16 +0000152
153def pre_input_hook():
Martin Panter6afbc652016-06-14 08:45:43 +0000154 readline.insert_text(inserted)
Martin Panterf00c49d2016-06-14 01:16:16 +0000155 readline.redisplay()
Martin Panter6afbc652016-06-14 08:45:43 +0000156if set_pre_input_hook:
157 set_pre_input_hook(pre_input_hook)
Martin Panterf00c49d2016-06-14 01:16:16 +0000158
159def completer(text, state):
160 if text == "t\xEB":
161 if state == 0:
162 print("text", ascii(text))
163 print("line", ascii(readline.get_line_buffer()))
164 print("indexes", readline.get_begidx(), readline.get_endidx())
165 return "t\xEBnt"
166 if state == 1:
167 return "t\xEBxt"
168 if text == "t\xEBx" and state == 0:
169 return "t\xEBxt"
170 return None
171readline.set_completer(completer)
172
173def display(substitution, matches, longest_match_length):
174 print("substitution", ascii(substitution))
175 print("matches", ascii(matches))
176readline.set_completion_display_matches_hook(display)
177
178print("result", ascii(input()))
179print("history", ascii(readline.get_history_item(1)))
180"""
181
182 input = b"\x01" # Ctrl-A, expands to "|t\xEB[after]"
183 input += b"\x02" * len("[after]") # Move cursor back
184 input += b"\t\t" # Display possible completions
185 input += b"x\t" # Complete "t\xEBx" -> "t\xEBxt"
186 input += b"\r"
187 output = run_pty(script, input)
188 self.assertIn(b"text 't\\xeb'\r\n", output)
189 self.assertIn(b"line '[\\xefnserted]|t\\xeb[after]'\r\n", output)
190 self.assertIn(b"indexes 11 13\r\n", output)
Martin Pantera8cadb22016-06-14 11:29:31 +0000191 if not is_editline and hasattr(readline, "set_pre_input_hook"):
Martin Panter056f76d2016-06-14 05:45:31 +0000192 self.assertIn(b"substitution 't\\xeb'\r\n", output)
193 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output)
Martin Panterf00c49d2016-06-14 01:16:16 +0000194 expected = br"'[\xefnserted]|t\xebxt[after]'"
195 self.assertIn(b"result " + expected + b"\r\n", output)
196 self.assertIn(b"history " + expected + b"\r\n", output)
197
198
199def run_pty(script, input=b"dummy input\r"):
200 pty = import_module('pty')
201 output = bytearray()
202 [master, slave] = pty.openpty()
203 args = (sys.executable, '-c', script)
204 proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
205 os.close(slave)
206 with ExitStack() as cleanup:
207 cleanup.enter_context(proc)
208 def terminate(proc):
209 try:
210 proc.terminate()
211 except ProcessLookupError:
212 # Workaround for Open/Net BSD bug (Issue 16762)
213 pass
214 cleanup.callback(terminate, proc)
215 cleanup.callback(os.close, master)
216 # Avoid using DefaultSelector and PollSelector. Kqueue() does not
217 # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
218 # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
219 # either (Issue 20472). Hopefully the file descriptor is low enough
220 # to use with select().
221 sel = cleanup.enter_context(selectors.SelectSelector())
222 sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
223 os.set_blocking(master, False)
224 while True:
225 for [_, events] in sel.select():
226 if events & selectors.EVENT_READ:
227 try:
228 chunk = os.read(master, 0x10000)
229 except OSError as err:
230 # Linux raises EIO when slave is closed (Issue 5380)
231 if err.errno != EIO:
232 raise
233 chunk = b""
234 if not chunk:
235 return output
236 output.extend(chunk)
237 if events & selectors.EVENT_WRITE:
238 try:
239 input = input[os.write(master, input):]
240 except OSError as err:
241 # Apparently EIO means the slave was closed
242 if err.errno != EIO:
243 raise
244 input = b"" # Stop writing
245 if not input:
246 sel.modify(master, selectors.EVENT_READ)
247
Victor Stinnera3c80ce2014-07-24 12:23:56 +0200248
Ronald Oussoren2efd9242009-09-20 14:53:22 +0000249if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500250 unittest.main()