|
16 | 16 | import shutil
|
17 | 17 | import stat
|
18 | 18 | import sys
|
| 19 | +import subprocess |
19 | 20 | import time
|
20 | 21 | import types
|
21 | 22 | import tempfile
|
|
35 | 36 | from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
|
36 | 37 | from test.support.import_helper import DirsOnSysPath, ready_to_import
|
37 | 38 | from test.support.os_helper import TESTFN
|
38 |
| -from test.support.script_helper import assert_python_ok, assert_python_failure |
| 39 | +from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python |
| 40 | +from test.support import has_subprocess_support, SuppressCrashReport |
39 | 41 | from test import support
|
40 | 42 |
|
41 | 43 | from . import inspect_fodder as mod
|
@@ -4961,5 +4963,66 @@ def test_getsource_reload(self):
|
4961 | 4963 | self.assertInspectEqual(path, module)
|
4962 | 4964 |
|
4963 | 4965 |
|
| 4966 | +class TestRepl(unittest.TestCase): |
| 4967 | + |
| 4968 | + def spawn_repl(self, *args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): |
| 4969 | + """Run the Python REPL with the given arguments. |
| 4970 | +
|
| 4971 | + kw is extra keyword args to pass to subprocess.Popen. Returns a Popen |
| 4972 | + object. |
| 4973 | + """ |
| 4974 | + |
| 4975 | + # To run the REPL without using a terminal, spawn python with the command |
| 4976 | + # line option '-i' and the process name set to '<stdin>'. |
| 4977 | + # The directory of argv[0] must match the directory of the Python |
| 4978 | + # executable for the Popen() call to python to succeed as the directory |
| 4979 | + # path may be used by Py_GetPath() to build the default module search |
| 4980 | + # path. |
| 4981 | + stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>") |
| 4982 | + cmd_line = [stdin_fname, '-E', '-i'] |
| 4983 | + cmd_line.extend(args) |
| 4984 | + |
| 4985 | + # Set TERM=vt100, for the rationale see the comments in spawn_python() of |
| 4986 | + # test.support.script_helper. |
| 4987 | + env = kw.setdefault('env', dict(os.environ)) |
| 4988 | + env['TERM'] = 'vt100' |
| 4989 | + return subprocess.Popen(cmd_line, |
| 4990 | + executable=sys.executable, |
| 4991 | + text=True, |
| 4992 | + stdin=subprocess.PIPE, |
| 4993 | + stdout=stdout, stderr=stderr, |
| 4994 | + **kw) |
| 4995 | + |
| 4996 | + def run_on_interactive_mode(self, source): |
| 4997 | + """Spawn a new Python interpreter, pass the given |
| 4998 | + input source code from the stdin and return the |
| 4999 | + result back. If the interpreter exits non-zero, it |
| 5000 | + raises a ValueError.""" |
| 5001 | + |
| 5002 | + process = self.spawn_repl() |
| 5003 | + process.stdin.write(source) |
| 5004 | + output = kill_python(process) |
| 5005 | + |
| 5006 | + if process.returncode != 0: |
| 5007 | + raise ValueError("Process didn't exit properly.") |
| 5008 | + return output |
| 5009 | + |
| 5010 | + @unittest.skipIf(not has_subprocess_support, "test requires subprocess") |
| 5011 | + def test_getsource(self): |
| 5012 | + output = self.run_on_interactive_mode(textwrap.dedent("""\ |
| 5013 | + def f(): |
| 5014 | + print(0) |
| 5015 | + return 1 + 2 |
| 5016 | +
|
| 5017 | + import inspect |
| 5018 | + print(f"The source is: <<<{inspect.getsource(f)}>>>") |
| 5019 | + """)) |
| 5020 | + |
| 5021 | + expected = "The source is: <<<def f():\n print(0)\n return 1 + 2\n>>>" |
| 5022 | + self.assertIn(expected, output) |
| 5023 | + |
| 5024 | + |
| 5025 | + |
| 5026 | + |
4964 | 5027 | if __name__ == "__main__":
|
4965 | 5028 | unittest.main()
|
0 commit comments