1""" 2Very minimal unittests for parts of the readline module. 3""" 4from contextlib import ExitStack 5from errno import EIO 6import os 7import selectors 8import subprocess 9import sys 10import tempfile 11import unittest 12from test.support import import_module, unlink, TESTFN 13from test.support.script_helper import assert_python_ok 14 15# Skip tests if there is no readline module 16readline = import_module('readline') 17 18is_editline = readline.__doc__ and "libedit" in readline.__doc__ 19 20@unittest.skipUnless(hasattr(readline, "clear_history"), 21 "The history update test cannot be run because the " 22 "clear_history method is not available.") 23class TestHistoryManipulation (unittest.TestCase): 24 """ 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 """ 29 30 def testHistoryUpdates(self): 31 readline.clear_history() 32 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 53 @unittest.skipUnless(hasattr(readline, "append_history_file"), 54 "append_history not available") 55 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 92 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) 104 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") 108 self.assertEqual(readline.get_history_item(1), "entrée 1") 109 self.assertEqual(readline.get_history_item(2), "entrée 22") 110 111 112class TestReadline(unittest.TestCase): 113 114 @unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline, 115 "not supported in this library version") 116 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 124 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 139 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 147is_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: 152 # The insert_line() call via pre_input_hook() does nothing with Editline, 153 # so include the extra text that would have been inserted here 154 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)) 160else: 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') 166 readline.parse_and_bind(r'Control-a: "{}"'.format(macro)) 167 168def pre_input_hook(): 169 readline.insert_text(inserted) 170 readline.redisplay() 171if set_pre_input_hook: 172 set_pre_input_hook(pre_input_hook) 173 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) 206 if not is_editline and hasattr(readline, "set_pre_input_hook"): 207 self.assertIn(b"substitution 't\\xeb'\r\n", output) 208 self.assertIn(b"matches ['t\\xebnt', 't\\xebxt']\r\n", output) 209 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 213 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) 221 with ExitStack() as cleanup: 222 cleanup.enter_context(proc) 223 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) 230 cleanup.callback(os.close, master) 231 # 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()) 237 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: 245 # Linux raises EIO when slave is closed (Issue 5380) 246 if err.errno != EIO: 247 raise 248 chunk = b"" 249 if not chunk: 250 return output 251 output.extend(chunk) 252 if events & selectors.EVENT_WRITE: 253 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 260 if not input: 261 sel.modify(master, selectors.EVENT_READ) 262 263 264if __name__ == "__main__": 265 unittest.main() 266