mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
Issue #20400: Merge Tulip into Python: add the new asyncio.subprocess module
* Add a new asyncio.subprocess module * Add new create_subprocess_exec() and create_subprocess_shell() functions * The new asyncio.subprocess.SubprocessStreamProtocol creates stream readers for stdout and stderr and a stream writer for stdin. * The new asyncio.subprocess.Process class offers an API close to the subprocess.Popen class: - pid, returncode, stdin, stdout and stderr attributes - communicate(), wait(), send_signal(), terminate() and kill() methods * Remove STDIN (0), STDOUT (1) and STDERR (2) constants from base_subprocess and unix_events, to not be confused with the symbols with the same name of subprocess and asyncio.subprocess modules * _ProactorBasePipeTransport.get_write_buffer_size() now counts also the size of the pending write * _ProactorBaseWritePipeTransport._loop_writing() may now pause the protocol if the write buffer size is greater than the high water mark (64 KB by default)
This commit is contained in:
parent
153d97b24e
commit
915bcb0111
9 changed files with 434 additions and 50 deletions
|
@ -116,18 +116,17 @@ class BaseEventLoopTests(unittest.TestCase):
|
|||
self.loop.stop()
|
||||
|
||||
self.loop._process_events = unittest.mock.Mock()
|
||||
delay = 0.1
|
||||
|
||||
when = self.loop.time() + delay
|
||||
when = self.loop.time() + 0.1
|
||||
self.loop.call_at(when, cb)
|
||||
t0 = self.loop.time()
|
||||
self.loop.run_forever()
|
||||
dt = self.loop.time() - t0
|
||||
|
||||
self.assertGreaterEqual(dt, delay - self.loop._granularity, dt)
|
||||
# tolerate a difference of +800 ms because some Python buildbots
|
||||
# are really slow
|
||||
self.assertLessEqual(dt, 0.9, dt)
|
||||
self.assertTrue(0.09 <= dt <= 0.9,
|
||||
# Issue #20452: add more info in case of failure,
|
||||
# to try to investigate the bug
|
||||
(dt,
|
||||
self.loop._granularity,
|
||||
time.get_clock_info('monotonic')))
|
||||
|
||||
def test_run_once_in_executor_handle(self):
|
||||
def cb():
|
||||
|
|
|
@ -1179,14 +1179,6 @@ class EventLoopTestsMixin:
|
|||
calls.append(self.loop._run_once_counter)
|
||||
self.assertEqual(calls, [1, 3, 5, 6])
|
||||
|
||||
def test_granularity(self):
|
||||
granularity = self.loop._granularity
|
||||
self.assertGreater(granularity, 0.0)
|
||||
# Worst expected granularity: 1 ms on Linux (limited by poll/epoll
|
||||
# resolution), 15.6 ms on Windows (limited by time.monotonic
|
||||
# resolution)
|
||||
self.assertLess(granularity, 0.050)
|
||||
|
||||
|
||||
class SubprocessTestsMixin:
|
||||
|
||||
|
|
196
Lib/test/test_asyncio/test_subprocess.py
Normal file
196
Lib/test/test_asyncio/test_subprocess.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
from asyncio import subprocess
|
||||
import asyncio
|
||||
import signal
|
||||
import sys
|
||||
import unittest
|
||||
from test import support
|
||||
if sys.platform != 'win32':
|
||||
from asyncio import unix_events
|
||||
|
||||
# Program exiting quickly
|
||||
PROGRAM_EXIT_FAST = [sys.executable, '-c', 'pass']
|
||||
|
||||
# Program blocking
|
||||
PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)']
|
||||
|
||||
# Program sleeping during 1 second
|
||||
PROGRAM_SLEEP_1SEC = [sys.executable, '-c', 'import time; time.sleep(1)']
|
||||
|
||||
# Program copying input to output
|
||||
PROGRAM_CAT = [
|
||||
sys.executable, '-c',
|
||||
';'.join(('import sys',
|
||||
'data = sys.stdin.buffer.read()',
|
||||
'sys.stdout.buffer.write(data)'))]
|
||||
|
||||
class SubprocessMixin:
|
||||
def test_stdin_stdout(self):
|
||||
args = PROGRAM_CAT
|
||||
|
||||
@asyncio.coroutine
|
||||
def run(data):
|
||||
proc = yield from asyncio.create_subprocess_exec(
|
||||
*args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
loop=self.loop)
|
||||
|
||||
# feed data
|
||||
proc.stdin.write(data)
|
||||
yield from proc.stdin.drain()
|
||||
proc.stdin.close()
|
||||
|
||||
# get output and exitcode
|
||||
data = yield from proc.stdout.read()
|
||||
exitcode = yield from proc.wait()
|
||||
return (exitcode, data)
|
||||
|
||||
task = run(b'some data')
|
||||
task = asyncio.wait_for(task, 10.0, loop=self.loop)
|
||||
exitcode, stdout = self.loop.run_until_complete(task)
|
||||
self.assertEqual(exitcode, 0)
|
||||
self.assertEqual(stdout, b'some data')
|
||||
|
||||
def test_communicate(self):
|
||||
args = PROGRAM_CAT
|
||||
|
||||
@asyncio.coroutine
|
||||
def run(data):
|
||||
proc = yield from asyncio.create_subprocess_exec(
|
||||
*args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
loop=self.loop)
|
||||
stdout, stderr = yield from proc.communicate(data)
|
||||
return proc.returncode, stdout
|
||||
|
||||
task = run(b'some data')
|
||||
task = asyncio.wait_for(task, 10.0, loop=self.loop)
|
||||
exitcode, stdout = self.loop.run_until_complete(task)
|
||||
self.assertEqual(exitcode, 0)
|
||||
self.assertEqual(stdout, b'some data')
|
||||
|
||||
def test_shell(self):
|
||||
create = asyncio.create_subprocess_shell('exit 7',
|
||||
loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
exitcode = self.loop.run_until_complete(proc.wait())
|
||||
self.assertEqual(exitcode, 7)
|
||||
|
||||
def test_start_new_session(self):
|
||||
# start the new process in a new session
|
||||
create = asyncio.create_subprocess_shell('exit 8',
|
||||
start_new_session=True,
|
||||
loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
exitcode = self.loop.run_until_complete(proc.wait())
|
||||
self.assertEqual(exitcode, 8)
|
||||
|
||||
def test_kill(self):
|
||||
args = PROGRAM_BLOCKED
|
||||
create = asyncio.create_subprocess_exec(*args, loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
proc.kill()
|
||||
returncode = self.loop.run_until_complete(proc.wait())
|
||||
if sys.platform == 'win32':
|
||||
self.assertIsInstance(returncode, int)
|
||||
# expect 1 but sometimes get 0
|
||||
else:
|
||||
self.assertEqual(-signal.SIGKILL, returncode)
|
||||
|
||||
def test_terminate(self):
|
||||
args = PROGRAM_BLOCKED
|
||||
create = asyncio.create_subprocess_exec(*args, loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
proc.terminate()
|
||||
returncode = self.loop.run_until_complete(proc.wait())
|
||||
if sys.platform == 'win32':
|
||||
self.assertIsInstance(returncode, int)
|
||||
# expect 1 but sometimes get 0
|
||||
else:
|
||||
self.assertEqual(-signal.SIGTERM, returncode)
|
||||
|
||||
@unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
|
||||
def test_send_signal(self):
|
||||
args = PROGRAM_BLOCKED
|
||||
create = asyncio.create_subprocess_exec(*args, loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
proc.send_signal(signal.SIGHUP)
|
||||
returncode = self.loop.run_until_complete(proc.wait())
|
||||
self.assertEqual(-signal.SIGHUP, returncode)
|
||||
|
||||
def test_get_subprocess(self):
|
||||
args = PROGRAM_EXIT_FAST
|
||||
|
||||
@asyncio.coroutine
|
||||
def run():
|
||||
proc = yield from asyncio.create_subprocess_exec(*args,
|
||||
loop=self.loop)
|
||||
yield from proc.wait()
|
||||
|
||||
popen = proc.get_subprocess()
|
||||
popen.wait()
|
||||
return (proc, popen)
|
||||
|
||||
proc, popen = self.loop.run_until_complete(run())
|
||||
self.assertEqual(popen.returncode, proc.returncode)
|
||||
self.assertEqual(popen.pid, proc.pid)
|
||||
|
||||
def test_broken_pipe(self):
|
||||
large_data = b'x' * support.PIPE_MAX_SIZE
|
||||
|
||||
create = asyncio.create_subprocess_exec(
|
||||
*PROGRAM_SLEEP_1SEC,
|
||||
stdin=subprocess.PIPE,
|
||||
loop=self.loop)
|
||||
proc = self.loop.run_until_complete(create)
|
||||
with self.assertRaises(BrokenPipeError):
|
||||
self.loop.run_until_complete(proc.communicate(large_data))
|
||||
self.loop.run_until_complete(proc.wait())
|
||||
|
||||
|
||||
if sys.platform != 'win32':
|
||||
# Unix
|
||||
class SubprocessWatcherMixin(SubprocessMixin):
|
||||
Watcher = None
|
||||
|
||||
def setUp(self):
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
self.loop = policy.new_event_loop()
|
||||
|
||||
# ensure that the event loop is passed explicitly in the code
|
||||
policy.set_event_loop(None)
|
||||
|
||||
watcher = self.Watcher()
|
||||
watcher.attach_loop(self.loop)
|
||||
policy.set_child_watcher(watcher)
|
||||
|
||||
def tearDown(self):
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
policy.set_child_watcher(None)
|
||||
self.loop.close()
|
||||
policy.set_event_loop(None)
|
||||
|
||||
class SubprocessSafeWatcherTests(SubprocessWatcherMixin, unittest.TestCase):
|
||||
Watcher = unix_events.SafeChildWatcher
|
||||
|
||||
class SubprocessFastWatcherTests(SubprocessWatcherMixin, unittest.TestCase):
|
||||
Watcher = unix_events.FastChildWatcher
|
||||
else:
|
||||
# Windows
|
||||
class SubprocessProactorTests(SubprocessMixin, unittest.TestCase):
|
||||
def setUp(self):
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
self.loop = asyncio.ProactorEventLoop()
|
||||
|
||||
# ensure that the event loop is passed explicitly in the code
|
||||
policy.set_event_loop(None)
|
||||
|
||||
def tearDown(self):
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
self.loop.close()
|
||||
policy.set_event_loop(None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -105,7 +105,7 @@ class ProactorTests(unittest.TestCase):
|
|||
self.loop.run_until_complete(f)
|
||||
elapsed = self.loop.time() - start
|
||||
self.assertFalse(f.result())
|
||||
self.assertTrue(0.18 < elapsed < 0.9, elapsed)
|
||||
self.assertTrue(0.18 < elapsed < 0.5, elapsed)
|
||||
|
||||
_overlapped.SetEvent(event)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue