[Python-checkins] cpython (3.4): Issue #24402: Fix input() when stdout.fileno() fails; diagnosed by Eryksun

martin.panter python-checkins at python.org
Fri Oct 9 22:10:34 EDT 2015


https://hg.python.org/cpython/rev/425d81d10b13
changeset:   98630:425d81d10b13
branch:      3.4
parent:      98627:7cc3a8141022
user:        Martin Panter <vadmium+py at gmail.com>
date:        Sat Oct 10 01:25:38 2015 +0000
summary:
  Issue #24402: Fix input() when stdout.fileno() fails; diagnosed by Eryksun

Also factored out some test cases into a new PtyTests class.

files:
  Lib/test/test_builtin.py |  186 +++++++++++++++-----------
  Misc/NEWS                |    3 +
  Python/bltinmodule.c     |    4 +-
  3 files changed, 116 insertions(+), 77 deletions(-)


diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -1134,82 +1134,6 @@
             sys.stdout = savestdout
             fp.close()
 
-    @unittest.skipUnless(pty, "the pty and signal modules must be available")
-    def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
-        if not sys.stdin.isatty() or not sys.stdout.isatty():
-            self.skipTest("stdin and stdout must be ttys")
-        r, w = os.pipe()
-        try:
-            pid, fd = pty.fork()
-        except (OSError, AttributeError) as e:
-            os.close(r)
-            os.close(w)
-            self.skipTest("pty.fork() raised {}".format(e))
-        if pid == 0:
-            # Child
-            try:
-                # Make sure we don't get stuck if there's a problem
-                signal.alarm(2)
-                os.close(r)
-                # Check the error handlers are accounted for
-                if stdio_encoding:
-                    sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
-                                                 encoding=stdio_encoding,
-                                                 errors='surrogateescape')
-                    sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
-                                                  encoding=stdio_encoding,
-                                                  errors='replace')
-                with open(w, "w") as wpipe:
-                    print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
-                    print(ascii(input(prompt)), file=wpipe)
-            except:
-                traceback.print_exc()
-            finally:
-                # We don't want to return to unittest...
-                os._exit(0)
-        # Parent
-        os.close(w)
-        os.write(fd, terminal_input + b"\r\n")
-        # Get results from the pipe
-        with open(r, "r") as rpipe:
-            lines = []
-            while True:
-                line = rpipe.readline().strip()
-                if line == "":
-                    # The other end was closed => the child exited
-                    break
-                lines.append(line)
-        # Check the result was got and corresponds to the user's terminal input
-        if len(lines) != 2:
-            # Something went wrong, try to get at stderr
-            with open(fd, "r", encoding="ascii", errors="ignore") as child_output:
-                self.fail("got %d lines in pipe but expected 2, child output was:\n%s"
-                          % (len(lines), child_output.read()))
-        os.close(fd)
-        # Check we did exercise the GNU readline path
-        self.assertIn(lines[0], {'tty = True', 'tty = False'})
-        if lines[0] != 'tty = True':
-            self.skipTest("standard IO in should have been a tty")
-        input_result = eval(lines[1])   # ascii() -> eval() roundtrip
-        if stdio_encoding:
-            expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
-        else:
-            expected = terminal_input.decode(sys.stdin.encoding)  # what else?
-        self.assertEqual(input_result, expected)
-
-    def test_input_tty(self):
-        # Test input() functionality when wired to a tty (the code path
-        # is different and invokes GNU readline if available).
-        self.check_input_tty("prompt", b"quux")
-
-    def test_input_tty_non_ascii(self):
-        # Check stdin/stdout encoding is used when invoking GNU readline
-        self.check_input_tty("prompté", b"quux\xe9", "utf-8")
-
-    def test_input_tty_non_ascii_unicode_errors(self):
-        # Check stdin/stdout error handler is used when invoking GNU readline
-        self.check_input_tty("prompté", b"quux\xe9", "ascii")
-
     # test_int(): see test_int.py for tests of built-in function int().
 
     def test_repr(self):
@@ -1564,6 +1488,116 @@
             self.assertRaises(TypeError, tp, 1, 2)
             self.assertRaises(TypeError, tp, a=1, b=2)
 
