1import os 2import signal 3import sys 4import unittest 5import warnings 6from unittest import mock 7 8import asyncio 9from asyncio import base_subprocess 10from asyncio import subprocess 11from test.test_asyncio import utils as test_utils 12from test import support 13 14if sys.platform != 'win32': 15 from asyncio import unix_events 16 17# Program blocking 18PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)'] 19 20# Program copying input to output 21PROGRAM_CAT = [ 22 sys.executable, '-c', 23 ';'.join(('import sys', 24 'data = sys.stdin.buffer.read()', 25 'sys.stdout.buffer.write(data)'))] 26 27 28def tearDownModule(): 29 asyncio.set_event_loop_policy(None) 30 31 32class TestSubprocessTransport(base_subprocess.BaseSubprocessTransport): 33 def _start(self, *args, **kwargs): 34 self._proc = mock.Mock() 35 self._proc.stdin = None 36 self._proc.stdout = None 37 self._proc.stderr = None 38 self._proc.pid = -1 39 40 41class SubprocessTransportTests(test_utils.TestCase): 42 def setUp(self): 43 super().setUp() 44 self.loop = self.new_test_loop() 45 self.set_event_loop(self.loop) 46 47 def create_transport(self, waiter=None): 48 protocol = mock.Mock() 49 protocol.connection_made._is_coroutine = False 50 protocol.process_exited._is_coroutine = False 51 transport = TestSubprocessTransport( 52 self.loop, protocol, ['test'], False, 53 None, None, None, 0, waiter=waiter) 54 return (transport, protocol) 55 56 def test_proc_exited(self): 57 waiter = self.loop.create_future() 58 transport, protocol = self.create_transport(waiter) 59 transport._process_exited(6) 60 self.loop.run_until_complete(waiter) 61 62 self.assertEqual(transport.get_returncode(), 6) 63 64 self.assertTrue(protocol.connection_made.called) 65 self.assertTrue(protocol.process_exited.called) 66 self.assertTrue(protocol.connection_lost.called) 67 self.assertEqual(protocol.connection_lost.call_args[0], (None,)) 68 69 self.assertFalse(transport.is_closing()) 70 self.assertIsNone(transport._loop) 71 self.assertIsNone(transport._proc) 72 self.assertIsNone(transport._protocol) 73 74 # methods must raise ProcessLookupError if the process exited 75 self.assertRaises(ProcessLookupError, 76 transport.send_signal, signal.SIGTERM) 77 self.assertRaises(ProcessLookupError, transport.terminate) 78 self.assertRaises(ProcessLookupError, transport.kill) 79 80 transport.close() 81 82 def test_subprocess_repr(self): 83 waiter = self.loop.create_future() 84 transport, protocol = self.create_transport(waiter) 85 transport._process_exited(6) 86 self.loop.run_until_complete(waiter) 87 88 self.assertEqual( 89 repr(transport), 90 "<TestSubprocessTransport pid=-1 returncode=6>" 91 ) 92 transport._returncode = None 93 self.assertEqual( 94 repr(transport), 95 "<TestSubprocessTransport pid=-1 running>" 96 ) 97 transport._pid = None 98 transport._returncode = None 99 self.assertEqual( 100 repr(transport), 101 "<TestSubprocessTransport not started>" 102 ) 103 transport.close() 104 105 106class SubprocessMixin: 107 108 def test_stdin_stdout(self): 109 args = PROGRAM_CAT 110 111 async def run(data): 112 proc = await asyncio.create_subprocess_exec( 113 *args, 114 stdin=subprocess.PIPE, 115 stdout=subprocess.PIPE, 116 ) 117 118 # feed data 119 proc.stdin.write(data) 120 await proc.stdin.drain() 121 proc.stdin.close() 122 123 # get output and exitcode 124 data = await proc.stdout.read() 125 exitcode = await proc.wait() 126 return (exitcode, data) 127 128 task = run(b'some data') 129 task = asyncio.wait_for(task, 60.0) 130 exitcode, stdout = self.loop.run_until_complete(task) 131 self.assertEqual(exitcode, 0) 132 self.assertEqual(stdout, b'some data') 133 134 def test_communicate(self): 135 args = PROGRAM_CAT 136 137 async def run(data): 138 proc = await asyncio.create_subprocess_exec( 139 *args, 140 stdin=subprocess.PIPE, 141 stdout=subprocess.PIPE, 142 ) 143 stdout, stderr = await proc.communicate(data) 144 return proc.returncode, stdout 145 146 task = run(b'some data') 147 task = asyncio.wait_for(task, support.LONG_TIMEOUT) 148 exitcode, stdout = self.loop.run_until_complete(task) 149 self.assertEqual(exitcode, 0) 150 self.assertEqual(stdout, b'some data') 151 152 def test_shell(self): 153 proc = self.loop.run_until_complete( 154 asyncio.create_subprocess_shell('exit 7') 155 ) 156 exitcode = self.loop.run_until_complete(proc.wait()) 157 self.assertEqual(exitcode, 7) 158 159 def test_start_new_session(self): 160 # start the new process in a new session 161 proc = self.loop.run_until_complete( 162 asyncio.create_subprocess_shell( 163 'exit 8', 164 start_new_session=True, 165 ) 166 ) 167 exitcode = self.loop.run_until_complete(proc.wait()) 168 self.assertEqual(exitcode, 8) 169 170 def test_kill(self): 171 args = PROGRAM_BLOCKED 172 proc = self.loop.run_until_complete( 173 asyncio.create_subprocess_exec(*args) 174 ) 175 proc.kill() 176 returncode = self.loop.run_until_complete(proc.wait()) 177 if sys.platform == 'win32': 178 self.assertIsInstance(returncode, int) 179 # expect 1 but sometimes get 0 180 else: 181 self.assertEqual(-signal.SIGKILL, returncode) 182 183 def test_terminate(self): 184 args = PROGRAM_BLOCKED 185 proc = self.loop.run_until_complete( 186 asyncio.create_subprocess_exec(*args) 187 ) 188 proc.terminate() 189 returncode = self.loop.run_until_complete(proc.wait()) 190 if sys.platform == 'win32': 191 self.assertIsInstance(returncode, int) 192 # expect 1 but sometimes get 0 193 else: 194 self.assertEqual(-signal.SIGTERM, returncode) 195 196 @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") 197 def test_send_signal(self): 198 # bpo-31034: Make sure that we get the default signal handler (killing 199 # the process). The parent process may have decided to ignore SIGHUP, 200 # and signal handlers are inherited. 201 old_handler = signal.signal(signal.SIGHUP, signal.SIG_DFL) 202 try: 203 code = 'import time; print("sleeping", flush=True); time.sleep(3600)' 204 args = [sys.executable, '-c', code] 205 proc = self.loop.run_until_complete( 206 asyncio.create_subprocess_exec( 207 *args, 208 stdout=subprocess.PIPE, 209 ) 210 ) 211 212 async def send_signal(proc): 213 # basic synchronization to wait until the program is sleeping 214 line = await proc.stdout.readline() 215 self.assertEqual(line, b'sleeping\n') 216 217 proc.send_signal(signal.SIGHUP) 218 returncode = await proc.wait() 219 return returncode 220 221 returncode = self.loop.run_until_complete(send_signal(proc)) 222 self.assertEqual(-signal.SIGHUP, returncode) 223 finally: 224 signal.signal(signal.SIGHUP, old_handler) 225 226 def prepare_broken_pipe_test(self): 227 # buffer large enough to feed the whole pipe buffer 228 large_data = b'x' * support.PIPE_MAX_SIZE 229 230 # the program ends before the stdin can be feeded 231 proc = self.loop.run_until_complete( 232 asyncio.create_subprocess_exec( 233 sys.executable, '-c', 'pass', 234 stdin=subprocess.PIPE, 235 ) 236 ) 237 238 return (proc, large_data) 239 240 def test_stdin_broken_pipe(self): 241 proc, large_data = self.prepare_broken_pipe_test() 242 243 async def write_stdin(proc, data): 244 await asyncio.sleep(0.5) 245 proc.stdin.write(data) 246 await proc.stdin.drain() 247 248 coro = write_stdin(proc, large_data) 249 # drain() must raise BrokenPipeError or ConnectionResetError 250 with test_utils.disable_logger(): 251 self.assertRaises((BrokenPipeError, ConnectionResetError), 252 self.loop.run_until_complete, coro) 253 self.loop.run_until_complete(proc.wait()) 254 255 def test_communicate_ignore_broken_pipe(self): 256 proc, large_data = self.prepare_broken_pipe_test() 257 258 # communicate() must ignore BrokenPipeError when feeding stdin 259 self.loop.set_exception_handler(lambda loop, msg: None) 260 self.loop.run_until_complete(proc.communicate(large_data)) 261 self.loop.run_until_complete(proc.wait()) 262 263 def test_pause_reading(self): 264 limit = 10 265 size = (limit * 2 + 1) 266 267 async def test_pause_reading(): 268 code = '\n'.join(( 269 'import sys', 270 'sys.stdout.write("x" * %s)' % size, 271 'sys.stdout.flush()', 272 )) 273 274 connect_read_pipe = self.loop.connect_read_pipe 275 276 async def connect_read_pipe_mock(*args, **kw): 277 transport, protocol = await connect_read_pipe(*args, **kw) 278 transport.pause_reading = mock.Mock() 279 transport.resume_reading = mock.Mock() 280 return (transport, protocol) 281 282 self.loop.connect_read_pipe = connect_read_pipe_mock 283 284 proc = await asyncio.create_subprocess_exec( 285 sys.executable, '-c', code, 286 stdin=asyncio.subprocess.PIPE, 287 stdout=asyncio.subprocess.PIPE, 288 limit=limit, 289 ) 290 stdout_transport = proc._transport.get_pipe_transport(1) 291 292 stdout, stderr = await proc.communicate() 293 294 # The child process produced more than limit bytes of output, 295 # the stream reader transport should pause the protocol to not 296 # allocate too much memory. 297 return (stdout, stdout_transport) 298 299 # Issue #22685: Ensure that the stream reader pauses the protocol 300 # when the child process produces too much data 301 stdout, transport = self.loop.run_until_complete(test_pause_reading()) 302 303 self.assertEqual(stdout, b'x' * size) 304 self.assertTrue(transport.pause_reading.called) 305 self.assertTrue(transport.resume_reading.called) 306 307 def test_stdin_not_inheritable(self): 308 # asyncio issue #209: stdin must not be inheritable, otherwise 309 # the Process.communicate() hangs 310 async def len_message(message): 311 code = 'import sys; data = sys.stdin.read(); print(len(data))' 312 proc = await asyncio.create_subprocess_exec( 313 sys.executable, '-c', code, 314 stdin=asyncio.subprocess.PIPE, 315 stdout=asyncio.subprocess.PIPE, 316 stderr=asyncio.subprocess.PIPE, 317 close_fds=False, 318 ) 319 stdout, stderr = await proc.communicate(message) 320 exitcode = await proc.wait() 321 return (stdout, exitcode) 322 323 output, exitcode = self.loop.run_until_complete(len_message(b'abc')) 324 self.assertEqual(output.rstrip(), b'3') 325 self.assertEqual(exitcode, 0) 326 327 def test_empty_input(self): 328 329 async def empty_input(): 330 code = 'import sys; data = sys.stdin.read(); print(len(data))' 331 proc = await asyncio.create_subprocess_exec( 332 sys.executable, '-c', code, 333 stdin=asyncio.subprocess.PIPE, 334 stdout=asyncio.subprocess.PIPE, 335 stderr=asyncio.subprocess.PIPE, 336 close_fds=False, 337 ) 338 stdout, stderr = await proc.communicate(b'') 339 exitcode = await proc.wait() 340 return (stdout, exitcode) 341 342 output, exitcode = self.loop.run_until_complete(empty_input()) 343 self.assertEqual(output.rstrip(), b'0') 344 self.assertEqual(exitcode, 0) 345 346 def test_devnull_input(self): 347 348 async def empty_input(): 349 code = 'import sys; data = sys.stdin.read(); print(len(data))' 350 proc = await asyncio.create_subprocess_exec( 351 sys.executable, '-c', code, 352 stdin=asyncio.subprocess.DEVNULL, 353 stdout=asyncio.subprocess.PIPE, 354 stderr=asyncio.subprocess.PIPE, 355 close_fds=False, 356 ) 357 stdout, stderr = await proc.communicate() 358 exitcode = await proc.wait() 359 return (stdout, exitcode) 360 361 output, exitcode = self.loop.run_until_complete(empty_input()) 362 self.assertEqual(output.rstrip(), b'0') 363 self.assertEqual(exitcode, 0) 364 365 def test_devnull_output(self): 366 367 async def empty_output(): 368 code = 'import sys; data = sys.stdin.read(); print(len(data))' 369 proc = await asyncio.create_subprocess_exec( 370 sys.executable, '-c', code, 371 stdin=asyncio.subprocess.PIPE, 372 stdout=asyncio.subprocess.DEVNULL, 373 stderr=asyncio.subprocess.PIPE, 374 close_fds=False, 375 ) 376 stdout, stderr = await proc.communicate(b"abc") 377 exitcode = await proc.wait() 378 return (stdout, exitcode) 379 380 output, exitcode = self.loop.run_until_complete(empty_output()) 381 self.assertEqual(output, None) 382 self.assertEqual(exitcode, 0) 383 384 def test_devnull_error(self): 385 386 async def empty_error(): 387 code = 'import sys; data = sys.stdin.read(); print(len(data))' 388 proc = await asyncio.create_subprocess_exec( 389 sys.executable, '-c', code, 390 stdin=asyncio.subprocess.PIPE, 391 stdout=asyncio.subprocess.PIPE, 392 stderr=asyncio.subprocess.DEVNULL, 393 close_fds=False, 394 ) 395 stdout, stderr = await proc.communicate(b"abc") 396 exitcode = await proc.wait() 397 return (stderr, exitcode) 398 399 output, exitcode = self.loop.run_until_complete(empty_error()) 400 self.assertEqual(output, None) 401 self.assertEqual(exitcode, 0) 402 403 def test_cancel_process_wait(self): 404 # Issue #23140: cancel Process.wait() 405 406 async def cancel_wait(): 407 proc = await asyncio.create_subprocess_exec(*PROGRAM_BLOCKED) 408 409 # Create an internal future waiting on the process exit 410 task = self.loop.create_task(proc.wait()) 411 self.loop.call_soon(task.cancel) 412 try: 413 await task 414 except asyncio.CancelledError: 415 pass 416 417 # Cancel the future 418 task.cancel() 419 420 # Kill the process and wait until it is done 421 proc.kill() 422 await proc.wait() 423 424 self.loop.run_until_complete(cancel_wait()) 425 426 def test_cancel_make_subprocess_transport_exec(self): 427 428 async def cancel_make_transport(): 429 coro = asyncio.create_subprocess_exec(*PROGRAM_BLOCKED) 430 task = self.loop.create_task(coro) 431 432 self.loop.call_soon(task.cancel) 433 try: 434 await task 435 except asyncio.CancelledError: 436 pass 437 438 # ignore the log: 439 # "Exception during subprocess creation, kill the subprocess" 440 with test_utils.disable_logger(): 441 self.loop.run_until_complete(cancel_make_transport()) 442 443 def test_cancel_post_init(self): 444 445 async def cancel_make_transport(): 446 coro = self.loop.subprocess_exec(asyncio.SubprocessProtocol, 447 *PROGRAM_BLOCKED) 448 task = self.loop.create_task(coro) 449 450 self.loop.call_soon(task.cancel) 451 try: 452 await task 453 except asyncio.CancelledError: 454 pass 455 456 # ignore the log: 457 # "Exception during subprocess creation, kill the subprocess" 458 with test_utils.disable_logger(): 459 self.loop.run_until_complete(cancel_make_transport()) 460 test_utils.run_briefly(self.loop) 461 462 def test_close_kill_running(self): 463 464 async def kill_running(): 465 create = self.loop.subprocess_exec(asyncio.SubprocessProtocol, 466 *PROGRAM_BLOCKED) 467 transport, protocol = await create 468 469 kill_called = False 470 def kill(): 471 nonlocal kill_called 472 kill_called = True 473 orig_kill() 474 475 proc = transport.get_extra_info('subprocess') 476 orig_kill = proc.kill 477 proc.kill = kill 478 returncode = transport.get_returncode() 479 transport.close() 480 await asyncio.wait_for(transport._wait(), 5) 481 return (returncode, kill_called) 482 483 # Ignore "Close running child process: kill ..." log 484 with test_utils.disable_logger(): 485 try: 486 returncode, killed = self.loop.run_until_complete( 487 kill_running() 488 ) 489 except asyncio.TimeoutError: 490 self.skipTest( 491 "Timeout failure on waiting for subprocess stopping" 492 ) 493 self.assertIsNone(returncode) 494 495 # transport.close() must kill the process if it is still running 496 self.assertTrue(killed) 497 test_utils.run_briefly(self.loop) 498 499 def test_close_dont_kill_finished(self): 500 501 async def kill_running(): 502 create = self.loop.subprocess_exec(asyncio.SubprocessProtocol, 503 *PROGRAM_BLOCKED) 504 transport, protocol = await create 505 proc = transport.get_extra_info('subprocess') 506 507 # kill the process (but asyncio is not notified immediately) 508 proc.kill() 509 proc.wait() 510 511 proc.kill = mock.Mock() 512 proc_returncode = proc.poll() 513 transport_returncode = transport.get_returncode() 514 transport.close() 515 return (proc_returncode, transport_returncode, proc.kill.called) 516 517 # Ignore "Unknown child process pid ..." log of SafeChildWatcher, 518 # emitted because the test already consumes the exit status: 519 # proc.wait() 520 with test_utils.disable_logger(): 521 result = self.loop.run_until_complete(kill_running()) 522 test_utils.run_briefly(self.loop) 523 524 proc_returncode, transport_return_code, killed = result 525 526 self.assertIsNotNone(proc_returncode) 527 self.assertIsNone(transport_return_code) 528 529 # transport.close() must not kill the process if it finished, even if 530 # the transport was not notified yet 531 self.assertFalse(killed) 532 533 # Unlike SafeChildWatcher, FastChildWatcher does not pop the 534 # callbacks if waitpid() is called elsewhere. Let's clear them 535 # manually to avoid a warning when the watcher is detached. 536 if (sys.platform != 'win32' and 537 isinstance(self, SubprocessFastWatcherTests)): 538 asyncio.get_child_watcher()._callbacks.clear() 539 540 async def _test_popen_error(self, stdin): 541 if sys.platform == 'win32': 542 target = 'asyncio.windows_utils.Popen' 543 else: 544 target = 'subprocess.Popen' 545 with mock.patch(target) as popen: 546 exc = ZeroDivisionError 547 popen.side_effect = exc 548 549 with warnings.catch_warnings(record=True) as warns: 550 with self.assertRaises(exc): 551 await asyncio.create_subprocess_exec( 552 sys.executable, 553 '-c', 554 'pass', 555 stdin=stdin 556 ) 557 self.assertEqual(warns, []) 558 559 def test_popen_error(self): 560 # Issue #24763: check that the subprocess transport is closed 561 # when BaseSubprocessTransport fails 562 self.loop.run_until_complete(self._test_popen_error(stdin=None)) 563 564 def test_popen_error_with_stdin_pipe(self): 565 # Issue #35721: check that newly created socket pair is closed when 566 # Popen fails 567 self.loop.run_until_complete( 568 self._test_popen_error(stdin=subprocess.PIPE)) 569 570 def test_read_stdout_after_process_exit(self): 571 572 async def execute(): 573 code = '\n'.join(['import sys', 574 'for _ in range(64):', 575 ' sys.stdout.write("x" * 4096)', 576 'sys.stdout.flush()', 577 'sys.exit(1)']) 578 579 process = await asyncio.create_subprocess_exec( 580 sys.executable, '-c', code, 581 stdout=asyncio.subprocess.PIPE, 582 ) 583 584 while True: 585 data = await process.stdout.read(65536) 586 if data: 587 await asyncio.sleep(0.3) 588 else: 589 break 590 591 self.loop.run_until_complete(execute()) 592 593 def test_create_subprocess_exec_text_mode_fails(self): 594 async def execute(): 595 with self.assertRaises(ValueError): 596 await subprocess.create_subprocess_exec(sys.executable, 597 text=True) 598 599 with self.assertRaises(ValueError): 600 await subprocess.create_subprocess_exec(sys.executable, 601 encoding="utf-8") 602 603 with self.assertRaises(ValueError): 604 await subprocess.create_subprocess_exec(sys.executable, 605 errors="strict") 606 607 self.loop.run_until_complete(execute()) 608 609 def test_create_subprocess_shell_text_mode_fails(self): 610 611 async def execute(): 612 with self.assertRaises(ValueError): 613 await subprocess.create_subprocess_shell(sys.executable, 614 text=True) 615 616 with self.assertRaises(ValueError): 617 await subprocess.create_subprocess_shell(sys.executable, 618 encoding="utf-8") 619 620 with self.assertRaises(ValueError): 621 await subprocess.create_subprocess_shell(sys.executable, 622 errors="strict") 623 624 self.loop.run_until_complete(execute()) 625 626 def test_create_subprocess_exec_with_path(self): 627 async def execute(): 628 p = await subprocess.create_subprocess_exec( 629 support.FakePath(sys.executable), '-c', 'pass') 630 await p.wait() 631 p = await subprocess.create_subprocess_exec( 632 sys.executable, '-c', 'pass', support.FakePath('.')) 633 await p.wait() 634 635 self.assertIsNone(self.loop.run_until_complete(execute())) 636 637 def test_exec_loop_deprecated(self): 638 async def go(): 639 with self.assertWarns(DeprecationWarning): 640 proc = await asyncio.create_subprocess_exec( 641 sys.executable, '-c', 'pass', 642 loop=self.loop, 643 ) 644 await proc.wait() 645 self.loop.run_until_complete(go()) 646 647 def test_shell_loop_deprecated(self): 648 async def go(): 649 with self.assertWarns(DeprecationWarning): 650 proc = await asyncio.create_subprocess_shell( 651 "exit 0", 652 loop=self.loop, 653 ) 654 await proc.wait() 655 self.loop.run_until_complete(go()) 656 657 658if sys.platform != 'win32': 659 # Unix 660 class SubprocessWatcherMixin(SubprocessMixin): 661 662 Watcher = None 663 664 def setUp(self): 665 super().setUp() 666 policy = asyncio.get_event_loop_policy() 667 self.loop = policy.new_event_loop() 668 self.set_event_loop(self.loop) 669 670 watcher = self.Watcher() 671 watcher.attach_loop(self.loop) 672 policy.set_child_watcher(watcher) 673 674 def tearDown(self): 675 super().tearDown() 676 policy = asyncio.get_event_loop_policy() 677 watcher = policy.get_child_watcher() 678 policy.set_child_watcher(None) 679 watcher.attach_loop(None) 680 watcher.close() 681 682 class SubprocessThreadedWatcherTests(SubprocessWatcherMixin, 683 test_utils.TestCase): 684 685 Watcher = unix_events.ThreadedChildWatcher 686 687 class SubprocessMultiLoopWatcherTests(SubprocessWatcherMixin, 688 test_utils.TestCase): 689 690 Watcher = unix_events.MultiLoopChildWatcher 691 692 class SubprocessSafeWatcherTests(SubprocessWatcherMixin, 693 test_utils.TestCase): 694 695 Watcher = unix_events.SafeChildWatcher 696 697 class SubprocessFastWatcherTests(SubprocessWatcherMixin, 698 test_utils.TestCase): 699 700 Watcher = unix_events.FastChildWatcher 701 702 def has_pidfd_support(): 703 if not hasattr(os, 'pidfd_open'): 704 return False 705 try: 706 os.close(os.pidfd_open(os.getpid())) 707 except OSError: 708 return False 709 return True 710 711 @unittest.skipUnless( 712 has_pidfd_support(), 713 "operating system does not support pidfds", 714 ) 715 class SubprocessPidfdWatcherTests(SubprocessWatcherMixin, 716 test_utils.TestCase): 717 Watcher = unix_events.PidfdChildWatcher 718 719else: 720 # Windows 721 class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase): 722 723 def setUp(self): 724 super().setUp() 725 self.loop = asyncio.ProactorEventLoop() 726 self.set_event_loop(self.loop) 727 728 729class GenericWatcherTests: 730 731 def test_create_subprocess_fails_with_inactive_watcher(self): 732 733 async def execute(): 734 watcher = mock.create_authspec(asyncio.AbstractChildWatcher) 735 watcher.is_active.return_value = False 736 asyncio.set_child_watcher(watcher) 737 738 with self.assertRaises(RuntimeError): 739 await subprocess.create_subprocess_exec( 740 support.FakePath(sys.executable), '-c', 'pass') 741 742 watcher.add_child_handler.assert_not_called() 743 744 self.assertIsNone(self.loop.run_until_complete(execute())) 745 746 747 748 749if __name__ == '__main__': 750 unittest.main() 751