Skip to content

Commit 5ea3ae7

Browse files
authored
gh-140287: Handle PYTHONSTARTUP script exceptions in the asyncio REPL (#140288)
1 parent 9dab866 commit 5ea3ae7

3 files changed

Lines changed: 51 additions & 6 deletions

File tree

Lib/asyncio/__main__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,15 @@ def run(self):
101101

102102
if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
103103
sys.audit("cpython.run_startup", startup_path)
104-
105-
import tokenize
106-
with tokenize.open(startup_path) as f:
107-
startup_code = compile(f.read(), startup_path, "exec")
104+
try:
105+
import tokenize
106+
with tokenize.open(startup_path) as f:
107+
startup_code = compile(f.read(), startup_path, "exec")
108108
exec(startup_code, console.locals)
109+
except SystemExit:
110+
raise
111+
except BaseException:
112+
console.showtraceback()
109113

110114
ps1 = getattr(sys, "ps1", ">>> ")
111115
if CAN_USE_PYREPL:

Lib/test/test_repl.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
import sys
77
import unittest
8+
from contextlib import contextmanager
89
from functools import partial
910
from textwrap import dedent
1011
from test import support
@@ -67,6 +68,19 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F
6768
spawn_asyncio_repl = partial(spawn_repl, "-m", "asyncio", custom=True)
6869

6970

71+
@contextmanager
72+
def temp_pythonstartup(*, source: str, histfile: str = ".pythonhist"):
73+
"""Create environment variables for a PYTHONSTARTUP script in a temporary directory."""
74+
with os_helper.temp_dir() as tmpdir:
75+
filename = os.path.join(tmpdir, "pythonstartup.py")
76+
with open(filename, "w") as f:
77+
f.write(source)
78+
yield {
79+
"PYTHONSTARTUP": filename,
80+
"PYTHON_HISTORY": os.path.join(tmpdir, histfile)
81+
}
82+
83+
7084
def run_on_interactive_mode(source):
7185
"""Spawn a new Python interpreter, pass the given
7286
input source code from the stdin and return the
@@ -276,8 +290,6 @@ def make_repl(env):
276290
""") % script
277291
self.assertIn(expected, output)
278292

279-
280-
281293
def test_runsource_show_syntax_error_location(self):
282294
user_input = dedent("""def f(x, x): ...
283295
""")
@@ -449,6 +461,33 @@ def test_quiet_mode(self):
449461
self.assertEqual(p.returncode, 0)
450462
self.assertEqual(output[:3], ">>>")
451463

464+
@support.force_not_colorized
465+
@support.subTests(
466+
("startup_code", "expected_error"),
467+
[
468+
("some invalid syntax\n", "SyntaxError: invalid syntax"),
469+
("1/0\n", "ZeroDivisionError: division by zero"),
470+
],
471+
)
472+
def test_pythonstartup_failure(self, startup_code, expected_error):
473+
startup_env = self.enterContext(
474+
temp_pythonstartup(source=startup_code, histfile=".asyncio_history"))
475+
476+
p = spawn_repl(
477+
"-qm", "asyncio",
478+
env=os.environ | startup_env,
479+
isolated=False,
480+
custom=True)
481+
p.stdin.write("print('user code', 'executed')\n")
482+
output = kill_python(p)
483+
self.assertEqual(p.returncode, 0)
484+
485+
tb_hint = f'File "{startup_env["PYTHONSTARTUP"]}", line 1'
486+
self.assertIn(tb_hint, output)
487+
self.assertIn(expected_error, output)
488+
489+
self.assertIn("user code executed", output)
490+
452491

453492
if __name__ == "__main__":
454493
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :mod:`asyncio` REPL now handles exceptions when executing :envvar:`PYTHONSTARTUP` scripts.
2+
Patch by Bartosz Sławecki.

0 commit comments

Comments
 (0)