+ at unittest.skipUnless(pty, "the pty and signal modules must be available")
+class PtyTests(unittest.TestCase):
+    """Tests that use a pseudo terminal to guarantee stdin and stdout are
+    terminals in the test environment"""
+
+    def fork(self):
+        try:
+            return pty.fork()
+        except (OSError, AttributeError) as e:
+            self.skipTest("pty.fork() raised {}".format(e))
+
+    def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
+        if not sys.stdin.isatty() or not sys.stdout.isatty():
+            self.skipTest("stdin and stdout must be ttys")
+        r, w = os.pipe()
+        try:
+            pid, fd = self.fork()
+        except:
+            os.close(r)
+            os.close(w)
+            raise
+        if pid == 0:
+            # Child
+            try:
+                # Make sure we don't get stuck if there's a problem
+                signal.alarm(2)
+                os.close(r)
+                # Check the error handlers are accounted for
+                if stdio_encoding:
+                    sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
+                                                 encoding=stdio_encoding,
+                                                 errors='surrogateescape')
+                    sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
+                                                  encoding=stdio_encoding,
+                                                  errors='replace')
+                with open(w, "w") as wpipe:
+                    print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
+                    print(ascii(input(prompt)), file=wpipe)
+            except:
+                traceback.print_exc()
+            finally:
+                # We don't want to return to unittest...
+                os._exit(0)
+        # Parent
+        os.close(w)
+        os.write(fd, terminal_input + b"\r\n")
+        # Get results from the pipe
+        with open(r, "r") as rpipe:
+            lines = []
+            while True:
+                line = rpipe.readline().strip()
+                if line == "":
+                    # The other end was closed => the child exited
+                    break
+                lines.append(line)
+        # Check the result was got and corresponds to the user's terminal input
+        if len(lines) != 2:
+            # Something went wrong, try to get at stderr
+            with open(fd, "r", encoding="ascii", errors="ignore") as child_output:
+                self.fail("got %d lines in pipe but expected 2, child output was:\n%s"
+                          % (len(lines), child_output.read()))
+        os.close(fd)
+        # Check we did exercise the GNU readline path
+        self.assertIn(lines[0], {'tty = True', 'tty = False'})
+        if lines[0] != 'tty = True':
+            self.skipTest("standard IO in should have been a tty")
+        input_result = eval(lines[1])   # ascii() -> eval() roundtrip
+        if stdio_encoding:
+            expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
+        else:
+            expected = terminal_input.decode(sys.stdin.encoding)  # what else?
+        self.assertEqual(input_result, expected)
+
+    def test_input_tty(self):
+        # Test input() functionality when wired to a tty (the code path
+        # is different and invokes GNU readline if available).
+        self.check_input_tty("prompt", b"quux")
+
+    def test_input_tty_non_ascii(self):
+        # Check stdin/stdout encoding is used when invoking GNU readline
+        self.check_input_tty("prompté", b"quux\xe9", "utf-8")
+
+    def test_input_tty_non_ascii_unicode_errors(self):
+        # Check stdin/stdout error handler is used when invoking GNU readline
+        self.check_input_tty("prompté", b"quux\xe9", "ascii")
+
+    def test_input_no_stdout_fileno(self):
+        # Issue #24402: If stdin is the original terminal but stdout.fileno()
+        # fails, do not use the original stdout file descriptor
+        pid, pty = self.fork()
+        if pid:  # Parent process
+            # Ideally this should read and write concurrently using select()
+            # or similar, to avoid the possibility of a deadlock.
+            os.write(pty, b"quux\r")
+            _, status = os.waitpid(pid, 0)
+            output = os.read(pty, 3000).decode("ascii", "backslashreplace")
+            os.close(pty)
+            self.assertEqual(status, 0, output)
+        else:  # Child process
+            try:
+                self.assertTrue(sys.stdin.isatty(), "stdin not a terminal")
+                sys.stdout = io.StringIO()  # Does not support fileno()
+                input("prompt")
+                self.assertEqual(sys.stdout.getvalue(), "prompt")
+                os._exit(0)  # Success!
+            except:
+                sys.excepthook(*sys.exc_info())
+            finally:
+                os._exit(1)  # Failure
+
 class TestSorted(unittest.TestCase):
 
     def test_basic(self):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #24402: Fix input() to prompt to the redirected stdout when
+  sys.stdout.fileno() fails.
+
 - Issue #24806: Prevent builtin types that are not allowed to be subclassed from
   being subclassed through multiple inheritance.
 
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -1723,8 +1723,10 @@
     }
     if (tty) {
         tmp = _PyObject_CallMethodId(fout, &PyId_fileno, "");
-        if (tmp == NULL)
+        if (tmp == NULL) {
             PyErr_Clear();
+            tty = 0;
+        }
         else {
             fd = PyLong_AsLong(tmp);
             Py_DECREF(tmp);

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list