Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5344da5

Browse files
committed
Issue #24402: Merge input() fix from 3.5
2 parents 748cf9b + e02f8fc commit 5344da5

3 files changed

Lines changed: 119 additions & 77 deletions

File tree

Lib/test/test_builtin.py

Lines changed: 110 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,82 +1134,6 @@ def test_input(self):
11341134
sys.stdout = savestdout
11351135
fp.close()
11361136

1137-
@unittest.skipUnless(pty, "the pty and signal modules must be available")
1138-
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
1139-
if not sys.stdin.isatty() or not sys.stdout.isatty():
1140-
self.skipTest("stdin and stdout must be ttys")
1141-
r, w = os.pipe()
1142-
try:
1143-
pid, fd = pty.fork()
1144-
except (OSError, AttributeError) as e:
1145-
os.close(r)
1146-
os.close(w)
1147-
self.skipTest("pty.fork() raised {}".format(e))
1148-
if pid == 0:
1149-
# Child
1150-
try:
1151-
# Make sure we don't get stuck if there's a problem
1152-
signal.alarm(2)
1153-
os.close(r)
1154-
# Check the error handlers are accounted for
1155-
if stdio_encoding:
1156-
sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
1157-
encoding=stdio_encoding,
1158-
errors='surrogateescape')
1159-
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
1160-
encoding=stdio_encoding,
1161-
errors='replace')
1162-
with open(w, "w") as wpipe:
1163-
print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
1164-
print(ascii(input(prompt)), file=wpipe)
1165-
except:
1166-
traceback.print_exc()
1167-
finally:
1168-
# We don't want to return to unittest...
1169-
os._exit(0)
1170-
# Parent
1171-
os.close(w)
1172-
os.write(fd, terminal_input + b"\r\n")
1173-
# Get results from the pipe
1174-
with open(r, "r") as rpipe:
1175-
lines = []
1176-
while True:
1177-
line = rpipe.readline().strip()
1178-
if line == "":
1179-
# The other end was closed => the child exited
1180-
break
1181-
lines.append(line)
1182-
# Check the result was got and corresponds to the user's terminal input
1183-
if len(lines) != 2:
1184-
# Something went wrong, try to get at stderr
1185-
with open(fd, "r", encoding="ascii", errors="ignore") as child_output:
1186-
self.fail("got %d lines in pipe but expected 2, child output was:\n%s"
1187-
% (len(lines), child_output.read()))
1188-
os.close(fd)
1189-
# Check we did exercise the GNU readline path
1190-
self.assertIn(lines[0], {'tty = True', 'tty = False'})
1191-
if lines[0] != 'tty = True':
1192-
self.skipTest("standard IO in should have been a tty")
1193-
input_result = eval(lines[1]) # ascii() -> eval() roundtrip
1194-
if stdio_encoding:
1195-
expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
1196-
else:
1197-
expected = terminal_input.decode(sys.stdin.encoding) # what else?
1198-
self.assertEqual(input_result, expected)
1199-
1200-
def test_input_tty(self):
1201-
# Test input() functionality when wired to a tty (the code path
1202-
# is different and invokes GNU readline if available).
1203-
self.check_input_tty("prompt", b"quux")
1204-
1205-
def test_input_tty_non_ascii(self):
1206-
# Check stdin/stdout encoding is used when invoking GNU readline
1207-
self.check_input_tty("prompté", b"quux\xe9", "utf-8")
1208-
1209-
def test_input_tty_non_ascii_unicode_errors(self):
1210-
# Check stdin/stdout error handler is used when invoking GNU readline
1211-
self.check_input_tty("prompté", b"quux\xe9", "ascii")
1212-
12131137
# test_int(): see test_int.py for tests of built-in function int().
12141138

12151139
def test_repr(self):
@@ -1564,6 +1488,116 @@ def test_construct_singletons(self):
15641488
self.assertRaises(TypeError, tp, 1, 2)
15651489
self.assertRaises(TypeError, tp, a=1, b=2)
15661490

1491+
@unittest.skipUnless(pty, "the pty and signal modules must be available")
1492+
class PtyTests(unittest.TestCase):
1493+
"""Tests that use a pseudo terminal to guarantee stdin and stdout are
1494+
terminals in the test environment"""
1495+
1496+
def fork(self):
1497+
try:
1498+
return pty.fork()
1499+
except (OSError, AttributeError) as e:
1500+
self.skipTest("pty.fork() raised {}".format(e))
1501+
1502+
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
1503+
if not sys.stdin.isatty() or not sys.stdout.isatty():
1504+
self.skipTest("stdin and stdout must be ttys")
1505+
r, w = os.pipe()
1506+
try:
1507+
pid, fd = self.fork()
1508+
except:
1509+
os.close(r)
1510+
os.close(w)
1511+
raise
1512+
if pid == 0:
1513+
# Child
1514+
try:
1515+
# Make sure we don't get stuck if there's a problem
1516+
signal.alarm(2)
1517+
os.close(r)
1518+
# Check the error handlers are accounted for
1519+
if stdio_encoding:
1520+
sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
1521+
encoding=stdio_encoding,
1522+
errors='surrogateescape')
1523+
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
1524+
encoding=stdio_encoding,
1525+
errors='replace')
1526+
with open(w, "w") as wpipe:
1527+
print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
1528+
print(ascii(input(prompt)), file=wpipe)
1529+
except:
1530+
traceback.print_exc()
1531+
finally:
1532+
# We don't want to return to unittest...
1533+
os._exit(0)
1534+
# Parent
1535+
os.close(w)
1536+
os.write(fd, terminal_input + b"\r\n")
1537+
# Get results from the pipe
1538+
with open(r, "r") as rpipe:
1539+
lines = []
1540+
while True:
1541+
line = rpipe.readline().strip()
1542+
if line == "":
1543+
# The other end was closed => the child exited
1544+
break
1545+
lines.append(line)
1546+
# Check the result was got and corresponds to the user's terminal input
1547+
if len(lines) != 2:
1548+
# Something went wrong, try to get at stderr
1549+
with open(fd, "r", encoding="ascii", errors="ignore") as child_output:
1550+
self.fail("got %d lines in pipe but expected 2, child output was:\n%s"
1551+
% (len(lines), child_output.read()))
1552+
os.close(fd)
1553+
# Check we did exercise the GNU readline path
1554+
self.assertIn(lines[0], {'tty = True', 'tty = False'})
1555+
if lines[0] != 'tty = True':
1556+
self.skipTest("standard IO in should have been a tty")
1557+
input_result = eval(lines[1]) # ascii() -> eval() roundtrip
1558+
if stdio_encoding:
1559+
expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
1560+
else:
1561+
expected = terminal_input.decode(sys.stdin.encoding) # what else?
1562+
self.assertEqual(input_result, expected)
1563+
1564+
def test_input_tty(self):
1565+
# Test input() functionality when wired to a tty (the code path
1566+
# is different and invokes GNU readline if available).
1567+
self.check_input_tty("prompt", b"quux")
1568+
1569+
def test_input_tty_non_ascii(self):
1570+
# Check stdin/stdout encoding is used when invoking GNU readline
1571+
self.check_input_tty("prompté", b"quux\xe9", "utf-8")
1572+
1573+
def test_input_tty_non_ascii_unicode_errors(self):
1574+
# Check stdin/stdout error handler is used when invoking GNU readline
1575+
self.check_input_tty("prompté", b"quux\xe9", "ascii")
1576+
1577+
def test_input_no_stdout_fileno(self):
1578+
# Issue #24402: If stdin is the original terminal but stdout.fileno()
1579+
# fails, do not use the original stdout file descriptor
1580+
pid, pty = self.fork()
1581+
if pid: # Parent process
1582+
# Ideally this should read and write concurrently using select()
1583+
# or similar, to avoid the possibility of a deadlock.
1584+
os.write(pty, b"quux\r")
1585+
_, status = os.waitpid(pid, 0)
1586+
output = os.read(pty, 3000).decode("ascii", "backslashreplace")
1587+
os.close(pty)
1588+
self.assertEqual(status, 0, output)
1589+
else: # Child process
1590+
try:
1591+
self.assertTrue(sys.stdin.isatty(), "stdin not a terminal")
1592+
sys.stdout = io.StringIO() # Does not support fileno()
1593+
input("prompt")
1594+
self.assertEqual(sys.stdout.getvalue(), "prompt")
1595+
os._exit(0) # Success!
1596+
except:
1597+
sys.excepthook(*sys.exc_info())
1598+
finally:
1599+
os._exit(1) # Failure
1600+
15671601
class TestSorted(unittest.TestCase):
15681602

15691603
def test_basic(self):

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Release date: XXXX-XX-XX
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #24402: Fix input() to prompt to the redirected stdout when
14+
sys.stdout.fileno() fails.
15+
1316
- Issue #25349: Optimize bytes % args using the new private _PyBytesWriter API.
1417

1518
- Issue #24806: Prevent builtin types that are not allowed to be subclassed from
@@ -260,6 +263,9 @@ Release date: TBA
260263
Core and Builtins
261264
-----------------
262265

266+
- Issue #24402: Fix input() to prompt to the redirected stdout when
267+
sys.stdout.fileno() fails.
268+
263269
- Issue #25182: The stdprinter (used as sys.stderr before the io module is
264270
imported at startup) now uses the backslashreplace error handler.
265271

Python/bltinmodule.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1854,8 +1854,10 @@ builtin_input_impl(PyModuleDef *module, PyObject *prompt)
18541854
}
18551855
if (tty) {
18561856
tmp = _PyObject_CallMethodId(fout, &PyId_fileno, "");
1857-
if (tmp == NULL)
1857+
if (tmp == NULL) {
18581858
PyErr_Clear();
1859+
tty = 0;
1860+
}
18591861
else {
18601862
fd = PyLong_AsLong(tmp);
18611863
Py_DECREF(tmp);

0 commit comments

Comments
 (0)