mirror of
https://github.com/python/cpython.git
synced 2025-07-29 14:15:07 +00:00
[3.13] gh-120678: pyrepl: Include globals from modules passed with -i
(GH-120904) (#121916)
(cherry picked from commit ac07451116
)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
5a8e1373fe
commit
3d9692dbf8
6 changed files with 178 additions and 11 deletions
|
@ -2,6 +2,7 @@ import io
|
|||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import rlcompleter
|
||||
import select
|
||||
import subprocess
|
||||
|
@ -21,7 +22,8 @@ from .support import (
|
|||
more_lines,
|
||||
multiline_input,
|
||||
code_to_events,
|
||||
clean_screen
|
||||
clean_screen,
|
||||
make_clean_env,
|
||||
)
|
||||
from _pyrepl.console import Event
|
||||
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
||||
|
@ -487,6 +489,18 @@ class TestPyReplOutput(TestCase):
|
|||
reader.can_colorize = False
|
||||
return reader
|
||||
|
||||
def test_stdin_is_tty(self):
|
||||
# Used during test log analysis to figure out if a TTY was available.
|
||||
if os.isatty(sys.stdin.fileno()):
|
||||
return
|
||||
self.skipTest("stdin is not a tty")
|
||||
|
||||
def test_stdout_is_tty(self):
|
||||
# Used during test log analysis to figure out if a TTY was available.
|
||||
if os.isatty(sys.stdout.fileno()):
|
||||
return
|
||||
self.skipTest("stdout is not a tty")
|
||||
|
||||
def test_basic(self):
|
||||
reader = self.prepare_reader(code_to_events("1+1\n"))
|
||||
|
||||
|
@ -888,12 +902,7 @@ class TestMain(TestCase):
|
|||
# Cleanup from PYTHON* variables to isolate from local
|
||||
# user settings, see #121359. Such variables should be
|
||||
# added later in test methods to patched os.environ.
|
||||
clean_env = os.environ.copy()
|
||||
for k in clean_env.copy():
|
||||
if k.startswith("PYTHON"):
|
||||
clean_env.pop(k)
|
||||
|
||||
patcher = patch('os.environ', new=clean_env)
|
||||
patcher = patch('os.environ', new=make_clean_env())
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher.start()
|
||||
|
||||
|
@ -920,6 +929,84 @@ class TestMain(TestCase):
|
|||
|
||||
self.assertTrue(case1 or case2 or case3 or case4, output)
|
||||
|
||||
def _assertMatchOK(
|
||||
self, var: str, expected: str | re.Pattern, actual: str
|
||||
) -> None:
|
||||
if isinstance(expected, re.Pattern):
|
||||
self.assertTrue(
|
||||
expected.match(actual),
|
||||
f"{var}={actual} does not match {expected.pattern}",
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
actual,
|
||||
expected,
|
||||
f"expected {var}={expected}, got {var}={actual}",
|
||||
)
|
||||
|
||||
@force_not_colorized
|
||||
def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False):
|
||||
clean_env = make_clean_env()
|
||||
clean_env["NO_COLOR"] = "1" # force_not_colorized doesn't touch subprocesses
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
blue = pathlib.Path(td) / "blue"
|
||||
blue.mkdir()
|
||||
mod = blue / "calx.py"
|
||||
mod.write_text("FOO = 42", encoding="utf-8")
|
||||
commands = [
|
||||
"print(f'{" + var + "=}')" for var in expectations
|
||||
] + ["exit"]
|
||||
if as_file and as_module:
|
||||
self.fail("as_file and as_module are mutually exclusive")
|
||||
elif as_file:
|
||||
output, exit_code = self.run_repl(
|
||||
commands,
|
||||
cmdline_args=[str(mod)],
|
||||
env=clean_env,
|
||||
)
|
||||
elif as_module:
|
||||
output, exit_code = self.run_repl(
|
||||
commands,
|
||||
cmdline_args=["-m", "blue.calx"],
|
||||
env=clean_env,
|
||||
cwd=td,
|
||||
)
|
||||
else:
|
||||
self.fail("Choose one of as_file or as_module")
|
||||
|
||||
if "can't use pyrepl" in output:
|
||||
self.skipTest("pyrepl not available")
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
for var, expected in expectations.items():
|
||||
with self.subTest(var=var, expected=expected):
|
||||
if m := re.search(rf"[\r\n]{var}=(.+?)[\r\n]", output):
|
||||
self._assertMatchOK(var, expected, actual=m.group(1))
|
||||
else:
|
||||
self.fail(f"{var}= not found in output")
|
||||
|
||||
self.assertNotIn("Exception", output)
|
||||
self.assertNotIn("Traceback", output)
|
||||
|
||||
def test_inspect_keeps_globals_from_inspected_file(self):
|
||||
expectations = {
|
||||
"FOO": "42",
|
||||
"__name__": "'__main__'",
|
||||
"__package__": "None",
|
||||
# "__file__" is missing in -i, like in the basic REPL
|
||||
}
|
||||
self._run_repl_globals_test(expectations, as_file=True)
|
||||
|
||||
def test_inspect_keeps_globals_from_inspected_module(self):
|
||||
expectations = {
|
||||
"FOO": "42",
|
||||
"__name__": "'__main__'",
|
||||
"__package__": "'blue'",
|
||||
"__file__": re.compile(r"^'.*calx.py'$"),
|
||||
}
|
||||
self._run_repl_globals_test(expectations, as_module=True)
|
||||
|
||||
def test_dumb_terminal_exits_cleanly(self):
|
||||
env = os.environ.copy()
|
||||
env.update({"TERM": "dumb"})
|
||||
|
@ -981,16 +1068,27 @@ class TestMain(TestCase):
|
|||
self.assertIn("spam", output)
|
||||
self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0)
|
||||
|
||||
def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]:
|
||||
def run_repl(
|
||||
self,
|
||||
repl_input: str | list[str],
|
||||
env: dict | None = None,
|
||||
*,
|
||||
cmdline_args: list[str] | None = None,
|
||||
cwd: str | None = None,
|
||||
) -> tuple[str, int]:
|
||||
assert pty
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
cmd = [sys.executable, "-i", "-u"]
|
||||
if env is None:
|
||||
cmd.append("-I")
|
||||
if cmdline_args is not None:
|
||||
cmd.extend(cmdline_args)
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=slave_fd,
|
||||
stdout=slave_fd,
|
||||
stderr=slave_fd,
|
||||
cwd=cwd,
|
||||
text=True,
|
||||
close_fds=True,
|
||||
env=env if env else os.environ,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue