test_repl.py 5.0 KB

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