diff --git a/exec_helpers/exec_result.py b/exec_helpers/exec_result.py index 16317ad..fb9130d 100644 --- a/exec_helpers/exec_result.py +++ b/exec_helpers/exec_result.py @@ -185,10 +185,7 @@ def __poll_stream( if log: log.log( level=logging.INFO if verbose else logging.DEBUG, - msg=line.decode( - 'utf-8', - errors='backslashreplace' - ).rstrip() + msg=line.decode('utf-8', errors='backslashreplace').rstrip() ) except IOError: pass diff --git a/exec_helpers/subprocess_runner.py b/exec_helpers/subprocess_runner.py index 03720f4..4f9c5d0 100644 --- a/exec_helpers/subprocess_runner.py +++ b/exec_helpers/subprocess_runner.py @@ -26,11 +26,8 @@ import errno import logging import os -import select -import sys import subprocess # nosec # Expected usage import threading -import time import typing # noqa # pylint: disable=unused-import import six @@ -45,19 +42,6 @@ # noinspection PyUnresolvedReferences devnull = open(os.devnull) # subprocess.DEVNULL is py3.3+ -_win = sys.platform == "win32" # type: bool -_posix = 'posix' in sys.builtin_module_names # type: bool - -if _posix: # pragma: no cover - import fcntl # pylint: disable=import-error - -elif _win: # pragma: no cover - import ctypes - from ctypes import wintypes # pylint: disable=import-error - from ctypes import windll # pylint: disable=import-error - # noinspection PyUnresolvedReferences - import msvcrt # pylint: disable=import-error - class SingletonMeta(type): """Metaclass for Singleton. @@ -72,9 +56,8 @@ def __call__(cls, *args, **kwargs): """Singleton.""" with cls._lock: if cls not in cls._instances: - cls._instances[cls] = super( - SingletonMeta, cls - ).__call__(*args, **kwargs) + # noinspection PySuperArguments + cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs) return cls._instances[cls] @classmethod @@ -91,77 +74,6 @@ def __prepare__( return collections.OrderedDict() # pragma: no cover -def set_nonblocking_pipe(pipe): # type: (typing.Any) -> None - """Set PIPE unblocked to allow polling of all pipes in parallel.""" - if pipe is None: # pragma: no cover - return - - descriptor = pipe.fileno() # pragma: no cover - - if _posix: # pragma: no cover - # Get flags - flags = fcntl.fcntl(descriptor, fcntl.F_GETFL) - - # Set nonblock mode - fcntl.fcntl(descriptor, fcntl.F_SETFL, flags | os.O_NONBLOCK) - - elif _win: # pragma: no cover - # noinspection PyPep8Naming - SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState - SetNamedPipeHandleState.argtypes = [ - wintypes.HANDLE, # hNamedPipe - wintypes.LPDWORD, # lpMode - wintypes.LPDWORD, # lpMaxCollectionCount - wintypes.LPDWORD, # lpCollectDataTimeout - ] - SetNamedPipeHandleState.restype = wintypes.BOOL - # noinspection PyPep8Naming - PIPE_NOWAIT = wintypes.DWORD(0x00000001) - handle = msvcrt.get_osfhandle(descriptor) - - windll.kernel32.SetNamedPipeHandleState( - handle, - ctypes.byref(PIPE_NOWAIT), None, None - ) - - -def set_blocking_pipe(pipe): # type: (typing.Any) -> None - """Set pipe blocking mode for final read on process close. - - This will allow to read pipe until closed on remote side. - """ - if pipe is None: # pragma: no cover - return - - descriptor = pipe.fileno() # pragma: no cover - - if _posix: # pragma: no cover - # Get flags - flags = fcntl.fcntl(descriptor, fcntl.F_GETFL) - - # Set block mode - fcntl.fcntl(descriptor, fcntl.F_SETFL, flags & (flags ^ os.O_NONBLOCK)) - - elif _win: # pragma: no cover - # noinspection PyPep8Naming - SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState - SetNamedPipeHandleState.argtypes = [ - wintypes.HANDLE, # hNamedPipe - wintypes.LPDWORD, # lpMode - wintypes.LPDWORD, # lpMaxCollectionCount - wintypes.LPDWORD, # lpCollectDataTimeout - ] - SetNamedPipeHandleState.restype = wintypes.BOOL - # noinspection PyPep8Naming - PIPE_WAIT = wintypes.DWORD(0x00000000) - handle = msvcrt.get_osfhandle(descriptor) - - windll.kernel32.SetNamedPipeHandleState( - handle, - ctypes.byref(PIPE_WAIT), None, None - ) - - class Subprocess(six.with_metaclass(SingletonMeta, api.ExecHelper)): """Subprocess helper with timeouts and lock-free FIFO.""" @@ -179,10 +91,7 @@ def __init__( .. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd """ - super(Subprocess, self).__init__( - logger=logger, - log_mask_re=log_mask_re - ) + super(Subprocess, self).__init__(logger=logger, log_mask_re=log_mask_re) self.__process = None def _exec_command( @@ -218,98 +127,57 @@ def _exec_command( .. versionadded:: 1.2.0 """ - def poll_streams(): - """Poll streams to the result object.""" - if _win: # pragma: no cover - # select.select is not supported on windows - result.read_stdout(src=stdout, log=logger, verbose=verbose) - result.read_stderr(src=stderr, log=logger, verbose=verbose) - else: # pragma: no cover - rlist, _, _ = select.select( - [item for item in (stdout, stderr) if item is not None], - [], - []) - if rlist: - if stdout in rlist: - result.read_stdout( - src=stdout, - log=logger, - verbose=verbose - ) - if stderr in rlist: - result.read_stderr( - src=stderr, - log=logger, - verbose=verbose - ) + @threaded.threadpooled + def poll_stdout(): + """Sync stdout poll.""" + result.read_stdout( + src=stdout, + log=logger, + verbose=verbose + ) @threaded.threadpooled - def poll_pipes(stop, ): # type: (threading.Event) -> None - """Polling task for FIFO buffers. - - :type stop: Event - """ - while not stop.is_set(): - time.sleep(0.1) - if stdout or stderr: - poll_streams() - - interface.poll() - - if interface.returncode is not None: - set_blocking_pipe(stdout) - set_blocking_pipe(stderr) - result.read_stdout( - src=stdout, - log=logger, - verbose=verbose - ) - result.read_stderr( - src=stderr, - log=logger, - verbose=verbose - ) - result.exit_code = interface.returncode - - stop.set() + def poll_stderr(): + """Sync stderr poll.""" + result.read_stderr( + src=stderr, + log=logger, + verbose=verbose + ) # Store command with hidden data - cmd_for_log = self._mask_command( - cmd=command, - log_mask_re=log_mask_re - ) + cmd_for_log = self._mask_command(cmd=command, log_mask_re=log_mask_re) result = exec_result.ExecResult(cmd=cmd_for_log) - stop_event = threading.Event() # pylint: disable=assignment-from-no-return - future = poll_pipes(stop_event) # type: concurrent.futures.Future + stdout_future = poll_stdout() # type: concurrent.futures.Future + stderr_future = poll_stderr() # type: concurrent.futures.Future # pylint: enable=assignment-from-no-return - # wait for process close - concurrent.futures.wait([future], timeout) + concurrent.futures.wait([stdout_future, stderr_future], timeout=timeout) # Wait real timeout here + exit_code = interface.poll() # Update exit code # Process closed? - if stop_event.is_set(): + if exit_code is not None: + result.exit_code = exit_code return result # Kill not ended process and wait for close try: interface.kill() # kill -9 - stop_event.wait(5) + concurrent.futures.wait([stdout_future, stderr_future], timeout=5) # Force stop cycle if no exit code after kill - stop_event.set() - future.cancel() + stdout_future.cancel() + stderr_future.cancel() except OSError: - # Nothing to kill - logger.warning( - u"{!s} has been completed just after timeout: " - "please validate timeout.".format(command)) - return result - - wait_err_msg = _log_templates.CMD_WAIT_ERROR.format( - result=result, - timeout=timeout - ) + exit_code = interface.poll() + if exit_code is not None: # Nothing to kill + logger.warning("{!s} has been completed just after timeout: please validate timeout.".format(command)) + result.exit_code = exit_code + return result + raise # Some other error + + wait_err_msg = _log_templates.CMD_WAIT_ERROR.format(result=result, timeout=timeout) logger.debug(wait_err_msg) raise exceptions.ExecHelperTimeoutError(result=result, timeout=timeout) @@ -347,10 +215,7 @@ def execute_async( .. versionadded:: 1.2.0 """ - cmd_for_log = self._mask_command( - cmd=command, - log_mask_re=log_mask_re - ) + cmd_for_log = self._mask_command(cmd=command, log_mask_re=log_mask_re) self.logger.log( level=logging.INFO if verbose else logging.DEBUG, @@ -395,9 +260,4 @@ def execute_async( process.kill() raise - if open_stdout: - set_nonblocking_pipe(process.stdout) - if open_stderr: - set_nonblocking_pipe(process.stderr) - return process, None, process.stderr, process.stdout diff --git a/exec_helpers/subprocess_runner.pyi b/exec_helpers/subprocess_runner.pyi index 0229c21..4d6c2b7 100644 --- a/exec_helpers/subprocess_runner.pyi +++ b/exec_helpers/subprocess_runner.pyi @@ -9,9 +9,6 @@ from exec_helpers import exec_result, api logger: logging.Logger devnull: typing.IO -_win: bool -_posix: bool - class SingletonMeta(type): _instances: typing.Dict[typing.Type, typing.Any] = ... _lock: threading.RLock = ... @@ -26,12 +23,6 @@ class SingletonMeta(type): **kwargs: typing.Dict ) -> collections.OrderedDict: ... - -def set_nonblocking_pipe(pipe: typing.Any) -> None: ... - -def set_blocking_pipe(pipe: typing.Any) -> None: ... - - class Subprocess(api.ExecHelper, metaclass=SingletonMeta): def __init__(self, log_mask_re: typing.Optional[str] = ...) -> None: ... diff --git a/test/test_subprocess_runner.py b/test/test_subprocess_runner.py index f143853..1999214 100644 --- a/test/test_subprocess_runner.py +++ b/test/test_subprocess_runner.py @@ -23,7 +23,6 @@ import errno import logging import subprocess -import sys import unittest import mock @@ -32,13 +31,6 @@ import exec_helpers from exec_helpers import subprocess_runner -if 'posix' in sys.builtin_module_names: - mock_block = 'fcntl.fcntl' -elif sys.platform == "win32": - mock_block = 'windll.kernel32.SetNamedPipeHandleState' -else: - mock_block = 'logging.captureWarnings' - command = 'ls ~\nline 2\nline 3\nline с кирилицей' command_log = u"Executing command:\n{!r}\n".format(command.rstrip()) stdout_list = [b' \n', b'2\n', b'3\n', b' \n'] @@ -59,8 +51,6 @@ def fileno(self): @mock.patch('exec_helpers.subprocess_runner.logger', autospec=True) -@mock.patch('select.select', autospec=True) -@mock.patch(mock_block, autospec=True) @mock.patch('subprocess.Popen', autospec=True, name='subprocess.Popen') class TestSubprocessRunner(unittest.TestCase): def setUp(self): @@ -68,15 +58,15 @@ def setUp(self): @staticmethod def prepare_close( - popen, - cmd=command, + popen, # type: mock.MagicMock + cmd=command, # type: str stderr_val=None, ec=0, open_stdout=True, stdout_override=None, open_stderr=True, cmd_in_result=None, - ): + ): # type: (...) -> typing.Tuple[mock.Mock, exec_helpers.ExecResult] if open_stdout: stdout_lines = stdout_list if stdout_override is None else stdout_override stdout = FakeFileStream(*stdout_lines) @@ -97,6 +87,7 @@ def prepare_close( popen_obj.attach_mock(stderr, 'stderr') else: popen_obj.configure_mock(stderr=None) + popen_obj.attach_mock(mock.Mock(return_value=ec), 'poll') popen_obj.configure_mock(returncode=ec) popen.return_value = popen_obj @@ -118,12 +109,9 @@ def gen_cmd_result_log_message(result): def test_001_call( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None popen_obj, exp_result = self.prepare_close(popen) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -142,25 +130,15 @@ def test_001_call( universal_newlines=False, ), )) - logger.assert_has_calls( - [ - mock.call.log(level=logging.DEBUG, msg=command_log), - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8')) - ) - for x in stdout_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8'))) - for x in stderr_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=self.gen_cmd_result_log_message(result)), - ]) + + self.assertEqual( + logger.mock_calls[0], + mock.call.log(level=logging.DEBUG, msg=command_log) + ) + self.assertEqual( + logger.mock_calls[-1], + mock.call.log(level=logging.DEBUG, msg=self.gen_cmd_result_log_message(result)), + ) self.assertIn( mock.call.poll(), popen_obj.mock_calls ) @@ -168,46 +146,30 @@ def test_001_call( def test_002_call_verbose( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None popen_obj, _ = self.prepare_close(popen) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() # noinspection PyTypeChecker result = runner.execute(command, verbose=True) - logger.assert_has_calls( - [ - mock.call.log(level=logging.INFO, msg=command_log), - ] + [ - mock.call.log( - level=logging.INFO, - msg=str(x.rstrip().decode('utf-8'))) - for x in stdout_list - ] + [ - mock.call.log( - level=logging.INFO, - msg=str(x.rstrip().decode('utf-8'))) - for x in stderr_list - ] + [ - mock.call.log( - level=logging.INFO, - msg=self.gen_cmd_result_log_message(result)), - ]) + self.assertEqual( + logger.mock_calls[0], + mock.call.log(level=logging.INFO, msg=command_log) + ) + self.assertEqual( + logger.mock_calls[-1], + mock.call.log(level=logging.INFO, msg=self.gen_cmd_result_log_message(result)), + ) def test_003_context_manager( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None popen_obj, exp_result = self.prepare_close(popen) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] subprocess_runner.SingletonMeta._instances.clear() @@ -230,15 +192,12 @@ def test_003_context_manager( @mock.patch('time.sleep', autospec=True) def test_004_execute_timeout_fail( self, - sleep, # type: mock.MagicMock - popen, # type: mock.MagicMock _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + popen, # type: mock.MagicMock + __ # type: mock.MagicMock ): popen_obj, exp_result = self.prepare_close(popen) - popen_obj.configure_mock(returncode=None) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] + popen_obj.poll.return_value = None runner = exec_helpers.Subprocess() @@ -269,12 +228,9 @@ def test_004_execute_timeout_fail( def test_005_execute_no_stdout( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): popen_obj, exp_result = self.prepare_close(popen, open_stdout=False) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -313,12 +269,9 @@ def test_005_execute_no_stdout( def test_006_execute_no_stderr( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): popen_obj, exp_result = self.prepare_close(popen, open_stderr=False) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -358,8 +311,6 @@ def test_006_execute_no_stderr( def test_007_execute_no_stdout_stderr( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): popen_obj, exp_result = self.prepare_close( @@ -367,7 +318,6 @@ def test_007_execute_no_stdout_stderr( open_stdout=False, open_stderr=False ) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -401,8 +351,6 @@ def test_007_execute_no_stdout_stderr( def test_008_execute_mask_global( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): cmd = "USE='secret=secret_pass' do task" @@ -415,7 +363,6 @@ def test_008_execute_mask_global( cmd=cmd, cmd_in_result=masked_cmd ) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess( log_mask_re=log_mask_re @@ -436,25 +383,16 @@ def test_008_execute_mask_global( universal_newlines=False, ), )) - logger.assert_has_calls( - [ - mock.call.log(level=logging.DEBUG, msg=cmd_log), - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8')) - ) - for x in stdout_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8'))) - for x in stderr_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=self.gen_cmd_result_log_message(result)), - ]) + + self.assertEqual( + logger.mock_calls[0], + mock.call.log(level=logging.DEBUG, msg=cmd_log) + ) + self.assertEqual( + logger.mock_calls[-1], + mock.call.log(level=logging.DEBUG, msg=self.gen_cmd_result_log_message(result)), + ) + self.assertIn( mock.call.poll(), popen_obj.mock_calls ) @@ -462,8 +400,6 @@ def test_008_execute_mask_global( def test_009_execute_mask_local( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): cmd = "USE='secret=secret_pass' do task" @@ -476,7 +412,6 @@ def test_009_execute_mask_local( cmd=cmd, cmd_in_result=masked_cmd ) - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -495,25 +430,14 @@ def test_009_execute_mask_local( universal_newlines=False, ), )) - logger.assert_has_calls( - [ - mock.call.log(level=logging.DEBUG, msg=cmd_log), - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8')) - ) - for x in stdout_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=str(x.rstrip().decode('utf-8'))) - for x in stderr_list - ] + [ - mock.call.log( - level=logging.DEBUG, - msg=self.gen_cmd_result_log_message(result)), - ]) + self.assertEqual( + logger.mock_calls[0], + mock.call.log(level=logging.DEBUG, msg=cmd_log) + ) + self.assertEqual( + logger.mock_calls[-1], + mock.call.log(level=logging.DEBUG, msg=self.gen_cmd_result_log_message(result)), + ) self.assertIn( mock.call.poll(), popen_obj.mock_calls ) @@ -521,9 +445,7 @@ def test_009_execute_mask_local( def test_004_check_stdin_str( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None stdin = u'this is a line' @@ -531,7 +453,6 @@ def test_004_check_stdin_str( stdin_mock = mock.Mock() popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -560,9 +481,7 @@ def test_004_check_stdin_str( def test_005_check_stdin_bytes( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -570,7 +489,6 @@ def test_005_check_stdin_bytes( stdin_mock = mock.Mock() popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -599,9 +517,7 @@ def test_005_check_stdin_bytes( def test_006_check_stdin_bytearray( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None stdin = bytearray(b'this is a line') @@ -609,7 +525,6 @@ def test_006_check_stdin_bytearray( stdin_mock = mock.Mock() popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -639,8 +554,6 @@ def test_006_check_stdin_bytearray( def test_007_check_stdin_fail_broken_pipe( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -653,7 +566,6 @@ def test_007_check_stdin_fail_broken_pipe( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_err), 'write') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -683,8 +595,6 @@ def test_007_check_stdin_fail_broken_pipe( def test_008_check_stdin_fail_closed_win( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -697,7 +607,6 @@ def test_008_check_stdin_fail_closed_win( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_error), 'write') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -726,9 +635,7 @@ def test_008_check_stdin_fail_closed_win( def test_009_check_stdin_fail_write( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -739,7 +646,6 @@ def test_009_check_stdin_fail_write( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_error), 'write') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -752,8 +658,6 @@ def test_009_check_stdin_fail_write( def test_010_check_stdin_fail_close_pipe( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -766,7 +670,6 @@ def test_010_check_stdin_fail_close_pipe( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_err), 'close') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -796,8 +699,6 @@ def test_010_check_stdin_fail_close_pipe( def test_011_check_stdin_fail_close_pipe_win( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock logger # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -810,7 +711,6 @@ def test_011_check_stdin_fail_close_pipe_win( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_error), 'close') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -839,9 +739,7 @@ def test_011_check_stdin_fail_close_pipe_win( def test_012_check_stdin_fail_close( self, popen, # type: mock.MagicMock - _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + _ # type: mock.MagicMock ): # type: (...) -> None stdin = b'this is a line' @@ -852,7 +750,6 @@ def test_012_check_stdin_fail_close( stdin_mock = mock.Mock() stdin_mock.attach_mock(mock.Mock(side_effect=pipe_error), 'close') popen_obj.attach_mock(stdin_mock, 'stdin') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() @@ -864,17 +761,14 @@ def test_012_check_stdin_fail_close( @mock.patch('time.sleep', autospec=True) def test_013_execute_timeout_done( self, - sleep, # type: mock.MagicMock - popen, # type: mock.MagicMock _, # type: mock.MagicMock - select, # type: mock.MagicMock - logger # type: mock.MagicMock + popen, # type: mock.MagicMock + __ # type: mock.MagicMock ): popen_obj, exp_result = self.prepare_close(popen, ec=exec_helpers.ExitCodes.EX_INVALID) - popen_obj.configure_mock(returncode=None) + popen_obj.poll.side_effect = [None, exec_helpers.ExitCodes.EX_INVALID] popen_obj.attach_mock(mock.Mock(side_effect=OSError), 'kill') - select.return_value = [popen_obj.stdout, popen_obj.stderr], [], [] runner = exec_helpers.Subprocess() diff --git a/tox.ini b/tox.ini index ea31f72..f0bb51b 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,7 @@ deps = py{27,py}: mock commands = + pip freeze py.test --junitxml=unit_result.xml --cov-report html --self-contained-html --html=report.html --cov-config .coveragerc --cov=exec_helpers {posargs:test} coverage report --fail-under 97 @@ -33,28 +34,28 @@ usedevelop = False commands = python setup.py bdist_wheel pip install exec_helpers --no-index -f dist - py.test -vv {posargs:test} + py.test -vvv {posargs:test} [testenv:py35-nocov] usedevelop = False commands = python setup.py bdist_wheel pip install exec_helpers --no-index -f dist - py.test -vv {posargs:test} + py.test -vvv {posargs:test} [testenv:py36-nocov] usedevelop = False commands = python setup.py bdist_wheel pip install exec_helpers --no-index -f dist - py.test -vv {posargs:test} + py.test -vvv {posargs:test} [testenv:py37-nocov] usedevelop = False commands = python setup.py bdist_wheel pip install exec_helpers --no-index -f dist - py.test -vv {posargs:test} + py.test -vvv {posargs:test} [testenv:venv] commands = {posargs:}