gh-128231: Use runcode() return value for failing early (GH-129488)

This commit is contained in:
Bartosz Sławecki 2025-02-24 15:50:13 +01:00 committed by GitHub
parent 9f25c1f012
commit 7ed3dc6392
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 40 additions and 3 deletions

View file

@ -152,6 +152,8 @@ class Console(ABC):
class InteractiveColoredConsole(code.InteractiveConsole): class InteractiveColoredConsole(code.InteractiveConsole):
STATEMENT_FAILED = object()
def __init__( def __init__(
self, self,
locals: dict[str, object] | None = None, locals: dict[str, object] | None = None,
@ -173,6 +175,16 @@ class InteractiveColoredConsole(code.InteractiveConsole):
limit=traceback.BUILTIN_EXCEPTION_LIMIT) limit=traceback.BUILTIN_EXCEPTION_LIMIT)
self.write(''.join(lines)) self.write(''.join(lines))
def runcode(self, code):
try:
exec(code, self.locals)
except SystemExit:
raise
except BaseException:
self.showtraceback()
return self.STATEMENT_FAILED
return None
def runsource(self, source, filename="<input>", symbol="single"): def runsource(self, source, filename="<input>", symbol="single"):
try: try:
tree = self.compile.compiler( tree = self.compile.compiler(
@ -209,5 +221,7 @@ class InteractiveColoredConsole(code.InteractiveConsole):
if code is None: if code is None:
return True return True
self.runcode(code) result = self.runcode(code)
if result is self.STATEMENT_FAILED:
break
return False return False

View file

@ -75,7 +75,7 @@ class AsyncIOInteractiveConsole(InteractiveColoredConsole):
self.write("\nKeyboardInterrupt\n") self.write("\nKeyboardInterrupt\n")
else: else:
self.showtraceback() self.showtraceback()
return self.STATEMENT_FAILED
class REPLThread(threading.Thread): class REPLThread(threading.Thread):

View file

@ -53,6 +53,19 @@ class TestSimpleInteract(unittest.TestCase):
self.assertFalse(more) self.assertFalse(more)
self.assertEqual(f.getvalue(), "1\n") self.assertEqual(f.getvalue(), "1\n")
@force_not_colorized
def test_multiple_statements_fail_early(self):
console = InteractiveColoredConsole()
code = dedent("""\
raise Exception('foobar')
print('spam&eggs')
""")
f = io.StringIO()
with contextlib.redirect_stderr(f):
console.runsource(code)
self.assertIn('Exception: foobar', f.getvalue())
self.assertNotIn('spam&eggs', f.getvalue())
def test_empty(self): def test_empty(self):
namespace = {} namespace = {}
code = "" code = ""

View file

@ -294,7 +294,15 @@ class TestInteractiveModeSyntaxErrors(unittest.TestCase):
self.assertEqual(traceback_lines, expected_lines) self.assertEqual(traceback_lines, expected_lines)
class TestAsyncioREPLContextVars(unittest.TestCase): class TestAsyncioREPL(unittest.TestCase):
def test_multiple_statements_fail_early(self):
user_input = "1 / 0; print('afterwards')"
p = spawn_repl("-m", "asyncio")
p.stdin.write(user_input)
output = kill_python(p)
self.assertIn("ZeroDivisionError", output)
self.assertNotIn("afterwards", output)
def test_toplevel_contextvars_sync(self): def test_toplevel_contextvars_sync(self):
user_input = dedent("""\ user_input = dedent("""\
from contextvars import ContextVar from contextvars import ContextVar

View file

@ -0,0 +1,2 @@
Execution of multiple statements in the new REPL now stops immediately upon
the first exception encountered. Patch by Bartosz Sławecki.