PageRenderTime 70ms CodeModel.GetById 36ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/test/test_asyncio/test_subprocess.py

https://bitbucket.org/TJG/cpython
Python | 453 lines | 327 code | 88 blank | 38 comment | 22 complexity | 3e316a8024dbca057d6686152a5d237a MD5 | raw file
Possible License(s): Unlicense, CC-BY-SA-3.0, 0BSD
  1. import signal
  2. import sys
  3. import unittest
  4. from unittest import mock
  5. import asyncio
  6. from asyncio import base_subprocess
  7. from asyncio import subprocess
  8. from asyncio import test_utils
  9. try:
  10. from test import support
  11. except ImportError:
  12. from asyncio import test_support as support
  13. if sys.platform != 'win32':
  14. from asyncio import unix_events
  15. # Program blocking
  16. PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)']
  17. # Program copying input to output
  18. PROGRAM_CAT = [
  19. sys.executable, '-c',
  20. ';'.join(('import sys',
  21. 'data = sys.stdin.buffer.read()',
  22. 'sys.stdout.buffer.write(data)'))]
  23. class TestSubprocessTransport(base_subprocess.BaseSubprocessTransport):
  24. def _start(self, *args, **kwargs):
  25. self._proc = mock.Mock()
  26. self._proc.stdin = None
  27. self._proc.stdout = None
  28. self._proc.stderr = None
  29. class SubprocessTransportTests(test_utils.TestCase):
  30. def setUp(self):
  31. self.loop = self.new_test_loop()
  32. self.set_event_loop(self.loop)
  33. def create_transport(self, waiter=None):
  34. protocol = mock.Mock()
  35. protocol.connection_made._is_coroutine = False
  36. protocol.process_exited._is_coroutine = False
  37. transport = TestSubprocessTransport(
  38. self.loop, protocol, ['test'], False,
  39. None, None, None, 0, waiter=waiter)
  40. return (transport, protocol)
  41. def test_proc_exited(self):
  42. waiter = asyncio.Future(loop=self.loop)
  43. transport, protocol = self.create_transport(waiter)
  44. transport._process_exited(6)
  45. self.loop.run_until_complete(waiter)
  46. self.assertEqual(transport.get_returncode(), 6)
  47. self.assertTrue(protocol.connection_made.called)
  48. self.assertTrue(protocol.process_exited.called)
  49. self.assertTrue(protocol.connection_lost.called)
  50. self.assertEqual(protocol.connection_lost.call_args[0], (None,))
  51. self.assertFalse(transport._closed)
  52. self.assertIsNone(transport._loop)
  53. self.assertIsNone(transport._proc)
  54. self.assertIsNone(transport._protocol)
  55. # methods must raise ProcessLookupError if the process exited
  56. self.assertRaises(ProcessLookupError,
  57. transport.send_signal, signal.SIGTERM)
  58. self.assertRaises(ProcessLookupError, transport.terminate)
  59. self.assertRaises(ProcessLookupError, transport.kill)
  60. transport.close()
  61. class SubprocessMixin:
  62. def test_stdin_stdout(self):
  63. args = PROGRAM_CAT
  64. @asyncio.coroutine
  65. def run(data):
  66. proc = yield from asyncio.create_subprocess_exec(
  67. *args,
  68. stdin=subprocess.PIPE,
  69. stdout=subprocess.PIPE,
  70. loop=self.loop)
  71. # feed data
  72. proc.stdin.write(data)
  73. yield from proc.stdin.drain()
  74. proc.stdin.close()
  75. # get output and exitcode
  76. data = yield from proc.stdout.read()
  77. exitcode = yield from proc.wait()
  78. return (exitcode, data)
  79. task = run(b'some data')
  80. task = asyncio.wait_for(task, 60.0, loop=self.loop)
  81. exitcode, stdout = self.loop.run_until_complete(task)
  82. self.assertEqual(exitcode, 0)
  83. self.assertEqual(stdout, b'some data')
  84. def test_communicate(self):
  85. args = PROGRAM_CAT
  86. @asyncio.coroutine
  87. def run(data):
  88. proc = yield from asyncio.create_subprocess_exec(
  89. *args,
  90. stdin=subprocess.PIPE,
  91. stdout=subprocess.PIPE,
  92. loop=self.loop)
  93. stdout, stderr = yield from proc.communicate(data)
  94. return proc.returncode, stdout
  95. task = run(b'some data')
  96. task = asyncio.wait_for(task, 60.0, loop=self.loop)
  97. exitcode, stdout = self.loop.run_until_complete(task)
  98. self.assertEqual(exitcode, 0)
  99. self.assertEqual(stdout, b'some data')
  100. def test_shell(self):
  101. create = asyncio.create_subprocess_shell('exit 7',
  102. loop=self.loop)
  103. proc = self.loop.run_until_complete(create)
  104. exitcode = self.loop.run_until_complete(proc.wait())
  105. self.assertEqual(exitcode, 7)
  106. def test_start_new_session(self):
  107. # start the new process in a new session
  108. create = asyncio.create_subprocess_shell('exit 8',
  109. start_new_session=True,
  110. loop=self.loop)
  111. proc = self.loop.run_until_complete(create)
  112. exitcode = self.loop.run_until_complete(proc.wait())
  113. self.assertEqual(exitcode, 8)
  114. def test_kill(self):
  115. args = PROGRAM_BLOCKED
  116. create = asyncio.create_subprocess_exec(*args, loop=self.loop)
  117. proc = self.loop.run_until_complete(create)
  118. proc.kill()
  119. returncode = self.loop.run_until_complete(proc.wait())
  120. if sys.platform == 'win32':
  121. self.assertIsInstance(returncode, int)
  122. # expect 1 but sometimes get 0
  123. else:
  124. self.assertEqual(-signal.SIGKILL, returncode)
  125. def test_terminate(self):
  126. args = PROGRAM_BLOCKED
  127. create = asyncio.create_subprocess_exec(*args, loop=self.loop)
  128. proc = self.loop.run_until_complete(create)
  129. proc.terminate()
  130. returncode = self.loop.run_until_complete(proc.wait())
  131. if sys.platform == 'win32':
  132. self.assertIsInstance(returncode, int)
  133. # expect 1 but sometimes get 0
  134. else:
  135. self.assertEqual(-signal.SIGTERM, returncode)
  136. @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
  137. def test_send_signal(self):
  138. code = 'import time; print("sleeping", flush=True); time.sleep(3600)'
  139. args = [sys.executable, '-c', code]
  140. create = asyncio.create_subprocess_exec(*args,
  141. stdout=subprocess.PIPE,
  142. loop=self.loop)
  143. proc = self.loop.run_until_complete(create)
  144. @asyncio.coroutine
  145. def send_signal(proc):
  146. # basic synchronization to wait until the program is sleeping
  147. line = yield from proc.stdout.readline()
  148. self.assertEqual(line, b'sleeping\n')
  149. proc.send_signal(signal.SIGHUP)
  150. returncode = (yield from proc.wait())
  151. return returncode
  152. returncode = self.loop.run_until_complete(send_signal(proc))
  153. self.assertEqual(-signal.SIGHUP, returncode)
  154. def prepare_broken_pipe_test(self):
  155. # buffer large enough to feed the whole pipe buffer
  156. large_data = b'x' * support.PIPE_MAX_SIZE
  157. # the program ends before the stdin can be feeded
  158. create = asyncio.create_subprocess_exec(
  159. sys.executable, '-c', 'pass',
  160. stdin=subprocess.PIPE,
  161. loop=self.loop)
  162. proc = self.loop.run_until_complete(create)
  163. return (proc, large_data)
  164. def test_stdin_broken_pipe(self):
  165. proc, large_data = self.prepare_broken_pipe_test()
  166. @asyncio.coroutine
  167. def write_stdin(proc, data):
  168. proc.stdin.write(data)
  169. yield from proc.stdin.drain()
  170. coro = write_stdin(proc, large_data)
  171. # drain() must raise BrokenPipeError or ConnectionResetError
  172. with test_utils.disable_logger():
  173. self.assertRaises((BrokenPipeError, ConnectionResetError),
  174. self.loop.run_until_complete, coro)
  175. self.loop.run_until_complete(proc.wait())
  176. def test_communicate_ignore_broken_pipe(self):
  177. proc, large_data = self.prepare_broken_pipe_test()
  178. # communicate() must ignore BrokenPipeError when feeding stdin
  179. with test_utils.disable_logger():
  180. self.loop.run_until_complete(proc.communicate(large_data))
  181. self.loop.run_until_complete(proc.wait())
  182. def test_pause_reading(self):
  183. limit = 10
  184. size = (limit * 2 + 1)
  185. @asyncio.coroutine
  186. def test_pause_reading():
  187. code = '\n'.join((
  188. 'import sys',
  189. 'sys.stdout.write("x" * %s)' % size,
  190. 'sys.stdout.flush()',
  191. ))
  192. connect_read_pipe = self.loop.connect_read_pipe
  193. @asyncio.coroutine
  194. def connect_read_pipe_mock(*args, **kw):
  195. transport, protocol = yield from connect_read_pipe(*args, **kw)
  196. transport.pause_reading = mock.Mock()
  197. transport.resume_reading = mock.Mock()
  198. return (transport, protocol)
  199. self.loop.connect_read_pipe = connect_read_pipe_mock
  200. proc = yield from asyncio.create_subprocess_exec(
  201. sys.executable, '-c', code,
  202. stdin=asyncio.subprocess.PIPE,
  203. stdout=asyncio.subprocess.PIPE,
  204. limit=limit,
  205. loop=self.loop)
  206. stdout_transport = proc._transport.get_pipe_transport(1)
  207. stdout, stderr = yield from proc.communicate()
  208. # The child process produced more than limit bytes of output,
  209. # the stream reader transport should pause the protocol to not
  210. # allocate too much memory.
  211. return (stdout, stdout_transport)
  212. # Issue #22685: Ensure that the stream reader pauses the protocol
  213. # when the child process produces too much data
  214. stdout, transport = self.loop.run_until_complete(test_pause_reading())
  215. self.assertEqual(stdout, b'x' * size)
  216. self.assertTrue(transport.pause_reading.called)
  217. self.assertTrue(transport.resume_reading.called)
  218. def test_stdin_not_inheritable(self):
  219. # Tulip issue #209: stdin must not be inheritable, otherwise
  220. # the Process.communicate() hangs
  221. @asyncio.coroutine
  222. def len_message(message):
  223. code = 'import sys; data = sys.stdin.read(); print(len(data))'
  224. proc = yield from asyncio.create_subprocess_exec(
  225. sys.executable, '-c', code,
  226. stdin=asyncio.subprocess.PIPE,
  227. stdout=asyncio.subprocess.PIPE,
  228. stderr=asyncio.subprocess.PIPE,
  229. close_fds=False,
  230. loop=self.loop)
  231. stdout, stderr = yield from proc.communicate(message)
  232. exitcode = yield from proc.wait()
  233. return (stdout, exitcode)
  234. output, exitcode = self.loop.run_until_complete(len_message(b'abc'))
  235. self.assertEqual(output.rstrip(), b'3')
  236. self.assertEqual(exitcode, 0)
  237. def test_cancel_process_wait(self):
  238. # Issue #23140: cancel Process.wait()
  239. @asyncio.coroutine
  240. def cancel_wait():
  241. proc = yield from asyncio.create_subprocess_exec(
  242. *PROGRAM_BLOCKED,
  243. loop=self.loop)
  244. # Create an internal future waiting on the process exit
  245. task = self.loop.create_task(proc.wait())
  246. self.loop.call_soon(task.cancel)
  247. try:
  248. yield from task
  249. except asyncio.CancelledError:
  250. pass
  251. # Cancel the future
  252. task.cancel()
  253. # Kill the process and wait until it is done
  254. proc.kill()
  255. yield from proc.wait()
  256. self.loop.run_until_complete(cancel_wait())
  257. def test_cancel_make_subprocess_transport_exec(self):
  258. @asyncio.coroutine
  259. def cancel_make_transport():
  260. coro = asyncio.create_subprocess_exec(*PROGRAM_BLOCKED,
  261. loop=self.loop)
  262. task = self.loop.create_task(coro)
  263. self.loop.call_soon(task.cancel)
  264. try:
  265. yield from task
  266. except asyncio.CancelledError:
  267. pass
  268. # ignore the log:
  269. # "Exception during subprocess creation, kill the subprocess"
  270. with test_utils.disable_logger():
  271. self.loop.run_until_complete(cancel_make_transport())
  272. def test_cancel_post_init(self):
  273. @asyncio.coroutine
  274. def cancel_make_transport():
  275. coro = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  276. *PROGRAM_BLOCKED)
  277. task = self.loop.create_task(coro)
  278. self.loop.call_soon(task.cancel)
  279. try:
  280. yield from task
  281. except asyncio.CancelledError:
  282. pass
  283. # ignore the log:
  284. # "Exception during subprocess creation, kill the subprocess"
  285. with test_utils.disable_logger():
  286. self.loop.run_until_complete(cancel_make_transport())
  287. test_utils.run_briefly(self.loop)
  288. def test_close_kill_running(self):
  289. @asyncio.coroutine
  290. def kill_running():
  291. create = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  292. *PROGRAM_BLOCKED)
  293. transport, protocol = yield from create
  294. kill_called = False
  295. def kill():
  296. nonlocal kill_called
  297. kill_called = True
  298. orig_kill()
  299. proc = transport.get_extra_info('subprocess')
  300. orig_kill = proc.kill
  301. proc.kill = kill
  302. returncode = transport.get_returncode()
  303. transport.close()
  304. yield from transport._wait()
  305. return (returncode, kill_called)
  306. # Ignore "Close running child process: kill ..." log
  307. with test_utils.disable_logger():
  308. returncode, killed = self.loop.run_until_complete(kill_running())
  309. self.assertIsNone(returncode)
  310. # transport.close() must kill the process if it is still running
  311. self.assertTrue(killed)
  312. test_utils.run_briefly(self.loop)
  313. def test_close_dont_kill_finished(self):
  314. @asyncio.coroutine
  315. def kill_running():
  316. create = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  317. *PROGRAM_BLOCKED)
  318. transport, protocol = yield from create
  319. proc = transport.get_extra_info('subprocess')
  320. # kill the process (but asyncio is not notified immediatly)
  321. proc.kill()
  322. proc.wait()
  323. proc.kill = mock.Mock()
  324. proc_returncode = proc.poll()
  325. transport_returncode = transport.get_returncode()
  326. transport.close()
  327. return (proc_returncode, transport_returncode, proc.kill.called)
  328. # Ignore "Unknown child process pid ..." log of SafeChildWatcher,
  329. # emitted because the test already consumes the exit status:
  330. # proc.wait()
  331. with test_utils.disable_logger():
  332. result = self.loop.run_until_complete(kill_running())
  333. test_utils.run_briefly(self.loop)
  334. proc_returncode, transport_return_code, killed = result
  335. self.assertIsNotNone(proc_returncode)
  336. self.assertIsNone(transport_return_code)
  337. # transport.close() must not kill the process if it finished, even if
  338. # the transport was not notified yet
  339. self.assertFalse(killed)
  340. if sys.platform != 'win32':
  341. # Unix
  342. class SubprocessWatcherMixin(SubprocessMixin):
  343. Watcher = None
  344. def setUp(self):
  345. policy = asyncio.get_event_loop_policy()
  346. self.loop = policy.new_event_loop()
  347. self.set_event_loop(self.loop)
  348. watcher = self.Watcher()
  349. watcher.attach_loop(self.loop)
  350. policy.set_child_watcher(watcher)
  351. self.addCleanup(policy.set_child_watcher, None)
  352. class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
  353. test_utils.TestCase):
  354. Watcher = unix_events.SafeChildWatcher
  355. class SubprocessFastWatcherTests(SubprocessWatcherMixin,
  356. test_utils.TestCase):
  357. Watcher = unix_events.FastChildWatcher
  358. else:
  359. # Windows
  360. class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
  361. def setUp(self):
  362. self.loop = asyncio.ProactorEventLoop()
  363. self.set_event_loop(self.loop)
  364. if __name__ == '__main__':
  365. unittest.main()