Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,7 @@ private int readEvalPrint(Context context, ConsoleHandler consoleHandler, Value
while (true) { // processing subsequent lines while input is incomplete
try {
context.eval(Source.newBuilder(getLanguageId(), sb.toString(), "<stdin>").interactive(true).buildLiteral());
flushInteractiveOutput(sysModule);
} catch (PolyglotException e) {
if (ps2 == null) {
ps2 = doEcho ? sysModule.getMember("ps2").asString() : null;
Expand Down Expand Up @@ -1261,6 +1262,7 @@ private int readEvalPrint(Context context, ConsoleHandler consoleHandler, Value
continue;
}
}
flushInteractiveOutput(sysModule);
// process the exception from eval or from the last parsing of the input
// + additional source
if (e.isExit()) {
Expand Down Expand Up @@ -1302,6 +1304,22 @@ private int readEvalPrint(Context context, ConsoleHandler consoleHandler, Value
}
}

private static void flushInteractiveOutput(Value sysModule) {
flushInteractiveStream(sysModule, "stderr");
flushInteractiveStream(sysModule, "stdout");
}

private static void flushInteractiveStream(Value sysModule, String name) {
try {
Value stream = sysModule.getMember(name);
if (stream != null && !stream.isNull() && stream.canInvokeMember("flush")) {
stream.invokeMember("flush");
}
} catch (PolyglotException | UnsupportedOperationException e) {
// Match CPython's interactive flush_io: stream flush failures are ignored.
}
}

private static boolean canSkipFromEval(String input) {
String[] split = input.split("\n");
for (String s : split) {
Expand Down
25 changes: 20 additions & 5 deletions graalpython/com.oracle.graal.python.test/src/tests/test_repl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -47,13 +47,15 @@
import unittest
from tests import util
from dataclasses import dataclass
from functools import wraps
from textwrap import dedent

if (sys.platform != 'win32' and (sys.platform != 'linux' or platform.machine() != 'aarch64')) and (
sys.implementation.name != 'graalpy' or __graalpython__.posix_module_backend() != 'java'):

# The terminal tests can be flaky
def autoretry(fn):
@wraps(fn)
def decorated(*args, **kwargs):
retries = 3
while retries:
Expand All @@ -77,7 +79,7 @@ class ExpectedInOutItem:


@autoretry
def validate_repl(stdin, python_args=(), ignore_preamble=True):
def validate_repl(stdin, python_args=(), ignore_preamble=True, extra_input_and_output=()):
env = os.environ.copy()
env['TERM'] = 'ansi'
env['PYTHONIOENCODING'] = 'utf-8'
Expand All @@ -103,6 +105,7 @@ def validate_repl(stdin, python_args=(), ignore_preamble=True):
expected_input = match.group(2)
expected_output = stdin[match.end():in_matches[i + 1].start() - 1 if i + 1 < len(in_matches) else -1]
input_and_output.append(ExpectedInOutItem(prompt, expected_input, expected_output))
input_and_output.extend(extra_input_and_output)
index = -1
whole_out = ''
while True:
Expand All @@ -111,9 +114,12 @@ def validate_repl(stdin, python_args=(), ignore_preamble=True):
out += os.read(pty_parent, 1024).decode('utf-8')
out = re.sub(r'\x1b\[(?:\?2004[hl]|\d+[A-G])', '', out)
out = re.sub(r'\r+\n', '\n', out)
if out == '>>> ' or out.endswith(('\n>>> ', '\n... ')):
if out.endswith(('\n... ', '>>> ')):
prompt = out[:3]
actual = out[:-5]
actual_end = len(out) - 4
if actual_end > 0 and out[actual_end - 1] == '\n':
actual_end -= 1
actual = out[:actual_end]
if index >= 0:
current = input_and_output[index]
assert prompt == current.prompt, f"Actual prompt: {prompt}\nExpected prompt: {current.prompt}"
Expand All @@ -126,7 +132,7 @@ def validate_repl(stdin, python_args=(), ignore_preamble=True):
whole_out += out[:-4]
out = out[-4:]
if index >= len(input_and_output):
os.write(pty_parent, b'\x04') # CTRL-D
os.write(pty_parent, b'\x04') # Ctrl-D / EOF
proc.wait(timeout=60)
out = os.read(pty_parent, 1024).decode('utf-8')
out = re.sub(r'\x1b\[\?2004[hl]', '', out)
Expand Down Expand Up @@ -165,6 +171,15 @@ def test_basic_repl_no_readline():
"""), python_args=['-I'])


@autoretry
def test_repl_flushes_std_streams():
validate_repl("", extra_input_and_output=[
ExpectedInOutItem('>>>', '40 + 2', '\n42'),
ExpectedInOutItem('>>>', '_ = __import__("sys").stderr.write("stderr")', '\nstderr'),
ExpectedInOutItem('>>>', '_ = __import__("sys").stdout.write("stdout")', '\nstdout'),
])


def test_continuation():
validate_repl(dedent(r'''\
>>> def foo():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -1006,9 +1006,9 @@ public Void visit(TypeVarTuple node) {
return null;
}

/*-
// Validation of sequences
*/
/*-
// Validation of sequences
*/

// Equivalent of validate_stmts
private void validateStmts(StmtTy[] stmts) {
Expand Down
Loading