summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_subprocess.py
diff options
context:
space:
mode:
authorAmmar Askar <ammar_askar@hotmail.com>2017-09-06 06:41:30 (GMT)
committerGregory P. Smith <greg@krypto.org>2017-09-06 06:41:30 (GMT)
commit3fc499bca18454b9f432b9b0106cab662bfeb549 (patch)
tree294f00f9922bff52dad0fea08afd8d28db45a3f5 /Lib/test/test_subprocess.py
parent6877111648ac3e042ee5d0458cbeb65dd1a84b2d (diff)
downloadcpython-3fc499bca18454b9f432b9b0106cab662bfeb549.zip
cpython-3fc499bca18454b9f432b9b0106cab662bfeb549.tar.gz
cpython-3fc499bca18454b9f432b9b0106cab662bfeb549.tar.bz2
bpo-31178: Avoid concatenating bytes with str in subprocess error (#3066)
Avoid concatenating bytes with str in the typically rare subprocess error path (exec failed). Includes a mock based unittest to exercise the codepath.
Diffstat (limited to 'Lib/test/test_subprocess.py')
-rw-r--r--Lib/test/test_subprocess.py47
1 files changed, 47 insertions, 0 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index a0d3dcd..b7079b1 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1542,6 +1542,53 @@ class POSIXProcessTestCase(BaseTestCase):
else:
self.fail("Expected OSError: %s" % desired_exception)
+ # We mock the __del__ method for Popen in the next two tests
+ # because it does cleanup based on the pid returned by fork_exec
+ # along with issuing a resource warning if it still exists. Since
+ # we don't actually spawn a process in these tests we can forego
+ # the destructor. An alternative would be to set _child_created to
+ # False before the destructor is called but there is no easy way
+ # to do that
+ class PopenNoDestructor(subprocess.Popen):
+ def __del__(self):
+ pass
+
+ @mock.patch("subprocess._posixsubprocess.fork_exec")
+ def test_exception_errpipe_normal(self, fork_exec):
+ """Test error passing done through errpipe_write in the good case"""
+ def proper_error(*args):
+ errpipe_write = args[13]
+ # Write the hex for the error code EISDIR: 'is a directory'
+ err_code = '{:x}'.format(errno.EISDIR).encode()
+ os.write(errpipe_write, b"OSError:" + err_code + b":")
+ return 0
+
+ fork_exec.side_effect = proper_error
+
+ with self.assertRaises(IsADirectoryError):
+ self.PopenNoDestructor(["non_existent_command"])
+
+ @mock.patch("subprocess._posixsubprocess.fork_exec")
+ def test_exception_errpipe_bad_data(self, fork_exec):
+ """Test error passing done through errpipe_write where its not
+ in the expected format"""
+ error_data = b"\xFF\x00\xDE\xAD"
+ def bad_error(*args):
+ errpipe_write = args[13]
+ # Anything can be in the pipe, no assumptions should
+ # be made about its encoding, so we'll write some
+ # arbitrary hex bytes to test it out
+ os.write(errpipe_write, error_data)
+ return 0
+
+ fork_exec.side_effect = bad_error
+
+ with self.assertRaises(subprocess.SubprocessError) as e:
+ self.PopenNoDestructor(["non_existent_command"])
+
+ self.assertIn(repr(error_data), str(e.exception))
+
+
def test_restore_signals(self):
# Code coverage for both values of restore_signals to make sure it
# at least does not blow up.