Commit 750e0bc4 authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #883 from gevent/subprocess_run2

Implement subprocess.run()
parents 143b2d5f f1399ac5
...@@ -161,6 +161,17 @@ Other Changes ...@@ -161,6 +161,17 @@ Other Changes
considered deprecated after it is constructed. considered deprecated after it is constructed.
- The :func:`gevent.os.waitpid` function is cooperative in more - The :func:`gevent.os.waitpid` function is cooperative in more
circumstances. Reported in :issue:`878` by Heungsub Lee. circumstances. Reported in :issue:`878` by Heungsub Lee.
- The :mod:`gevent.subprocess` module now provides the
:func:`gevent.subprocess.run` function in a cooperative way even
when the system is not monkey patched, on all supported versions of
Python. (It was added officially in Python 3.5.)
- Popen objects can be used as context managers even on Python 2.
- Popen objects save their *args* attribute even on Python 2.
- :exc:`gevent.subprocess.TimeoutExpired` is defined even on Python 2,
where it is a subclass of the :exc:`gevent.timeout.Timeout`
exception; all instances where a ``Timeout`` exception would
previously be thrown under Python 2 will now throw a
``TimeoutExpired`` exception.
1.1.2 (Jul 21, 2016) 1.1.2 (Jul 21, 2016)
==================== ====================
......
This diff is collapsed.
...@@ -124,11 +124,11 @@ class Timeout(BaseException): ...@@ -124,11 +124,11 @@ class Timeout(BaseException):
Add warning about negative *seconds* values. Add warning about negative *seconds* values.
""" """
def __init__(self, seconds=None, exception=None, ref=True, priority=-1): def __init__(self, seconds=None, exception=None, ref=True, priority=-1, _use_timer=True):
BaseException.__init__(self) BaseException.__init__(self)
self.seconds = seconds self.seconds = seconds
self.exception = exception self.exception = exception
if seconds is None: if seconds is None or not _use_timer:
# Avoid going through the timer codepath if no timeout is # Avoid going through the timer codepath if no timeout is
# desired; this avoids some CFFI interactions on PyPy that can lead to a # desired; this avoids some CFFI interactions on PyPy that can lead to a
# RuntimeError if this implementation is used during an `import` statement. See # RuntimeError if this implementation is used during an `import` statement. See
......
...@@ -506,7 +506,7 @@ class TestBasic(greentest.TestCase): ...@@ -506,7 +506,7 @@ class TestBasic(greentest.TestCase):
return return_value return return_value
g = gevent.Greenlet(func, 0.01, return_value=5) g = gevent.Greenlet(func, 0.01, return_value=5)
g.rawlink(link_test.append) # use rawlink to avoid timing issues on Appveyor (not always successful) g.rawlink(link_test.append) # use rawlink to avoid timing issues on Appveyor/Travis (not always successful)
assert not g, bool(g) assert not g, bool(g)
assert not g.dead assert not g.dead
assert not g.started assert not g.started
...@@ -542,7 +542,7 @@ class TestBasic(greentest.TestCase): ...@@ -542,7 +542,7 @@ class TestBasic(greentest.TestCase):
assert g.successful() assert g.successful()
assert g.value == 5 assert g.value == 5
assert g.exception is None # not changed assert g.exception is None # not changed
assert link_test == [g] or greentest.RUNNING_ON_APPVEYOR, link_test # changed assert link_test == [g] or greentest.RUNNING_ON_CI, link_test # changed
def test_error_exit(self): def test_error_exit(self):
link_test = [] link_test = []
......
...@@ -6,6 +6,7 @@ import gevent ...@@ -6,6 +6,7 @@ import gevent
from gevent import subprocess from gevent import subprocess
import time import time
import gc import gc
import tempfile
PYPY = hasattr(sys, 'pypy_version_info') PYPY = hasattr(sys, 'pypy_version_info')
...@@ -229,5 +230,104 @@ class Test(greentest.TestCase): ...@@ -229,5 +230,104 @@ class Test(greentest.TestCase):
test_subprocess_in_native_thread.ignore_leakcheck = True test_subprocess_in_native_thread.ignore_leakcheck = True
class RunFuncTestCase(greentest.TestCase):
# Based on code from python 3.6
__timeout__ = 6
def run_python(self, code, **kwargs):
"""Run Python code in a subprocess using subprocess.run"""
argv = [sys.executable, "-c", code]
return subprocess.run(argv, **kwargs)
def test_returncode(self):
# call() function with sequence argument
cp = self.run_python("import sys; sys.exit(47)")
self.assertEqual(cp.returncode, 47)
with self.assertRaises(subprocess.CalledProcessError):
cp.check_returncode()
def test_check(self):
with self.assertRaises(subprocess.CalledProcessError) as c:
self.run_python("import sys; sys.exit(47)", check=True)
self.assertEqual(c.exception.returncode, 47)
def test_check_zero(self):
# check_returncode shouldn't raise when returncode is zero
cp = self.run_python("import sys; sys.exit(0)", check=True)
self.assertEqual(cp.returncode, 0)
def test_timeout(self):
# run() function with timeout argument; we want to test that the child
# process gets killed when the timeout expires. If the child isn't
# killed, this call will deadlock since subprocess.run waits for the
# child.
with self.assertRaises(subprocess.TimeoutExpired):
self.run_python("while True: pass", timeout=0.0001)
def test_capture_stdout(self):
# capture stdout with zero return code
cp = self.run_python("print('BDFL')", stdout=subprocess.PIPE)
self.assertIn(b'BDFL', cp.stdout)
def test_capture_stderr(self):
cp = self.run_python("import sys; sys.stderr.write('BDFL')",
stderr=subprocess.PIPE)
self.assertIn(b'BDFL', cp.stderr)
def test_check_output_stdin_arg(self):
# run() can be called with stdin set to a file
with tempfile.TemporaryFile() as tf:
tf.write(b'pear')
tf.seek(0)
cp = self.run_python(
"import sys; sys.stdout.write(sys.stdin.read().upper())",
stdin=tf, stdout=subprocess.PIPE)
self.assertIn(b'PEAR', cp.stdout)
def test_check_output_input_arg(self):
# check_output() can be called with input set to a string
cp = self.run_python(
"import sys; sys.stdout.write(sys.stdin.read().upper())",
input=b'pear', stdout=subprocess.PIPE)
self.assertIn(b'PEAR', cp.stdout)
def test_check_output_stdin_with_input_arg(self):
# run() refuses to accept 'stdin' with 'input'
with tempfile.TemporaryFile() as tf:
tf.write(b'pear')
tf.seek(0)
with self.assertRaises(ValueError,
msg="Expected ValueError when stdin and input args supplied.") as c:
self.run_python("print('will not be run')",
stdin=tf, input=b'hare')
self.assertIn('stdin', c.exception.args[0])
self.assertIn('input', c.exception.args[0])
def test_check_output_timeout(self):
with self.assertRaises(subprocess.TimeoutExpired) as c:
self.run_python(
(
"import sys, time\n"
"sys.stdout.write('BDFL')\n"
"sys.stdout.flush()\n"
"time.sleep(3600)"
),
# Some heavily loaded buildbots (sparc Debian 3.x) require
# this much time to start and print.
timeout=3, stdout=subprocess.PIPE)
self.assertEqual(c.exception.output, b'BDFL')
# output is aliased to stdout
self.assertEqual(c.exception.stdout, b'BDFL')
def test_run_kwargs(self):
newenv = os.environ.copy()
newenv["FRUIT"] = "banana"
cp = self.run_python(('import sys, os;'
'sys.exit(33 if os.getenv("FRUIT")=="banana" else 31)'),
env=newenv)
self.assertEqual(cp.returncode, 33)
if __name__ == '__main__': if __name__ == '__main__':
greentest.main() greentest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment