pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/pull/140288.patch

File "", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn("from pythonstartup", output) - self.assertIn(expected, output) - + for repl_name, repl in ("REPL", spawn_repl), ("asyncio REPL", spawn_asyncio_repl): + with os_helper.temp_dir() as tmpdir: + script = os.path.join(tmpdir, "pythonstartup.py") + with open(script, "w") as f: + f.write("print('from pythonstartup')" + os.linesep) + + env = os.environ.copy() + env['PYTHONSTARTUP'] = script + env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") + p = repl(env=env) + p.stdin.write("1/0") + output = kill_python(p) + + with self.subTest(repl_name): + self.assertIn("Traceback (most recent call last):", output) + expected = dedent(""" + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) + self.assertIn("from pythonstartup", output) + self.assertIn(expected, output) + + def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input - with os_helper.temp_dir() as tmpdir: - script = os.path.join(tmpdir, "pythonstartup.py") - with open(script, "w") as f: - f.write("def foo():\n 1/0\n") - - env = os.environ.copy() - env['PYTHONSTARTUP'] = script - env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") - p = make_repl(env) - p.stdin.write('foo()') - output = kill_python(p) - expected = dedent(""" - Traceback (most recent call last): - File "", line 1, in - foo() - ~~~^^ - File "%s", line 2, in foo - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) % script - self.assertIn(expected, output) + for repl_name, repl in ("REPL", spawn_repl), ("asyncio REPL", spawn_asyncio_repl): + with os_helper.temp_dir() as tmpdir: + script = os.path.join(tmpdir, "pythonstartup.py") + with open(script, "w") as f: + f.write("def foo():\n 1/0\n") + + env = os.environ.copy() + env['PYTHONSTARTUP'] = script + env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") + p = repl(env=env) + p.stdin.write('foo()') + output = kill_python(p) + + with self.subTest(repl_name): + self.assertIn("Traceback (most recent call last):", output) + expected = dedent(""" + File "", line 1, in + foo() + ~~~^^ + File "%s", line 2, in foo + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) % script + self.assertIn(expected, output) From d158dbf272d7ac0ebc210db897409ad614e13053 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Oct 2025 02:03:31 +0100 Subject: [PATCH 07/45] Improve test structure --- Lib/test/test_repl.py | 153 +++++++++++++++++++++--------------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 6dbf28605ce012..6a51eacd67e068 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -5,6 +5,7 @@ import subprocess import sys import unittest +from contextlib import contextmanager from functools import partial from textwrap import dedent from test import support @@ -28,7 +29,7 @@ raise unittest.SkipTest("test module requires subprocess") -def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=False, **kw): +def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=False, isolated=True, **kw): """Run the Python REPL with the given arguments. kw is extra keyword args to pass to subprocess.Popen. Returns a Popen @@ -42,7 +43,11 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F # path may be used by PyConfig_Get("module_search_paths") to build the # default module search path. stdin_fname = os.path.join(os.path.dirname(sys.executable), "") - cmd_line = [stdin_fname, '-I'] + cmd_line = [stdin_fname] + # Isolated mode implies -E, -P and -s, purifies sys.path and ignores PYTHON* + # variables. + if isolated: + cmd_line.append('-I') # Don't re-run the built-in REPL from interactive mode # if we're testing a custom REPL (such as the asyncio REPL). if not custom: @@ -197,63 +202,6 @@ def foo(x): ] self.assertEqual(traceback_lines, expected_lines) - def test_pythonstartup_success(self): - # errors based on https://github.com/python/cpython/issues/137576 - # case 1: error in user input, but PYTHONSTARTUP is fine - for repl_name, repl in ("REPL", spawn_repl), ("asyncio REPL", spawn_asyncio_repl): - with os_helper.temp_dir() as tmpdir: - script = os.path.join(tmpdir, "pythonstartup.py") - with open(script, "w") as f: - f.write("print('from pythonstartup')" + os.linesep) - - env = os.environ.copy() - env['PYTHONSTARTUP'] = script - env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") - p = repl(env=env) - p.stdin.write("1/0") - output = kill_python(p) - - with self.subTest(repl_name): - self.assertIn("Traceback (most recent call last):", output) - expected = dedent(""" - File "", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn("from pythonstartup", output) - self.assertIn(expected, output) - - def test_pythonstartup_failure(self): - # case 2: error in PYTHONSTARTUP triggered by user input - for repl_name, repl in ("REPL", spawn_repl), ("asyncio REPL", spawn_asyncio_repl): - with os_helper.temp_dir() as tmpdir: - script = os.path.join(tmpdir, "pythonstartup.py") - with open(script, "w") as f: - f.write("def foo():\n 1/0\n") - - env = os.environ.copy() - env['PYTHONSTARTUP'] = script - env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".pythonhist") - p = repl(env=env) - p.stdin.write('foo()') - output = kill_python(p) - - with self.subTest(repl_name): - self.assertIn("Traceback (most recent call last):", output) - expected = dedent(""" - File "", line 1, in - foo() - ~~~^^ - File "%s", line 2, in foo - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) % script - self.assertIn(expected, output) - - - def test_runsource_show_syntax_error_location(self): user_input = dedent("""def f(x, x): ... """) @@ -287,24 +235,6 @@ def bar(x): expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'\')" self.assertIn(expected, output, expected) - def test_asyncio_repl_reaches_python_startup_script(self): - with os_helper.temp_dir() as tmpdir: - script = os.path.join(tmpdir, "pythonstartup.py") - with open(script, "w") as f: - f.write("print('pythonstartup done!')" + os.linesep) - f.write("exit(0)" + os.linesep) - - env = os.environ.copy() - env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") - env["PYTHONSTARTUP"] = script - subprocess.check_call( - [sys.executable, "-m", "asyncio"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env, - timeout=SHORT_TIMEOUT, - ) - @unittest.skipUnless(pty, "requires pty") def test_asyncio_repl_is_ok(self): m, s = pty.openpty() @@ -341,6 +271,75 @@ def test_asyncio_repl_is_ok(self): self.assertEqual(exit_code, 0, "".join(output)) +@contextmanager +def pythonstartup_env(*, script: str, histfile: str = ".pythonhist", env=None): + with os_helper.temp_dir() as tmpdir: + filename = os.path.join(tmpdir, "pythonstartup.py") + with open(filename, "w") as f: + f.write(os.linesep.join(script.splitlines())) + if env is None: + env = os.environ.copy() + yield env | {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} + + +class TestPythonStartup(unittest.TestCase): + REPLS = [ + ("REPL", spawn_repl, ".pythonhist"), + ("asyncio REPL", spawn_asyncio_repl, ".asyncio_history"), + ] + + def test_pythonstartup_success(self): + # errors based on https://github.com/python/cpython/issues/137576 + # case 1: error in user input, but PYTHONSTARTUP is fine + for repl_name, repl_factory, histfile in self.REPLS: + with ( + self.subTest(repl_name), + pythonstartup_env(script="print('from pythonstartup')", histfile=histfile) as env + ): + p = repl_factory(env=env, isolated=False) + p.stdin.write("1/0") + output = kill_python(p) + + for chunk in ( + "from pythonstartup", + "Traceback (most recent call last):", + """\ + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + """ + ): + self.assertIn(dedent(chunk), output) + + def test_pythonstartup_failure(self): + # case 2: error in PYTHONSTARTUP triggered by user input + for repl_name, repl_factory, histfile in self.REPLS: + with ( + self.subTest(repl_name), + pythonstartup_env(script="def foo():\n 1/0\n", histfile=histfile) as env + ): + p = repl_factory(env=env, isolated=False) + p.stdin.write('foo()') + output = kill_python(p) + + for chunk in ( + "Traceback (most recent call last):", + """\ + File "", line 1, in + foo() + ~~~^^ + """, + f"""\ + File "{env['PYTHONSTARTUP']}", line 2, in foo + 1/0 + ~^~ + ZeroDivisionError: division by zero + """ + ): + self.assertIn(dedent(chunk), output) + + @support.force_not_colorized_test_class class TestInteractiveModeSyntaxErrors(unittest.TestCase): From 9355da7f36b2bc0063c5f0b2fff5527cc4f7d32e Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Oct 2025 02:06:23 +0100 Subject: [PATCH 08/45] Use `SHORT_TIMEOUT` --- Lib/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 6a51eacd67e068..ff99359e9d38cb 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -296,7 +296,7 @@ def test_pythonstartup_success(self): self.subTest(repl_name), pythonstartup_env(script="print('from pythonstartup')", histfile=histfile) as env ): - p = repl_factory(env=env, isolated=False) + p = repl_factory(env=env, isolated=False, timeout=SHORT_TIMEOUT) p.stdin.write("1/0") output = kill_python(p) @@ -319,7 +319,7 @@ def test_pythonstartup_failure(self): self.subTest(repl_name), pythonstartup_env(script="def foo():\n 1/0\n", histfile=histfile) as env ): - p = repl_factory(env=env, isolated=False) + p = repl_factory(env=env, isolated=False, timeout=SHORT_TIMEOUT) p.stdin.write('foo()') output = kill_python(p) From 3dc4cac7cbe6618e06d597afd4b625519865d730 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Oct 2025 02:10:11 +0100 Subject: [PATCH 09/45] Revert "Use `SHORT_TIMEOUT`" This reverts commit 9355da7f36b2bc0063c5f0b2fff5527cc4f7d32e. --- Lib/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index ff99359e9d38cb..6a51eacd67e068 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -296,7 +296,7 @@ def test_pythonstartup_success(self): self.subTest(repl_name), pythonstartup_env(script="print('from pythonstartup')", histfile=histfile) as env ): - p = repl_factory(env=env, isolated=False, timeout=SHORT_TIMEOUT) + p = repl_factory(env=env, isolated=False) p.stdin.write("1/0") output = kill_python(p) @@ -319,7 +319,7 @@ def test_pythonstartup_failure(self): self.subTest(repl_name), pythonstartup_env(script="def foo():\n 1/0\n", histfile=histfile) as env ): - p = repl_factory(env=env, isolated=False, timeout=SHORT_TIMEOUT) + p = repl_factory(env=env, isolated=False) p.stdin.write('foo()') output = kill_python(p) From c786584d99c54abc65dc508c413178e30f1e4d9a Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Oct 2025 02:26:50 +0100 Subject: [PATCH 10/45] Force no colorization --- Lib/test/test_repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 6a51eacd67e068..a769e5cb8d307a 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -282,6 +282,7 @@ def pythonstartup_env(*, script: str, histfile: str = ".pythonhist", env=None): yield env | {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} +@support.force_not_colorized_test_class class TestPythonStartup(unittest.TestCase): REPLS = [ ("REPL", spawn_repl, ".pythonhist"), From b76db6784b025fd31d263505cbf87401517202c4 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Oct 2025 17:59:38 +0100 Subject: [PATCH 11/45] Linecache doesn't matter and shouldn't break tests --- Lib/test/test_repl.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index a769e5cb8d307a..ba9f64e08a71a7 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -304,14 +304,10 @@ def test_pythonstartup_success(self): for chunk in ( "from pythonstartup", "Traceback (most recent call last):", - """\ - File "", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """ + 'File "", line 1, in ', + "ZeroDivisionError: division by zero", ): - self.assertIn(dedent(chunk), output) + self.assertIn(chunk, output) def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input @@ -326,19 +322,11 @@ def test_pythonstartup_failure(self): for chunk in ( "Traceback (most recent call last):", - """\ - File "", line 1, in - foo() - ~~~^^ - """, - f"""\ - File "{env['PYTHONSTARTUP']}", line 2, in foo - 1/0 - ~^~ - ZeroDivisionError: division by zero - """ + 'File "", line 1, in ', + f'File "{env['PYTHONSTARTUP']}", line 2, in foo', + "ZeroDivisionError: division by zero", ): - self.assertIn(dedent(chunk), output) + self.assertIn(chunk, output) @support.force_not_colorized_test_class From cad1748c94a6838262caf9d62a1421a36e058e08 Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Oct 2025 01:53:25 +0100 Subject: [PATCH 12/45] Don't rely on line numbering on Windows... --- Lib/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index ba9f64e08a71a7..c2f6bd5ca8f163 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -323,7 +323,7 @@ def test_pythonstartup_failure(self): for chunk in ( "Traceback (most recent call last):", 'File "", line 1, in ', - f'File "{env['PYTHONSTARTUP']}", line 2, in foo', + f'File "{env['PYTHONSTARTUP']}", line ', "ZeroDivisionError: division by zero", ): self.assertIn(chunk, output) From d114ed5660041cf5e67e530ba678450f96cee7aa Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Oct 2025 01:53:51 +0100 Subject: [PATCH 13/45] Different names --- Lib/test/test_repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index c2f6bd5ca8f163..e5b3a80badbf86 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -301,13 +301,13 @@ def test_pythonstartup_success(self): p.stdin.write("1/0") output = kill_python(p) - for chunk in ( + for expected in ( "from pythonstartup", "Traceback (most recent call last):", 'File "", line 1, in ', "ZeroDivisionError: division by zero", ): - self.assertIn(chunk, output) + self.assertIn(expected, output) def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input @@ -320,13 +320,13 @@ def test_pythonstartup_failure(self): p.stdin.write('foo()') output = kill_python(p) - for chunk in ( + for expected in ( "Traceback (most recent call last):", 'File "", line 1, in ', f'File "{env['PYTHONSTARTUP']}", line ', "ZeroDivisionError: division by zero", ): - self.assertIn(chunk, output) + self.assertIn(expected, output) @support.force_not_colorized_test_class From e6e10adb7aa75ee424ba6d57d94fec50dbd6e695 Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Oct 2025 18:20:54 +0100 Subject: [PATCH 14/45] Idiomatize and simplify --- Lib/test/test_repl.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index e5b3a80badbf86..617564f56144b7 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -272,14 +272,12 @@ def test_asyncio_repl_is_ok(self): @contextmanager -def pythonstartup_env(*, script: str, histfile: str = ".pythonhist", env=None): +def new_startup_env(*, code: str, histfile: str = ".pythonhist"): with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: - f.write(os.linesep.join(script.splitlines())) - if env is None: - env = os.environ.copy() - yield env | {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} + f.write(os.linesep.join(code.splitlines())) + yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} @support.force_not_colorized_test_class @@ -292,12 +290,13 @@ class TestPythonStartup(unittest.TestCase): def test_pythonstartup_success(self): # errors based on https://github.com/python/cpython/issues/137576 # case 1: error in user input, but PYTHONSTARTUP is fine + startup_code = "print('from pythonstartup')" for repl_name, repl_factory, histfile in self.REPLS: with ( self.subTest(repl_name), - pythonstartup_env(script="print('from pythonstartup')", histfile=histfile) as env + new_startup_env(code=startup_code, histfile=histfile) as startup_env ): - p = repl_factory(env=env, isolated=False) + p = repl_factory(env=os.environ | startup_env, isolated=False) p.stdin.write("1/0") output = kill_python(p) @@ -311,19 +310,20 @@ def test_pythonstartup_success(self): def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input + startup_code = "def foo():\n 1/0\n" for repl_name, repl_factory, histfile in self.REPLS: with ( self.subTest(repl_name), - pythonstartup_env(script="def foo():\n 1/0\n", histfile=histfile) as env + new_startup_env(code=startup_code, histfile=histfile) as startup_env ): - p = repl_factory(env=env, isolated=False) + p = repl_factory(env=os.environ | startup_env, isolated=False) p.stdin.write('foo()') output = kill_python(p) for expected in ( "Traceback (most recent call last):", 'File "", line 1, in ', - f'File "{env['PYTHONSTARTUP']}", line ', + f'File "{startup_env['PYTHONSTARTUP']}", line ', "ZeroDivisionError: division by zero", ): self.assertIn(expected, output) From 149740ae5ca17d280f54f9c2d347bb256413ef1d Mon Sep 17 00:00:00 2001 From: bswck Date: Thu, 30 Oct 2025 08:54:12 +0100 Subject: [PATCH 15/45] Purifying `sys.path` is implied by `-P` --- Lib/test/test_repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 617564f56144b7..1c9576d8511b83 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -44,8 +44,7 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F # default module search path. stdin_fname = os.path.join(os.path.dirname(sys.executable), "") cmd_line = [stdin_fname] - # Isolated mode implies -E, -P and -s, purifies sys.path and ignores PYTHON* - # variables. + # Isolated mode implies -EPs and ignores PYTHON* variables. if isolated: cmd_line.append('-I') # Don't re-run the built-in REPL from interactive mode From a4c307eec48259ca895e391395a7b38ce4479d3e Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:28:10 +0100 Subject: [PATCH 16/45] Separate tests for regular and asyncio REPL --- Lib/test/test_repl.py | 140 +++++++++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 49 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 1c9576d8511b83..fdef21ee2f7952 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -234,6 +234,46 @@ def bar(x): expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'\')" self.assertIn(expected, output, expected) + def test_pythonstartup_success(self): + # errors based on https://github.com/python/cpython/issues/137576 + # case 1: error in user input, but PYTHONSTARTUP is fine + startup_code = "print('notice from pythonstartup')" + with new_startup_env(code=startup_code, histfile=".pythonhist") as startup_env: + # -q to suppress noise + p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) + p.stdin.write("1/0") + output_lines = kill_python(p).splitlines() + traceback_lines = output_lines[2:-1] + expected_lines = [ + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + ] + self.assertEqual(output_lines[0], 'notice from pythonstartup') + self.assertEqual(traceback_lines, expected_lines) + + def test_pythonstartup_failure(self): + # case 2: error in PYTHONSTARTUP triggered by user input + startup_code = "def foo():\n 1/0\n" + with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: + # -q to suppress noise + p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) + p.stdin.write("foo()") + traceback_lines = kill_python(p).splitlines()[1:-1] + expected_lines = [ + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' foo()', + ' ~~~^^', + f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + ] + self.assertEqual(traceback_lines, expected_lines) + @unittest.skipUnless(pty, "requires pty") def test_asyncio_repl_is_ok(self): m, s = pty.openpty() @@ -279,55 +319,6 @@ def new_startup_env(*, code: str, histfile: str = ".pythonhist"): yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} -@support.force_not_colorized_test_class -class TestPythonStartup(unittest.TestCase): - REPLS = [ - ("REPL", spawn_repl, ".pythonhist"), - ("asyncio REPL", spawn_asyncio_repl, ".asyncio_history"), - ] - - def test_pythonstartup_success(self): - # errors based on https://github.com/python/cpython/issues/137576 - # case 1: error in user input, but PYTHONSTARTUP is fine - startup_code = "print('from pythonstartup')" - for repl_name, repl_factory, histfile in self.REPLS: - with ( - self.subTest(repl_name), - new_startup_env(code=startup_code, histfile=histfile) as startup_env - ): - p = repl_factory(env=os.environ | startup_env, isolated=False) - p.stdin.write("1/0") - output = kill_python(p) - - for expected in ( - "from pythonstartup", - "Traceback (most recent call last):", - 'File "", line 1, in ', - "ZeroDivisionError: division by zero", - ): - self.assertIn(expected, output) - - def test_pythonstartup_failure(self): - # case 2: error in PYTHONSTARTUP triggered by user input - startup_code = "def foo():\n 1/0\n" - for repl_name, repl_factory, histfile in self.REPLS: - with ( - self.subTest(repl_name), - new_startup_env(code=startup_code, histfile=histfile) as startup_env - ): - p = repl_factory(env=os.environ | startup_env, isolated=False) - p.stdin.write('foo()') - output = kill_python(p) - - for expected in ( - "Traceback (most recent call last):", - 'File "", line 1, in ', - f'File "{startup_env['PYTHONSTARTUP']}", line ', - "ZeroDivisionError: division by zero", - ): - self.assertIn(expected, output) - - @support.force_not_colorized_test_class class TestInteractiveModeSyntaxErrors(unittest.TestCase): @@ -347,6 +338,7 @@ def f(): self.assertEqual(traceback_lines, expected_lines) +@support.force_not_colorized_test_class class TestAsyncioREPL(unittest.TestCase): def test_multiple_statements_fail_early(self): user_input = "1 / 0; print(f'afterwards: {1+1}')" @@ -391,6 +383,56 @@ def test_toplevel_contextvars_async(self): expected = "toplevel contextvar test: ok" self.assertIn(expected, output, expected) + def test_pythonstartup_success(self): + startup_code = "import sys\nprint('notice from pythonstartup in asyncio repl', file=sys.stderr)" + with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p.stdin.write("1/0") + kill_python(p) + output_lines = p.stderr.read().splitlines() + p.stderr.close() + self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') + tb_start_lines = output_lines[4:6] + tb_final_lines = output_lines[13:] + expected_lines = [ + '>>> import asyncio', + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + '', + 'exiting asyncio REPL...', + ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + + def test_pythonstartup_failure(self): + startup_code = "def foo():\n 1/0\n" + with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p.stdin.write("foo()") + kill_python(p) + output = p.stderr.read() + p.stderr.close() + tb_start_lines = output.splitlines()[3:5] + tb_final_lines = output.splitlines()[12:] + + expected_lines = [ + '>>> import asyncio', + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' foo()', + ' ~~~^^', + f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + '', + 'exiting asyncio REPL...', + ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + if __name__ == "__main__": unittest.main() From a774da555644a953924e00d74d690843b3a31dea Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:28:29 +0100 Subject: [PATCH 17/45] Remove duplicate assertion --- Lib/test/test_repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index fdef21ee2f7952..352cff7ced8b38 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -405,7 +405,6 @@ def test_pythonstartup_success(self): 'exiting asyncio REPL...', ] self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) - self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) def test_pythonstartup_failure(self): startup_code = "def foo():\n 1/0\n" From b3ed3d4b88acaf0a75df92170959157faafa10b4 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:30:53 +0100 Subject: [PATCH 18/45] Document `new_startup_env` --- Lib/test/test_repl.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 352cff7ced8b38..e88491e57c537e 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -68,6 +68,16 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F spawn_asyncio_repl = partial(spawn_repl, "-m", "asyncio", custom=True) +@contextmanager +def new_startup_env(*, code: str, histfile: str = ".pythonhist"): + """Create temporary environment variables for a PYTHONSTARTUP script.""" + with os_helper.temp_dir() as tmpdir: + filename = os.path.join(tmpdir, "pythonstartup.py") + with open(filename, "w") as f: + f.write(os.linesep.join(code.splitlines())) + yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} + + def run_on_interactive_mode(source): """Spawn a new Python interpreter, pass the given input source code from the stdin and return the @@ -310,15 +320,6 @@ def test_asyncio_repl_is_ok(self): self.assertEqual(exit_code, 0, "".join(output)) -@contextmanager -def new_startup_env(*, code: str, histfile: str = ".pythonhist"): - with os_helper.temp_dir() as tmpdir: - filename = os.path.join(tmpdir, "pythonstartup.py") - with open(filename, "w") as f: - f.write(os.linesep.join(code.splitlines())) - yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} - - @support.force_not_colorized_test_class class TestInteractiveModeSyntaxErrors(unittest.TestCase): From df6dfd8dd93c35657d0453501c6cdac19f006ef8 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:31:20 +0100 Subject: [PATCH 19/45] Fix `new_startup_env` docs --- Lib/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index e88491e57c537e..4dd6c8b7711231 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -70,7 +70,7 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F @contextmanager def new_startup_env(*, code: str, histfile: str = ".pythonhist"): - """Create temporary environment variables for a PYTHONSTARTUP script.""" + """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: From b004839d30354af5b73ecadeca071121c1eb8277 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:33:31 +0100 Subject: [PATCH 20/45] More meaningful line breaks --- Lib/test/test_repl.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 4dd6c8b7711231..6a425a0f300d56 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -392,9 +392,12 @@ def test_pythonstartup_success(self): kill_python(p) output_lines = p.stderr.read().splitlines() p.stderr.close() + self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') + tb_start_lines = output_lines[4:6] tb_final_lines = output_lines[13:] + expected_lines = [ '>>> import asyncio', 'Traceback (most recent call last):', @@ -405,6 +408,7 @@ def test_pythonstartup_success(self): '', 'exiting asyncio REPL...', ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) def test_pythonstartup_failure(self): @@ -415,6 +419,7 @@ def test_pythonstartup_failure(self): kill_python(p) output = p.stderr.read() p.stderr.close() + tb_start_lines = output.splitlines()[3:5] tb_final_lines = output.splitlines()[12:] @@ -431,6 +436,7 @@ def test_pythonstartup_failure(self): '', 'exiting asyncio REPL...', ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) From ce03ccef689e6fed09ddf8ed7f2d41ea1c4b38f0 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 01:39:34 +0100 Subject: [PATCH 21/45] Better variables --- Lib/test/test_repl.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 6a425a0f300d56..1902a8eb5888ad 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -253,6 +253,9 @@ def test_pythonstartup_success(self): p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("1/0") output_lines = kill_python(p).splitlines() + + self.assertEqual(output_lines[0], 'notice from pythonstartup') + traceback_lines = output_lines[2:-1] expected_lines = [ 'Traceback (most recent call last):', @@ -261,7 +264,6 @@ def test_pythonstartup_success(self): ' ~^~', 'ZeroDivisionError: division by zero', ] - self.assertEqual(output_lines[0], 'notice from pythonstartup') self.assertEqual(traceback_lines, expected_lines) def test_pythonstartup_failure(self): @@ -417,11 +419,11 @@ def test_pythonstartup_failure(self): p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) p.stdin.write("foo()") kill_python(p) - output = p.stderr.read() + output_lines = p.stderr.read().splitlines() p.stderr.close() - tb_start_lines = output.splitlines()[3:5] - tb_final_lines = output.splitlines()[12:] + tb_start_lines = output_lines[3:5] + tb_final_lines = output_lines[12:] expected_lines = [ '>>> import asyncio', From 9e9251032bea7f1a6a8948970f83c3940a5211dc Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 13:35:15 +0100 Subject: [PATCH 22/45] Use default histfile --- Lib/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 1902a8eb5888ad..601b9e18089492 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -248,7 +248,7 @@ def test_pythonstartup_success(self): # errors based on https://github.com/python/cpython/issues/137576 # case 1: error in user input, but PYTHONSTARTUP is fine startup_code = "print('notice from pythonstartup')" - with new_startup_env(code=startup_code, histfile=".pythonhist") as startup_env: + with new_startup_env(code=startup_code) as startup_env: # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("1/0") @@ -269,7 +269,7 @@ def test_pythonstartup_success(self): def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input startup_code = "def foo():\n 1/0\n" - with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: + with new_startup_env(code=startup_code) as startup_env: # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("foo()") From 0a50a506bcc1f13e55768bdd386afda1d43b9f80 Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 13:35:20 +0100 Subject: [PATCH 23/45] Fix newline --- Lib/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 601b9e18089492..fd9991dd744eca 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -74,7 +74,7 @@ def new_startup_env(*, code: str, histfile: str = ".pythonhist"): with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: - f.write(os.linesep.join(code.splitlines())) + f.write('\n'.join(code.splitlines())) yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} From f8b8d533f06a827443a920800fcda76650d909eb Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 13:41:48 +0100 Subject: [PATCH 24/45] Use `TestCase.enterContext` --- Lib/test/test_repl.py | 159 ++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 82 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index fd9991dd744eca..f537468eafd690 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -248,43 +248,42 @@ def test_pythonstartup_success(self): # errors based on https://github.com/python/cpython/issues/137576 # case 1: error in user input, but PYTHONSTARTUP is fine startup_code = "print('notice from pythonstartup')" - with new_startup_env(code=startup_code) as startup_env: - # -q to suppress noise - p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) - p.stdin.write("1/0") - output_lines = kill_python(p).splitlines() - - self.assertEqual(output_lines[0], 'notice from pythonstartup') - - traceback_lines = output_lines[2:-1] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - ] - self.assertEqual(traceback_lines, expected_lines) + startup_env = self.enterContext(new_startup_env(code=startup_code)) + # -q to suppress noise + p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) + p.stdin.write("1/0") + output_lines = kill_python(p).splitlines() + self.assertEqual(output_lines[0], 'notice from pythonstartup') + + traceback_lines = output_lines[2:-1] + expected_lines = [ + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + ] + self.assertEqual(traceback_lines, expected_lines) def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input startup_code = "def foo():\n 1/0\n" - with new_startup_env(code=startup_code) as startup_env: - # -q to suppress noise - p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) - p.stdin.write("foo()") - traceback_lines = kill_python(p).splitlines()[1:-1] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' foo()', - ' ~~~^^', - f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - ] - self.assertEqual(traceback_lines, expected_lines) + startup_env = self.enterContext(new_startup_env(code=startup_code)) + # -q to suppress noise + p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) + p.stdin.write("foo()") + traceback_lines = kill_python(p).splitlines()[1:-1] + expected_lines = [ + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' foo()', + ' ~~~^^', + f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + ] + self.assertEqual(traceback_lines, expected_lines) @unittest.skipUnless(pty, "requires pty") def test_asyncio_repl_is_ok(self): @@ -388,58 +387,54 @@ def test_toplevel_contextvars_async(self): def test_pythonstartup_success(self): startup_code = "import sys\nprint('notice from pythonstartup in asyncio repl', file=sys.stderr)" - with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: - p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) - p.stdin.write("1/0") - kill_python(p) - output_lines = p.stderr.read().splitlines() - p.stderr.close() - - self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') - - tb_start_lines = output_lines[4:6] - tb_final_lines = output_lines[13:] - - expected_lines = [ - '>>> import asyncio', - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - '', - 'exiting asyncio REPL...', - ] - - self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + startup_env = self.enterContext(new_startup_env(code=startup_code, histfile=".asyncio_history")) + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p.stdin.write("1/0") + kill_python(p) + output_lines = p.stderr.read().splitlines() + p.stderr.close() + + self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') + + tb_start_lines = output_lines[4:6] + tb_final_lines = output_lines[13:] + expected_lines = [ + '>>> import asyncio', + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + '', + 'exiting asyncio REPL...', + ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) def test_pythonstartup_failure(self): startup_code = "def foo():\n 1/0\n" - with new_startup_env(code=startup_code, histfile=".asyncio_history") as startup_env: - p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) - p.stdin.write("foo()") - kill_python(p) - output_lines = p.stderr.read().splitlines() - p.stderr.close() - - tb_start_lines = output_lines[3:5] - tb_final_lines = output_lines[12:] - - expected_lines = [ - '>>> import asyncio', - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' foo()', - ' ~~~^^', - f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - '', - 'exiting asyncio REPL...', - ] - - self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + startup_env = self.enterContext(new_startup_env(code=startup_code, histfile=".asyncio_history")) + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p.stdin.write("foo()") + kill_python(p) + output_lines = p.stderr.read().splitlines() + p.stderr.close() + + tb_start_lines = output_lines[3:5] + tb_final_lines = output_lines[12:] + expected_lines = [ + '>>> import asyncio', + 'Traceback (most recent call last):', + ' File "", line 1, in ', + ' foo()', + ' ~~~^^', + f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', + ' 1/0', + ' ~^~', + 'ZeroDivisionError: division by zero', + '', + 'exiting asyncio REPL...', + ] + self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) if __name__ == "__main__": From 5701fefeeff38fe81e89add86965d05b6e645d4b Mon Sep 17 00:00:00 2001 From: bswck Date: Sat, 8 Nov 2025 16:00:18 +0100 Subject: [PATCH 25/45] Remove lines with ps1 --- Lib/test/test_repl.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index f537468eafd690..689df971f87ad6 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -396,10 +396,9 @@ def test_pythonstartup_success(self): self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') - tb_start_lines = output_lines[4:6] + tb_start_lines = output_lines[5:6] tb_final_lines = output_lines[13:] expected_lines = [ - '>>> import asyncio', 'Traceback (most recent call last):', ' File "", line 1, in ', ' 1/0', @@ -419,10 +418,9 @@ def test_pythonstartup_failure(self): output_lines = p.stderr.read().splitlines() p.stderr.close() - tb_start_lines = output_lines[3:5] + tb_start_lines = output_lines[4:5] tb_final_lines = output_lines[12:] expected_lines = [ - '>>> import asyncio', 'Traceback (most recent call last):', ' File "", line 1, in ', ' foo()', From 875fd2a42a01436402070f7cae5ac68e7a098af5 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Tue, 16 Dec 2025 04:36:36 +0100 Subject: [PATCH 26/45] Employ `asyncio.Runner` in the asyncio REPL --- Lib/asyncio/__main__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 6cd21a2c9df0f2..573cc9223476a7 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -192,8 +192,8 @@ def interrupt(self) -> None: from _pyrepl.main import CAN_USE_PYREPL return_code = 0 - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) + runner = asyncio.Runner() + loop = runner.get_loop() repl_locals = {'asyncio': asyncio} for key in {'__name__', '__package__', @@ -245,4 +245,5 @@ def interrupt(self) -> None: break console.write('exiting asyncio REPL...\n') + runner.close() sys.exit(return_code) From 4377d82c8dd96117271aa2a7ee1b6ff0a0698d39 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Tue, 16 Dec 2025 05:36:11 +0100 Subject: [PATCH 27/45] Revert "Employ `asyncio.Runner` in the asyncio REPL" This reverts commit 875fd2a42a01436402070f7cae5ac68e7a098af5. --- Lib/asyncio/__main__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 573cc9223476a7..6cd21a2c9df0f2 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -192,8 +192,8 @@ def interrupt(self) -> None: from _pyrepl.main import CAN_USE_PYREPL return_code = 0 - runner = asyncio.Runner() - loop = runner.get_loop() + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) repl_locals = {'asyncio': asyncio} for key in {'__name__', '__package__', @@ -245,5 +245,4 @@ def interrupt(self) -> None: break console.write('exiting asyncio REPL...\n') - runner.close() sys.exit(return_code) From b9ffea764c03679f35a8787aa31ba8b009e70dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Sat, 20 Dec 2025 18:36:11 +0100 Subject: [PATCH 28/45] Add a todo for future --- Lib/asyncio/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index db788531e225ce..b7c3af1356f97d 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -103,6 +103,7 @@ def run(self): startup_code = compile(f.read(), startup_path, "exec") try: exec(startup_code, console.locals) + # TODO: Revisit in GH-143023 except SystemExit: raise except BaseException: From 60209965ca934eae34202ae6c4c616dd7e327d64 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 16:02:21 +0100 Subject: [PATCH 29/45] Rename the decorator --- Lib/test/test_repl.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 1d9e6c36c98818..89ea2b9524569f 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -69,13 +69,16 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F @contextmanager -def new_startup_env(*, code: str, histfile: str = ".pythonhist"): +def new_pythonstartup_env(*, code: str, histfile: str = ".pythonhist"): """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: f.write('\n'.join(code.splitlines())) - yield {"PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile)} + yield { + "PYTHONSTARTUP": filename, + "PYTHON_HISTORY": os.path.join(tmpdir, histfile) + } def run_on_interactive_mode(source): @@ -369,7 +372,8 @@ def test_pythonstartup_success(self): # errors based on https://github.com/python/cpython/issues/137576 # case 1: error in user input, but PYTHONSTARTUP is fine startup_code = "print('notice from pythonstartup')" - startup_env = self.enterContext(new_startup_env(code=startup_code)) + startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) + # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("1/0") @@ -389,7 +393,8 @@ def test_pythonstartup_success(self): def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input startup_code = "def foo():\n 1/0\n" - startup_env = self.enterContext(new_startup_env(code=startup_code)) + startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) + # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("foo()") @@ -479,7 +484,9 @@ def test_quiet_mode(self): def test_pythonstartup_success(self): startup_code = "import sys\nprint('notice from pythonstartup in asyncio repl', file=sys.stderr)" - startup_env = self.enterContext(new_startup_env(code=startup_code, histfile=".asyncio_history")) + startup_env = self.enterContext( + new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) p.stdin.write("1/0") kill_python(p) @@ -503,7 +510,9 @@ def test_pythonstartup_success(self): def test_pythonstartup_failure(self): startup_code = "def foo():\n 1/0\n" - startup_env = self.enterContext(new_startup_env(code=startup_code, histfile=".asyncio_history")) + startup_env = self.enterContext( + new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) + p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) p.stdin.write("foo()") kill_python(p) From 125efa607c50a080824af130d3d2c823c0320079 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 16:20:20 +0100 Subject: [PATCH 30/45] Colorize the news entry --- .../Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst index 82bb2e7da41032..b92b35ceffde37 100644 --- a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst +++ b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst @@ -1,2 +1,4 @@ -The asyncio REPL now properly handles exceptions in ``PYTHONSTARTUP`` -scripts. Patch by Bartosz Sławecki in :gh:`140287`. +The :mod:`asyncio` REPL now properly handles exceptions in :envvar:`PYTHONSTARTUP` scripts. +Previously, those would prevent the REPL from starting or even cause a fatal error. + +Patch by Bartosz Sławecki in :gh:`140287`. From cc624d1fe0c017be972876d11c8db3064270c08b Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 16:22:00 +0100 Subject: [PATCH 31/45] Simplify `new_pythonstartup_env` --- Lib/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 89ea2b9524569f..990addb5311b53 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -74,7 +74,7 @@ def new_pythonstartup_env(*, code: str, histfile: str = ".pythonhist"): with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: - f.write('\n'.join(code.splitlines())) + f.write(code) yield { "PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile) From 812d22f31c9d7fc89e267e91feb3a79d77185f27 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 16:43:44 +0100 Subject: [PATCH 32/45] =?UTF-8?q?Simplify=20tests=20by=20a=20gazillion=20p?= =?UTF-8?q?rocent=20=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_repl.py | 72 +++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 990addb5311b53..fa44d1d770defa 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -483,57 +483,63 @@ def test_quiet_mode(self): self.assertEqual(output[:3], ">>>") def test_pythonstartup_success(self): - startup_code = "import sys\nprint('notice from pythonstartup in asyncio repl', file=sys.stderr)" + startup_code = dedent("""\ + import sys + print('notice from pythonstartup in asyncio repl', file=sys.stderr) + """) startup_env = self.enterContext( new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) - p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p = spawn_repl( + "-qm", "asyncio", + env=os.environ | startup_env, + stderr=subprocess.PIPE, + isolated=False, + custom=True) p.stdin.write("1/0") kill_python(p) - output_lines = p.stderr.read().splitlines() + output = p.stderr.read() p.stderr.close() + self.assertStartsWith(output, 'notice from pythonstartup in asyncio repl') - self.assertEqual(output_lines[3], 'notice from pythonstartup in asyncio repl') + expected = dedent("""\ + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero - tb_start_lines = output_lines[5:6] - tb_final_lines = output_lines[13:] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - '', - 'exiting asyncio REPL...', - ] - self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + exiting asyncio REPL... + """) + self.assertEndsWith(output, expected) def test_pythonstartup_failure(self): startup_code = "def foo():\n 1/0\n" startup_env = self.enterContext( new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) - p = spawn_asyncio_repl(env=os.environ | startup_env, stderr=subprocess.PIPE, isolated=False) + p = spawn_repl( + "-qm", "asyncio", + env=os.environ | startup_env, + stderr=subprocess.PIPE, + isolated=False, + custom=True) p.stdin.write("foo()") kill_python(p) - output_lines = p.stderr.read().splitlines() + output = p.stderr.read() p.stderr.close() - tb_start_lines = output_lines[4:5] - tb_final_lines = output_lines[12:] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' foo()', - ' ~~~^^', - f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - '', - 'exiting asyncio REPL...', - ] - self.assertEqual(tb_start_lines + tb_final_lines, expected_lines) + expected = dedent(f"""\ + File "", line 1, in + foo() + ~~~^^ + File "{startup_env['PYTHONSTARTUP']}", line 2, in foo + 1/0 + ~^~ + ZeroDivisionError: division by zero + + exiting asyncio REPL... + """) + self.assertEndsWith(output, expected) if __name__ == "__main__": unittest.main() From 6749b83473ee9156f9e77cc69c69d74be5d781be Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 17:25:20 +0100 Subject: [PATCH 33/45] =?UTF-8?q?Simplify=20the=20tests=20further=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_repl.py | 67 +++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index fa44d1d770defa..419fe34a5005ea 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -374,42 +374,38 @@ def test_pythonstartup_success(self): startup_code = "print('notice from pythonstartup')" startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) - # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("1/0") - output_lines = kill_python(p).splitlines() - self.assertEqual(output_lines[0], 'notice from pythonstartup') - - traceback_lines = output_lines[2:-1] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - ] - self.assertEqual(traceback_lines, expected_lines) + output = kill_python(p) + self.assertStartsWith(output, 'notice from pythonstartup') + expected = dedent("""\ + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) + self.assertIn(expected, output) def test_pythonstartup_failure(self): # case 2: error in PYTHONSTARTUP triggered by user input startup_code = "def foo():\n 1/0\n" startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) - # -q to suppress noise p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) p.stdin.write("foo()") - traceback_lines = kill_python(p).splitlines()[1:-1] - expected_lines = [ - 'Traceback (most recent call last):', - ' File "", line 1, in ', - ' foo()', - ' ~~~^^', - f' File "{startup_env['PYTHONSTARTUP']}", line 2, in foo', - ' 1/0', - ' ~^~', - 'ZeroDivisionError: division by zero', - ] - self.assertEqual(traceback_lines, expected_lines) + output = kill_python(p) + expected = dedent(f"""\ + Traceback (most recent call last): + File "", line 1, in + foo() + ~~~^^ + File "{startup_env['PYTHONSTARTUP']}", line 2, in foo + 1/0 + ~^~ + ZeroDivisionError: division by zero + """) + self.assertIn(expected, output) @support.force_not_colorized_test_class @@ -493,13 +489,10 @@ def test_pythonstartup_success(self): p = spawn_repl( "-qm", "asyncio", env=os.environ | startup_env, - stderr=subprocess.PIPE, isolated=False, custom=True) p.stdin.write("1/0") - kill_python(p) - output = p.stderr.read() - p.stderr.close() + output = kill_python(p) self.assertStartsWith(output, 'notice from pythonstartup in asyncio repl') expected = dedent("""\ @@ -507,10 +500,8 @@ def test_pythonstartup_success(self): 1/0 ~^~ ZeroDivisionError: division by zero - - exiting asyncio REPL... """) - self.assertEndsWith(output, expected) + self.assertIn(expected, output) def test_pythonstartup_failure(self): startup_code = "def foo():\n 1/0\n" @@ -520,14 +511,10 @@ def test_pythonstartup_failure(self): p = spawn_repl( "-qm", "asyncio", env=os.environ | startup_env, - stderr=subprocess.PIPE, isolated=False, custom=True) p.stdin.write("foo()") - kill_python(p) - output = p.stderr.read() - p.stderr.close() - + output = kill_python(p) expected = dedent(f"""\ File "", line 1, in foo() @@ -536,10 +523,8 @@ def test_pythonstartup_failure(self): 1/0 ~^~ ZeroDivisionError: division by zero - - exiting asyncio REPL... """) - self.assertEndsWith(output, expected) + self.assertIn(expected, output) if __name__ == "__main__": unittest.main() From e76426dc2724e9ad8b33a341cca1a6d58323e2bb Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 17:25:42 +0100 Subject: [PATCH 34/45] And even further --- Lib/test/test_repl.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 419fe34a5005ea..ddc79a122fd523 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -479,10 +479,7 @@ def test_quiet_mode(self): self.assertEqual(output[:3], ">>>") def test_pythonstartup_success(self): - startup_code = dedent("""\ - import sys - print('notice from pythonstartup in asyncio repl', file=sys.stderr) - """) + startup_code = dedent("print('notice from pythonstartup in asyncio repl')") startup_env = self.enterContext( new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) From 376c7a4783ec58be690bd46f7d463416498e66d2 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 4 Jan 2026 17:27:30 +0100 Subject: [PATCH 35/45] Rephrase the news entry --- .../Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst index b92b35ceffde37..a8d89ca599933c 100644 --- a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst +++ b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst @@ -1,4 +1,5 @@ The :mod:`asyncio` REPL now properly handles exceptions in :envvar:`PYTHONSTARTUP` scripts. -Previously, those would prevent the REPL from starting or even cause a fatal error. +Previously, any startup exception could prevent the REPL from starting or even cause +a fatal error. Patch by Bartosz Sławecki in :gh:`140287`. From a5e4ff0e49d9081dac283e19de1174a973c8f1d7 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 1 Apr 2026 23:34:51 +0200 Subject: [PATCH 36/45] Simplify test --- Lib/test/test_repl.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 252e7145586497..7189a7cbf15964 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -501,30 +501,8 @@ def test_quiet_mode(self): self.assertEqual(p.returncode, 0) self.assertEqual(output[:3], ">>>") - def test_pythonstartup_success(self): - startup_code = dedent("print('notice from pythonstartup in asyncio repl')") - startup_env = self.enterContext( - new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) - - p = spawn_repl( - "-qm", "asyncio", - env=os.environ | startup_env, - isolated=False, - custom=True) - p.stdin.write("1/0") - output = kill_python(p) - self.assertStartsWith(output, 'notice from pythonstartup in asyncio repl') - - expected = dedent("""\ - File "", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn(expected, output) - def test_pythonstartup_failure(self): - startup_code = "def foo():\n 1/0\n" + startup_code = "1/0\n" startup_env = self.enterContext( new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) @@ -533,18 +511,16 @@ def test_pythonstartup_failure(self): env=os.environ | startup_env, isolated=False, custom=True) - p.stdin.write("foo()") + p.stdin.write("print('executed user code anyway')") output = kill_python(p) expected = dedent(f"""\ - File "", line 1, in - foo() - ~~~^^ - File "{startup_env['PYTHONSTARTUP']}", line 2, in foo + File "{startup_env['PYTHONSTARTUP']}", line 1, in 1/0 ~^~ ZeroDivisionError: division by zero """) self.assertIn(expected, output) + self.assertIn("executed user code anyway", output) if __name__ == "__main__": unittest.main() From 0d246a53a252141136ff58b58555d3563b072352 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 1 Apr 2026 23:35:05 +0200 Subject: [PATCH 37/45] Formatting --- Lib/test/test_repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 7189a7cbf15964..03a1595defcf28 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -522,5 +522,6 @@ def test_pythonstartup_failure(self): self.assertIn(expected, output) self.assertIn("executed user code anyway", output) + if __name__ == "__main__": unittest.main() From e19437b23afbb131aeb854656ae9e1c6548a78b4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 1 Apr 2026 23:36:20 +0200 Subject: [PATCH 38/45] Remove coverage for regular REPL --- Lib/test/test_repl.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 03a1595defcf28..6aa52df3853b49 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -384,45 +384,6 @@ def test_asyncio_repl_is_ok(self): self.assertEqual(exit_code, 0, "".join(output)) - def test_pythonstartup_success(self): - # errors based on https://github.com/python/cpython/issues/137576 - # case 1: error in user input, but PYTHONSTARTUP is fine - startup_code = "print('notice from pythonstartup')" - startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) - - p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) - p.stdin.write("1/0") - output = kill_python(p) - self.assertStartsWith(output, 'notice from pythonstartup') - expected = dedent("""\ - Traceback (most recent call last): - File "", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn(expected, output) - - def test_pythonstartup_failure(self): - # case 2: error in PYTHONSTARTUP triggered by user input - startup_code = "def foo():\n 1/0\n" - startup_env = self.enterContext(new_pythonstartup_env(code=startup_code)) - - p = spawn_repl("-q", env=os.environ | startup_env, isolated=False) - p.stdin.write("foo()") - output = kill_python(p) - expected = dedent(f"""\ - Traceback (most recent call last): - File "", line 1, in - foo() - ~~~^^ - File "{startup_env['PYTHONSTARTUP']}", line 2, in foo - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn(expected, output) - @support.force_not_colorized_test_class class TestInteractiveModeSyntaxErrors(unittest.TestCase): From 57af54dfe9e9eb0fad762dc5fece34bf08a7b66d Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 1 Apr 2026 23:37:49 +0200 Subject: [PATCH 39/45] Shorten news entry --- .../Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst index a8d89ca599933c..09643956d98093 100644 --- a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst +++ b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst @@ -1,5 +1,2 @@ -The :mod:`asyncio` REPL now properly handles exceptions in :envvar:`PYTHONSTARTUP` scripts. -Previously, any startup exception could prevent the REPL from starting or even cause -a fatal error. - -Patch by Bartosz Sławecki in :gh:`140287`. +The :mod:`asyncio` REPL now handles exceptions when executing :envvar:`PYTHONSTARTUP` scripts. +Patch by Bartosz Sławecki. From 442bbe3b7238875283fffe86785c74b23c29dfa9 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 13:28:19 +0200 Subject: [PATCH 40/45] Remove unnecessary decorator --- Lib/test/test_repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 6aa52df3853b49..4583795e0dcbd1 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -404,7 +404,6 @@ def f(): self.assertEqual(traceback_lines, expected_lines) -@support.force_not_colorized_test_class class TestAsyncioREPL(unittest.TestCase): def test_multiple_statements_fail_early(self): user_input = "1 / 0; print(f'afterwards: {1+1}')" From c1153457c2a80acccb4c433f4523e6a402efe520 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 13:52:34 +0200 Subject: [PATCH 41/45] Catch compilation errors too --- Lib/asyncio/__main__.py | 19 +++++++++---------- Lib/test/test_repl.py | 27 ++++++++++++++++----------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 7cdf0e886f58da..d2413eb227e97c 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -101,17 +101,16 @@ def run(self): if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")): sys.audit("cpython.run_startup", startup_path) - - import tokenize - with tokenize.open(startup_path) as f: - startup_code = compile(f.read(), startup_path, "exec") - try: + try: + import tokenize + with tokenize.open(startup_path) as f: + startup_code = compile(f.read(), startup_path, "exec") exec(startup_code, console.locals) - # TODO: Revisit in GH-143023 - except SystemExit: - raise - except BaseException: - console.showtraceback() + # TODO: Revisit in GH-143023 + except SystemExit: + raise + except BaseException: + console.showtraceback() ps1 = getattr(sys, "ps1", ">>> ") if CAN_USE_PYREPL: diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 4583795e0dcbd1..49e145f848b1c9 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -13,6 +13,7 @@ cpython_only, has_subprocess_support, os_helper, + subTests, SuppressCrashReport, SHORT_TIMEOUT, ) @@ -461,8 +462,14 @@ def test_quiet_mode(self): self.assertEqual(p.returncode, 0) self.assertEqual(output[:3], ">>>") - def test_pythonstartup_failure(self): - startup_code = "1/0\n" + @subTests( + ("startup_code", "expected_error"), + [ + ("some invalid syntax\n", "SyntaxError: invalid syntax"), + ("1/0\n", "ZeroDivisionError: division by zero"), + ], + ) + def test_pythonstartup_failure(self, startup_code, expected_error): startup_env = self.enterContext( new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) @@ -471,16 +478,14 @@ def test_pythonstartup_failure(self): env=os.environ | startup_env, isolated=False, custom=True) - p.stdin.write("print('executed user code anyway')") + p.stdin.write("print('user code', 'executed')\n") output = kill_python(p) - expected = dedent(f"""\ - File "{startup_env['PYTHONSTARTUP']}", line 1, in - 1/0 - ~^~ - ZeroDivisionError: division by zero - """) - self.assertIn(expected, output) - self.assertIn("executed user code anyway", output) + + tb_hint = f'File "{startup_env["PYTHONSTARTUP"]}", line 1' + self.assertIn(tb_hint, output) + self.assertIn(expected_error, output) + + self.assertIn("user code executed", output) if __name__ == "__main__": From 02cfe94748a48fbb1fd5be3bb3f6d815aacd9361 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 13:54:19 +0200 Subject: [PATCH 42/45] Close startup file before executing code --- Lib/asyncio/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index d2413eb227e97c..b261d9f2d20b93 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -105,7 +105,7 @@ def run(self): import tokenize with tokenize.open(startup_path) as f: startup_code = compile(f.read(), startup_path, "exec") - exec(startup_code, console.locals) + exec(startup_code, console.locals) # TODO: Revisit in GH-143023 except SystemExit: raise From 30983a5b219d523b6dde2a2ffc3e8d892b99e646 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 13:55:44 +0200 Subject: [PATCH 43/45] Rename `new_pythonstartup_env` to `temp_pythonstartup` --- Lib/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 49e145f848b1c9..bb535e6c88b6be 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -70,7 +70,7 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F @contextmanager -def new_pythonstartup_env(*, code: str, histfile: str = ".pythonhist"): +def temp_pythonstartup(*, code: str, histfile: str = ".pythonhist"): """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") @@ -471,7 +471,7 @@ def test_quiet_mode(self): ) def test_pythonstartup_failure(self, startup_code, expected_error): startup_env = self.enterContext( - new_pythonstartup_env(code=startup_code, histfile=".asyncio_history")) + temp_pythonstartup(code=startup_code, histfile=".asyncio_history")) p = spawn_repl( "-qm", "asyncio", From 8d37d531339aecea7a7d7a0ea6b026d11c1602a8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 13:56:34 +0200 Subject: [PATCH 44/45] Rename `code` to `source` (`code` could be confused with the code object) --- Lib/test/test_repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index bb535e6c88b6be..55c48e730c2e36 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -70,12 +70,12 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F @contextmanager -def temp_pythonstartup(*, code: str, histfile: str = ".pythonhist"): +def temp_pythonstartup(*, source: str, histfile: str = ".pythonhist"): """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" with os_helper.temp_dir() as tmpdir: filename = os.path.join(tmpdir, "pythonstartup.py") with open(filename, "w") as f: - f.write(code) + f.write(source) yield { "PYTHONSTARTUP": filename, "PYTHON_HISTORY": os.path.join(tmpdir, histfile) @@ -471,7 +471,7 @@ def test_quiet_mode(self): ) def test_pythonstartup_failure(self, startup_code, expected_error): startup_env = self.enterContext( - temp_pythonstartup(code=startup_code, histfile=".asyncio_history")) + temp_pythonstartup(source=startup_code, histfile=".asyncio_history")) p = spawn_repl( "-qm", "asyncio", From e357efc5b206386d8139ce1ddff663cb1d44c922 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 5 Apr 2026 14:08:29 +0200 Subject: [PATCH 45/45] Bring back uncolorization decorator (hypothesis failures) --- Lib/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 55c48e730c2e36..a2fa812cfe4bad 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -13,7 +13,6 @@ cpython_only, has_subprocess_support, os_helper, - subTests, SuppressCrashReport, SHORT_TIMEOUT, ) @@ -462,7 +461,8 @@ def test_quiet_mode(self): self.assertEqual(p.returncode, 0) self.assertEqual(output[:3], ">>>") - @subTests( + @support.force_not_colorized + @support.subTests( ("startup_code", "expected_error"), [ ("some invalid syntax\n", "SyntaxError: invalid syntax"), pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy