PageRenderTime 64ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/test/test_asyncio/test_subprocess.py

https://bitbucket.org/bluehorn/sampling_prof
Python | 491 lines | 419 code | 52 blank | 20 comment | 9 complexity | 59cf66e6c38c3c0befc5a62cd3a67277 MD5 | raw file
Possible License(s): BSD-3-Clause, Unlicense, CC-BY-SA-3.0, 0BSD
  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.is_closing())
  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_empty_input(self):
  239. @asyncio.coroutine
  240. def empty_input():
  241. code = 'import sys; data = sys.stdin.read(); print(len(data))'
  242. proc = yield from asyncio.create_subprocess_exec(
  243. sys.executable, '-c', code,
  244. stdin=asyncio.subprocess.PIPE,
  245. stdout=asyncio.subprocess.PIPE,
  246. stderr=asyncio.subprocess.PIPE,
  247. close_fds=False,
  248. loop=self.loop)
  249. stdout, stderr = yield from proc.communicate(b'')
  250. exitcode = yield from proc.wait()
  251. return (stdout, exitcode)
  252. output, exitcode = self.loop.run_until_complete(empty_input())
  253. self.assertEqual(output.rstrip(), b'0')
  254. self.assertEqual(exitcode, 0)
  255. def test_cancel_process_wait(self):
  256. # Issue #23140: cancel Process.wait()
  257. @asyncio.coroutine
  258. def cancel_wait():
  259. proc = yield from asyncio.create_subprocess_exec(
  260. *PROGRAM_BLOCKED,
  261. loop=self.loop)
  262. # Create an internal future waiting on the process exit
  263. task = self.loop.create_task(proc.wait())
  264. self.loop.call_soon(task.cancel)
  265. try:
  266. yield from task
  267. except asyncio.CancelledError:
  268. pass
  269. # Cancel the future
  270. task.cancel()
  271. # Kill the process and wait until it is done
  272. proc.kill()
  273. yield from proc.wait()
  274. self.loop.run_until_complete(cancel_wait())
  275. def test_cancel_make_subprocess_transport_exec(self):
  276. @asyncio.coroutine
  277. def cancel_make_transport():
  278. coro = asyncio.create_subprocess_exec(*PROGRAM_BLOCKED,
  279. loop=self.loop)
  280. task = self.loop.create_task(coro)
  281. self.loop.call_soon(task.cancel)
  282. try:
  283. yield from task
  284. except asyncio.CancelledError:
  285. pass
  286. # ignore the log:
  287. # "Exception during subprocess creation, kill the subprocess"
  288. with test_utils.disable_logger():
  289. self.loop.run_until_complete(cancel_make_transport())
  290. def test_cancel_post_init(self):
  291. @asyncio.coroutine
  292. def cancel_make_transport():
  293. coro = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  294. *PROGRAM_BLOCKED)
  295. task = self.loop.create_task(coro)
  296. self.loop.call_soon(task.cancel)
  297. try:
  298. yield from task
  299. except asyncio.CancelledError:
  300. pass
  301. # ignore the log:
  302. # "Exception during subprocess creation, kill the subprocess"
  303. with test_utils.disable_logger():
  304. self.loop.run_until_complete(cancel_make_transport())
  305. test_utils.run_briefly(self.loop)
  306. def test_close_kill_running(self):
  307. @asyncio.coroutine
  308. def kill_running():
  309. create = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  310. *PROGRAM_BLOCKED)
  311. transport, protocol = yield from create
  312. kill_called = False
  313. def kill():
  314. nonlocal kill_called
  315. kill_called = True
  316. orig_kill()
  317. proc = transport.get_extra_info('subprocess')
  318. orig_kill = proc.kill
  319. proc.kill = kill
  320. returncode = transport.get_returncode()
  321. transport.close()
  322. yield from transport._wait()
  323. return (returncode, kill_called)
  324. # Ignore "Close running child process: kill ..." log
  325. with test_utils.disable_logger():
  326. returncode, killed = self.loop.run_until_complete(kill_running())
  327. self.assertIsNone(returncode)
  328. # transport.close() must kill the process if it is still running
  329. self.assertTrue(killed)
  330. test_utils.run_briefly(self.loop)
  331. def test_close_dont_kill_finished(self):
  332. @asyncio.coroutine
  333. def kill_running():
  334. create = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
  335. *PROGRAM_BLOCKED)
  336. transport, protocol = yield from create
  337. proc = transport.get_extra_info('subprocess')
  338. # kill the process (but asyncio is not notified immediately)
  339. proc.kill()
  340. proc.wait()
  341. proc.kill = mock.Mock()
  342. proc_returncode = proc.poll()
  343. transport_returncode = transport.get_returncode()
  344. transport.close()
  345. return (proc_returncode, transport_returncode, proc.kill.called)
  346. # Ignore "Unknown child process pid ..." log of SafeChildWatcher,
  347. # emitted because the test already consumes the exit status:
  348. # proc.wait()
  349. with test_utils.disable_logger():
  350. result = self.loop.run_until_complete(kill_running())
  351. test_utils.run_briefly(self.loop)
  352. proc_returncode, transport_return_code, killed = result
  353. self.assertIsNotNone(proc_returncode)
  354. self.assertIsNone(transport_return_code)
  355. # transport.close() must not kill the process if it finished, even if
  356. # the transport was not notified yet
  357. self.assertFalse(killed)
  358. def test_popen_error(self):
  359. # Issue #24763: check that the subprocess transport is closed
  360. # when BaseSubprocessTransport fails
  361. if sys.platform == 'win32':
  362. target = 'asyncio.windows_utils.Popen'
  363. else:
  364. target = 'subprocess.Popen'
  365. with mock.patch(target) as popen:
  366. exc = ZeroDivisionError
  367. popen.side_effect = exc
  368. create = asyncio.create_subprocess_exec(sys.executable, '-c',
  369. 'pass', loop=self.loop)
  370. with warnings.catch_warnings(record=True) as warns:
  371. with self.assertRaises(exc):
  372. self.loop.run_until_complete(create)
  373. self.assertEqual(warns, [])
  374. if sys.platform != 'win32':
  375. # Unix
  376. class SubprocessWatcherMixin(SubprocessMixin):
  377. Watcher = None
  378. def setUp(self):
  379. policy = asyncio.get_event_loop_policy()
  380. self.loop = policy.new_event_loop()
  381. self.set_event_loop(self.loop)
  382. watcher = self.Watcher()
  383. watcher.attach_loop(self.loop)
  384. policy.set_child_watcher(watcher)
  385. self.addCleanup(policy.set_child_watcher, None)
  386. class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
  387. test_utils.TestCase):
  388. Watcher = unix_events.SafeChildWatcher
  389. class SubprocessFastWatcherTests(SubprocessWatcherMixin,
  390. test_utils.TestCase):
  391. Watcher = unix_events.FastChildWatcher
  392. else:
  393. # Windows
  394. class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
  395. def setUp(self):
  396. self.loop = asyncio.ProactorEventLoop()
  397. self.set_event_loop(self.loop)
  398. if __name__ == '__main__':
  399. unittest.main()