1"""Test the interactive interpreter.""" 2 3import sys 4import os 5import unittest 6import subprocess 7from textwrap import dedent 8from test.support import cpython_only, has_subprocess_support, SuppressCrashReport 9from test.support.script_helper import kill_python 10 11 12if not has_subprocess_support: 13 raise unittest.SkipTest("test module requires subprocess") 14 15 16def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): 17 """Run the Python REPL with the given arguments. 18 19 kw is extra keyword args to pass to subprocess.Popen. Returns a Popen 20 object. 21 """ 22 23 # To run the REPL without using a terminal, spawn python with the command 24 # line option '-i' and the process name set to '<stdin>'. 25 # The directory of argv[0] must match the directory of the Python 26 # executable for the Popen() call to python to succeed as the directory 27 # path may be used by Py_GetPath() to build the default module search 28 # path. 29 stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>") 30 cmd_line = [stdin_fname, '-E', '-i'] 31 cmd_line.extend(args) 32 33 # Set TERM=vt100, for the rationale see the comments in spawn_python() of 34 # test.support.script_helper. 35 env = kw.setdefault('env', dict(os.environ)) 36 env['TERM'] = 'vt100' 37 return subprocess.Popen(cmd_line, 38 executable=sys.executable, 39 text=True, 40 stdin=subprocess.PIPE, 41 stdout=stdout, stderr=stderr, 42 **kw) 43 44def run_on_interactive_mode(source): 45 """Spawn a new Python interpreter, pass the given 46 input source code from the stdin and return the 47 result back. If the interpreter exits non-zero, it 48 raises a ValueError.""" 49 50 process = spawn_repl() 51 process.stdin.write(source) 52 output = kill_python(process) 53 54 if process.returncode != 0: 55 raise ValueError("Process didn't exit properly.") 56 return output 57 58 59class TestInteractiveInterpreter(unittest.TestCase): 60 61 @cpython_only 62 def test_no_memory(self): 63 # Issue #30696: Fix the interactive interpreter looping endlessly when 64 # no memory. Check also that the fix does not break the interactive 65 # loop when an exception is raised. 66 user_input = """ 67 import sys, _testcapi 68 1/0 69 print('After the exception.') 70 _testcapi.set_nomemory(0) 71 sys.exit(0) 72 """ 73 user_input = dedent(user_input) 74 p = spawn_repl() 75 with SuppressCrashReport(): 76 p.stdin.write(user_input) 77 output = kill_python(p) 78 self.assertIn('After the exception.', output) 79 # Exit code 120: Py_FinalizeEx() failed to flush stdout and stderr. 80 self.assertIn(p.returncode, (1, 120)) 81 82 @cpython_only 83 def test_multiline_string_parsing(self): 84 # bpo-39209: Multiline string tokens need to be handled in the tokenizer 85 # in two places: the interactive path and the non-interactive path. 86 user_input = '''\ 87 x = """<?xml version="1.0" encoding="iso-8859-1"?> 88 <test> 89 <Users> 90 <fun25> 91 <limits> 92 <total>0KiB</total> 93 <kbps>0</kbps> 94 <rps>1.3</rps> 95 <connections>0</connections> 96 </limits> 97 <usages> 98 <total>16738211KiB</total> 99 <kbps>237.15</kbps> 100 <rps>1.3</rps> 101 <connections>0</connections> 102 </usages> 103 <time_to_refresh>never</time_to_refresh> 104 <limit_exceeded_URL>none</limit_exceeded_URL> 105 </fun25> 106 </Users> 107 </test>""" 108 ''' 109 user_input = dedent(user_input) 110 p = spawn_repl() 111 p.stdin.write(user_input) 112 output = kill_python(p) 113 self.assertEqual(p.returncode, 0) 114 115 def test_close_stdin(self): 116 user_input = dedent(''' 117 import os 118 print("before close") 119 os.close(0) 120 ''') 121 prepare_repl = dedent(''' 122 from test.support import suppress_msvcrt_asserts 123 suppress_msvcrt_asserts() 124 ''') 125 process = spawn_repl('-c', prepare_repl) 126 output = process.communicate(user_input)[0] 127 self.assertEqual(process.returncode, 0) 128 self.assertIn('before close', output) 129 130 131class TestInteractiveModeSyntaxErrors(unittest.TestCase): 132 133 def test_interactive_syntax_error_correct_line(self): 134 output = run_on_interactive_mode(dedent("""\ 135 def f(): 136 print(0) 137 return yield 42 138 """)) 139 140 traceback_lines = output.splitlines()[-4:-1] 141 expected_lines = [ 142 ' return yield 42', 143 ' ^^^^^', 144 'SyntaxError: invalid syntax' 145 ] 146 self.assertEqual(traceback_lines, expected_lines) 147 148 149if __name__ == "__main__": 150 unittest.main() 151