diff options
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/subprocessdata/fd_status.py | 24 | ||||
-rw-r--r-- | Lib/test/subprocessdata/input_reader.py | 7 | ||||
-rw-r--r-- | Lib/test/subprocessdata/qcat.py | 7 | ||||
-rw-r--r-- | Lib/test/subprocessdata/qgrep.py | 10 | ||||
-rw-r--r-- | Lib/test/test_subprocess.py | 78 |
5 files changed, 126 insertions, 0 deletions
diff --git a/Lib/test/subprocessdata/fd_status.py b/Lib/test/subprocessdata/fd_status.py new file mode 100644 index 0000000..083b2f9 --- /dev/null +++ b/Lib/test/subprocessdata/fd_status.py @@ -0,0 +1,24 @@ +"""When called as a script, print a comma-separated list of the open +file descriptors on stdout.""" + +import errno +import os +import fcntl + +try: + _MAXFD = os.sysconf("SC_OPEN_MAX") +except: + _MAXFD = 256 + +def isopen(fd): + """Return True if the fd is open, and False otherwise""" + try: + fcntl.fcntl(fd, fcntl.F_GETFD, 0) + except IOError as e: + if e.errno == errno.EBADF: + return False + raise + return True + +if __name__ == "__main__": + print(','.join(str(fd) for fd in range(0, _MAXFD) if isopen(fd))) diff --git a/Lib/test/subprocessdata/input_reader.py b/Lib/test/subprocessdata/input_reader.py new file mode 100644 index 0000000..ccae5f3 --- /dev/null +++ b/Lib/test/subprocessdata/input_reader.py @@ -0,0 +1,7 @@ +"""When called as a script, consumes the input""" + +import sys + +if __name__ = "__main__": + for line in sys.stdin: + pass diff --git a/Lib/test/subprocessdata/qcat.py b/Lib/test/subprocessdata/qcat.py new file mode 100644 index 0000000..fe6f9db --- /dev/null +++ b/Lib/test/subprocessdata/qcat.py @@ -0,0 +1,7 @@ +"""When ran as a script, simulates cat with no arguments.""" + +import sys + +if __name__ == "__main__": + for line in sys.stdin: + sys.stdout.write(line) diff --git a/Lib/test/subprocessdata/qgrep.py b/Lib/test/subprocessdata/qgrep.py new file mode 100644 index 0000000..6990637 --- /dev/null +++ b/Lib/test/subprocessdata/qgrep.py @@ -0,0 +1,10 @@ +"""When called with a single argument, simulated fgrep with a single +argument and no options.""" + +import sys + +if __name__ == "__main__": + pattern = sys.argv[1] + for line in sys.stdin: + if pattern in line: + sys.stdout.write(line) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index eaa26d2..74e7404 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -10,6 +10,7 @@ import time import re import sysconfig import warnings +import select try: import gc except ImportError: @@ -964,6 +965,83 @@ class POSIXProcessTestCase(BaseTestCase): exitcode = subprocess.call([program, "-c", "pass"], env=envb) self.assertEqual(exitcode, 0) + def test_pipe_cloexec(self): + sleeper = support.findfile("input_reader.py", subdir="subprocessdata") + fd_status = support.findfile("fd_status.py", subdir="subprocessdata") + + p1 = subprocess.Popen([sys.executable, sleeper], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=False) + + self.addCleanup(p1.communicate, b'') + + p2 = subprocess.Popen([sys.executable, fd_status], + stdout=subprocess.PIPE, close_fds=False) + + output, error = p2.communicate() + result_fds = set(map(int, output.split(b','))) + unwanted_fds = set([p1.stdin.fileno(), p1.stdout.fileno(), + p1.stderr.fileno()]) + + self.assertFalse(result_fds & unwanted_fds, + "Expected no fds from %r to be open in child, " + "found %r" % + (unwanted_fds, result_fds & unwanted_fds)) + + def test_pipe_cloexec_real_tools(self): + qcat = support.findfile("qcat.py", subdir="subprocessdata") + qgrep = support.findfile("qgrep.py", subdir="subprocessdata") + + subdata = b'zxcvbn' + data = subdata * 4 + b'\n' + + p1 = subprocess.Popen([sys.executable, qcat], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + close_fds=False) + + p2 = subprocess.Popen([sys.executable, qgrep, subdata], + stdin=p1.stdout, stdout=subprocess.PIPE, + close_fds=False) + + self.addCleanup(p1.wait) + self.addCleanup(p2.wait) + self.addCleanup(p1.terminate) + self.addCleanup(p2.terminate) + + p1.stdin.write(data) + p1.stdin.close() + + readfiles, ignored1, ignored2 = select.select([p2.stdout], [], [], 10) + + self.assertTrue(readfiles, "The child hung") + self.assertEqual(p2.stdout.read(), data) + + def test_close_fds(self): + fd_status = support.findfile("fd_status.py", subdir="subprocessdata") + + fds = os.pipe() + self.addCleanup(os.close, fds[0]) + self.addCleanup(os.close, fds[1]) + + open_fds = set(fds) + + p = subprocess.Popen([sys.executable, fd_status], + stdout=subprocess.PIPE, close_fds=False) + output, ignored = p.communicate() + remaining_fds = set(map(int, output.split(b','))) + + self.assertEqual(remaining_fds & open_fds, open_fds, + "Some fds were closed") + + p = subprocess.Popen([sys.executable, fd_status], + stdout=subprocess.PIPE, close_fds=True) + output, ignored = p.communicate() + remaining_fds = set(map(int, output.split(b','))) + + self.assertFalse(remaining_fds & open_fds, + "Some fds were left open") + self.assertIn(1, remaining_fds, "Subprocess failed") + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): |