PageRenderTime 44ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/test/test_asyncio/test_subprocess.py

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