Example #1
0
    def test_012_check_stdin_fail_close(
        self,
        popen,  # type: mock.MagicMock
        _,  # type: mock.MagicMock
        select,  # type: mock.MagicMock
        logger  # type: mock.MagicMock
    ):  # type: (...) -> None
        stdin = b'this is a line'

        popen_obj, exp_result = self.prepare_close(popen,
                                                   cmd=print_stdin,
                                                   stdout_override=[stdin])

        pipe_error = OSError()

        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()

        with self.assertRaises(OSError):
            # noinspection PyTypeChecker
            runner.execute_async(print_stdin, stdin=stdin)
        popen_obj.kill.assert_called_once()
    def test_001_check_call(self, execute, logger):
        exit_code = 0
        return_value = exec_helpers.ExecResult(
            cmd=command,
            stdout=stdout_list,
            stderr=stdout_list,
            exit_code=exit_code,
        )
        execute.return_value = return_value

        verbose = False

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker
        result = runner.check_call(
            command=command, verbose=verbose, timeout=None)
        execute.assert_called_once_with(command, verbose, None)
        self.assertEqual(result, return_value)

        exit_code = 1
        return_value = exec_helpers.ExecResult(
            cmd=command,
            stdout=stdout_list,
            stderr=stdout_list,
            exit_code=exit_code,
        )
        execute.reset_mock()
        execute.return_value = return_value
        with self.assertRaises(exec_helpers.CalledProcessError):
            # noinspection PyTypeChecker
            runner.check_call(command=command, verbose=verbose, timeout=None)
        execute.assert_called_once_with(command, verbose, None)
    def test_004_execute_timeout_fail(
        self,
        sleep,
        popen, _, select, logger
    ):
        popen_obj, exp_result = self.prepare_close(popen)
        popen_obj.configure_mock(returncode=None)
        select.return_value = [popen_obj.stdout, popen_obj.stderr], [], []

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker

        with self.assertRaises(exec_helpers.ExecHelperTimeoutError) as cm:
            # noinspection PyTypeChecker
            runner.execute(command, timeout=0.2)

        self.assertEqual(cm.exception.timeout, 0.2)
        self.assertEqual(cm.exception.cmd, command)
        self.assertEqual(cm.exception.stdout, exp_result.stdout_str)
        self.assertEqual(cm.exception.stderr, exp_result.stderr_str)

        popen.assert_has_calls((
            mock.call(
                args=[command],
                cwd=None,
                env=None,
                shell=True,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                universal_newlines=False,
            ),
        ))
    def test_003_context_manager(
        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], [], []

        subprocess_runner.SingletonMeta._instances.clear()

        with mock.patch('threading.RLock', autospec=True):
            with exec_helpers.Subprocess() as runner:
                runner.lock.acquire.assert_called()
                self.assertEqual(
                    mock.call.acquire(), runner.lock.mock_calls[0]
                )
                result = runner.execute(command)
                self.assertEqual(
                    result, exp_result

                )

            runner.lock.release.assert_called()

        subprocess_runner.SingletonMeta._instances.clear()
Example #5
0
    def test_007_execute_no_stdout_stderr(self, popen, _, select, logger):
        popen_obj, exp_result = self.prepare_close(popen,
                                                   open_stdout=False,
                                                   open_stderr=False)
        select.return_value = [popen_obj.stdout, popen_obj.stderr], [], []

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker
        result = runner.execute(command, open_stdout=False, open_stderr=False)
        self.assertEqual(result, exp_result)
        popen.assert_has_calls((mock.call(
            args=[command],
            cwd=None,
            env=None,
            shell=True,
            stderr=subprocess_runner.devnull,
            stdin=subprocess.PIPE,
            stdout=subprocess_runner.devnull,
            universal_newlines=False,
        ), ))
        logger.assert_has_calls([
            mock.call.log(level=logging.DEBUG, msg=command_log),
        ] + [
            mock.call.log(level=logging.DEBUG,
                          msg=self.gen_cmd_result_log_message(result)),
        ])
        self.assertIn(mock.call.poll(), popen_obj.mock_calls)
Example #6
0
    def test_006_check_stdin_bytearray(
        self,
        popen,  # type: mock.MagicMock
        _,  # type: mock.MagicMock
        select,  # type: mock.MagicMock
        logger  # type: mock.MagicMock
    ):  # type: (...) -> None
        stdin = bytearray(b'this is a line')

        popen_obj, exp_result = self.prepare_close(
            popen, cmd=print_stdin, stdout_override=[bytes(l) for l in stdin])

        stdin_mock = mock.Mock()
        popen_obj.attach_mock(stdin_mock, 'stdin')
        select.return_value = [popen_obj.stdout, popen_obj.stderr], [], []

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker
        result = runner.execute(print_stdin, stdin=stdin)
        self.assertEqual(result, exp_result)

        popen.assert_has_calls((mock.call(
            args=[print_stdin],
            cwd=None,
            env=None,
            shell=True,
            stderr=subprocess.PIPE,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            universal_newlines=False,
        ), ))

        stdin_mock.assert_has_calls(
            [mock.call.write(stdin), mock.call.close()])
def test_007_check_stderr(execute, exec_result, subprocess_logger) -> None:
    """Test STDERR content validator."""
    runner = exec_helpers.Subprocess()
    if not exec_result.stderr:
        assert runner.check_stderr(command,
                                   stdin=exec_result.stdin,
                                   expected=[exec_result.exit_code
                                             ]) == exec_result
    else:
        with pytest.raises(exec_helpers.CalledProcessError) as e:
            runner.check_stderr(command,
                                stdin=exec_result.stdin,
                                expected=[exec_result.exit_code])
        exc: exec_helpers.CalledProcessError = e.value
        assert exc.result == exec_result
        assert exc.cmd == exec_result.cmd
        assert exc.returncode == exec_result.exit_code
        assert exc.stdout == exec_result.stdout_str
        assert exc.stderr == exec_result.stderr_str
        assert exc.result == exec_result

        assert subprocess_logger.mock_calls[-1] == mock.call.error(
            msg=
            f"Command {exc.result.cmd!r} output contains STDERR while not expected\n"
            f"\texit code: {exc.result.exit_code!s}")
def test_006_check_call_expect(execute, exec_result,
                               subprocess_logger) -> None:
    """Test exit code validator with custom return codes."""
    runner = exec_helpers.Subprocess()
    assert runner.check_call(command,
                             stdin=exec_result.stdin,
                             expected=[exec_result.exit_code]) == exec_result
    def test_013_execute_timeout_done(
        self,
        sleep,
        popen, _, select, logger
    ):
        popen_obj, exp_result = self.prepare_close(popen, ec=exec_helpers.ExitCodes.EX_INVALID)
        popen_obj.configure_mock(returncode=None)
        popen_obj.attach_mock(mock.Mock(side_effect=OSError), 'kill')
        select.return_value = [popen_obj.stdout, popen_obj.stderr], [], []

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker

        res = runner.execute(command, timeout=0.2)

        self.assertEqual(res, exp_result)

        popen.assert_has_calls((
            mock.call(
                args=[command],
                cwd=None,
                env=None,
                shell=True,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                universal_newlines=False,
            ),
        ))
    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)),
            ])
def test_008_check_stderr_no_raise(execute, exec_result,
                                   subprocess_logger) -> None:
    """Test STDERR content validator in permissive mode."""
    runner = exec_helpers.Subprocess()
    assert (runner.check_stderr(command,
                                stdin=exec_result.stdin,
                                expected=[exec_result.exit_code],
                                raise_on_err=False) == exec_result)
    def test_008_execute_mask_global(self, popen, _, select, logger):
        cmd = "USE='secret=secret_pass' do task"
        log_mask_re = r"secret\s*=\s*([A-Z-a-z0-9_\-]+)"
        masked_cmd = "USE='secret=<*masked*>' do task"
        cmd_log = u"Executing command:\n{!r}\n".format(masked_cmd)

        popen_obj, exp_result = self.prepare_close(
            popen,
            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
        )

        # noinspection PyTypeChecker
        result = runner.execute(cmd)
        self.assertEqual(result, exp_result)
        popen.assert_has_calls((
            mock.call(
                args=[cmd],
                cwd=None,
                env=None,
                shell=True,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                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.assertIn(
            mock.call.poll(), popen_obj.mock_calls
        )
def test_001_execute_async(popen, subprocess_logger, run_parameters) -> None:
    """Test low level API."""
    runner = exec_helpers.Subprocess()
    res = runner._execute_async(
        command,
        stdin=run_parameters["stdin"],
        open_stdout=run_parameters["open_stdout"],
        open_stderr=run_parameters["open_stderr"],
    )
    assert isinstance(res, SubprocessExecuteAsyncResult)
    assert res.interface.wait() == run_parameters["ec"]
    assert res.interface.returncode == run_parameters["ec"]

    stdout = run_parameters["stdout"]
    stderr = run_parameters["stderr"]

    if stdout is not None:
        assert read_stream(res.stdout) == stdout
    else:
        assert res.stdout is stdout
    if stderr is not None:
        assert read_stream(res.stderr) == stderr
    else:
        assert res.stderr is stderr

    if run_parameters["stdin"] is None:
        stdin = None
    elif isinstance(run_parameters["stdin"], bytes):
        stdin = run_parameters["stdin"]
    elif isinstance(run_parameters["stdin"], str):
        stdin = run_parameters["stdin"].encode(encoding="utf-8")
    else:
        stdin = bytes(run_parameters["stdin"])
    if stdin:
        assert res.stdin is None

    popen.assert_called_once_with(
        args=[command],
        stdout=subprocess.PIPE
        if run_parameters["open_stdout"] else subprocess.DEVNULL,
        stderr=subprocess.PIPE
        if run_parameters["open_stderr"] else subprocess.DEVNULL,
        stdin=subprocess.PIPE,
        shell=True,
        cwd=run_parameters.get("cwd", None),
        env=run_parameters.get("env", None),
        universal_newlines=False,
        **_subprocess_helpers.subprocess_kw,
    )

    if stdin is not None:
        res.interface.stdin.write.assert_called_once_with(stdin)
        res.interface.stdin.close.assert_called_once()
def test_003_context_manager(mocker, popen, subprocess_logger, exec_result,
                             run_parameters) -> None:
    """Test context manager for threads synchronization."""
    lock_mock = mocker.patch("threading.RLock")

    with exec_helpers.Subprocess() as runner:
        res = runner.execute(command, stdin=run_parameters["stdin"])
    lock_mock.acquire_assert_called_once()
    lock_mock.release_assert_called_once()

    assert isinstance(res, exec_helpers.ExecResult)
    assert res == exec_result
def test_009_call(popen, subprocess_logger, exec_result,
                  run_parameters) -> None:
    """Test callable."""
    runner = exec_helpers.Subprocess()
    res = runner(
        command,
        stdin=run_parameters["stdin"],
        open_stdout=run_parameters["open_stdout"],
        open_stderr=run_parameters["open_stderr"],
    )
    assert isinstance(res, exec_helpers.ExecResult)
    assert res == exec_result
    popen().wait.assert_called_once_with(timeout=default_timeout)
    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()

        # noinspection PyTypeChecker
        result = runner.execute(command)
        self.assertEqual(result, exp_result)
        popen.assert_has_calls((
            mock.call(
                args=[command],
                cwd=None,
                env=None,
                shell=True,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                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.assertIn(
            mock.call.poll(), popen_obj.mock_calls
        )
def test_005_check_call_no_raise(execute, exec_result,
                                 subprocess_logger) -> None:
    """Test exit code validator in permissive mode."""
    runner = exec_helpers.Subprocess()
    res = runner.check_call(command,
                            stdin=exec_result.stdin,
                            raise_on_err=False)
    assert res == exec_result

    if exec_result.exit_code != exec_helpers.ExitCodes.EX_OK:
        expected = (proc_enums.EXPECTED, )
        assert subprocess_logger.mock_calls[-1] == mock.call.error(
            msg=
            f"Command {res.cmd!r} returned exit code {res.exit_code!s} while expected {expected!r}"
        )
def test_002_execute(popen, subprocess_logger, exec_result,
                     run_parameters) -> None:
    """Test API without checkers."""
    runner = exec_helpers.Subprocess()
    res = runner.execute(
        command,
        stdin=run_parameters["stdin"],
        open_stdout=run_parameters["open_stdout"],
        open_stderr=run_parameters["open_stderr"],
    )
    assert isinstance(res, exec_helpers.ExecResult)
    assert res == exec_result
    popen().wait.assert_called_once_with(timeout=default_timeout)
    assert subprocess_logger.mock_calls[0] == mock.call.log(
        level=logging.DEBUG, msg=command_log)
Example #19
0
    def test_003_check_stderr(self, check_call, _, logger):
        return_value = exec_helpers.ExecResult(
            cmd=command,
            stdout=stdout_list,
            exit_code=0,
        )
        check_call.return_value = return_value

        verbose = False
        raise_on_err = True

        runner = exec_helpers.Subprocess()

        # noinspection PyTypeChecker
        result = runner.check_stderr(command=command,
                                     verbose=verbose,
                                     timeout=None,
                                     raise_on_err=raise_on_err)
        check_call.assert_called_once_with(command,
                                           verbose,
                                           timeout=None,
                                           error_info=None,
                                           raise_on_err=raise_on_err)
        self.assertEqual(result, return_value)

        return_value = exec_helpers.ExecResult(
            cmd=command,
            stdout=stdout_list,
            stderr=stdout_list,
            exit_code=0,
        )

        check_call.reset_mock()
        check_call.return_value = return_value
        with self.assertRaises(exec_helpers.CalledProcessError):
            # noinspection PyTypeChecker
            runner.check_stderr(command=command,
                                verbose=verbose,
                                timeout=None,
                                raise_on_err=raise_on_err)
        check_call.assert_called_once_with(command,
                                           verbose,
                                           timeout=None,
                                           error_info=None,
                                           raise_on_err=raise_on_err)
    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'

        popen_obj, exp_result = self.prepare_close(popen, cmd=print_stdin, stdout_override=[stdin])

        pipe_err = BrokenPipeError()
        pipe_err.errno = errno.EPIPE

        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()

        # noinspection PyTypeChecker
        result = runner.execute(print_stdin, stdin=stdin)
        self.assertEqual(result, exp_result)

        popen.assert_has_calls((
            mock.call(
                args=[print_stdin],
                cwd=None,
                env=None,
                shell=True,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                universal_newlines=False,
            ),
        ))

        stdin_mock.assert_has_calls([
            mock.call.write(stdin),
            mock.call.close()
        ])
        logger.warning.assert_not_called()
def test_004_check_call(execute, exec_result, subprocess_logger) -> None:
    """Test exit code validator."""
    runner = exec_helpers.Subprocess()
    if exec_result.exit_code == exec_helpers.ExitCodes.EX_OK:
        assert runner.check_call(command,
                                 stdin=exec_result.stdin) == exec_result
    else:
        with pytest.raises(exec_helpers.CalledProcessError) as e:
            runner.check_call(command, stdin=exec_result.stdin)

        exc: exec_helpers.CalledProcessError = e.value
        assert exc.cmd == exec_result.cmd
        assert exc.returncode == exec_result.exit_code
        assert exc.stdout == exec_result.stdout_str
        assert exc.stderr == exec_result.stderr_str
        assert exc.result == exec_result
        assert exc.expected == (proc_enums.EXPECTED, )

        assert subprocess_logger.mock_calls[-1] == mock.call.error(
            msg=
            f"Command {exc.result.cmd!r} returned exit code {exc.result.exit_code!s} "
            f"while expected {exc.expected!r}")
def test_special_cases(create_subprocess_shell, exec_result, subprocess_logger,
                       run_parameters) -> None:
    """Parametrized validation of special cases."""
    command_parameters: CommandParameters = run_parameters[
        "command_parameters"]
    runner = exec_helpers.Subprocess(
        log_mask_re=run_parameters.get("init_log_mask_re", None))
    if "expect_exc" not in run_parameters:
        res = runner.execute(**command_parameters.as_dict())
        level = logging.INFO if command_parameters.verbose else logging.DEBUG

        command_for_log = run_parameters.get("masked_cmd", command)
        command_log = f"Executing command:\n{command_for_log.rstrip()!r}\n"
        result_log = "Command {command!r} exit code: {result.exit_code!s}".format(
            command=command_for_log.rstrip(), result=res)

        assert subprocess_logger.mock_calls[0] == mock.call.log(
            level=level, msg=command_log)
        assert subprocess_logger.mock_calls[-1] == mock.call.log(
            level=level, msg=result_log)
        assert res == exec_result
    else:
        with pytest.raises(run_parameters["expect_exc"]):
            runner.execute(**command_parameters.as_dict())