コード例 #1
0
def _patch(target, method, new_value):
    if isinstance(target, six.string_types):
        target = testslide._importer(target)

    if isinstance(target, StrictMock):
        original_callable = None
    else:
        original_callable = getattr(target, method)

    new_value = _add_signature_validation(new_value, target, method)
    restore_value = target.__dict__.get(method, None)

    if inspect.isclass(target):
        if _is_instance_method(target, method):
            raise ValueError(
                "Patching an instance method at the class is not supported: "
                "bugs are easy to introduce, as patch is not scoped for an "
                "instance, which can potentially even break class behavior; "
                "assertions on calls are ambiguous (for every instance or one "
                "global assertion?).")
        new_value = staticmethod(new_value)

    if _is_instance_method(target, method):
        unpatcher = _mock_instance_attribute(target, method, new_value)
    else:
        setattr(target, method, new_value)

        def unpatcher():
            if restore_value:
                setattr(target, method, restore_value)
            else:
                delattr(target, method)

    return original_callable, unpatcher
コード例 #2
0
ファイル: mock_callable.py プロジェクト: deathowl/TestSlide
    def __init__(
        self,
        target: Any,
        method: str,
        caller_frame_info: Traceback,
        callable_mock: Union[Callable[[Type[object]], Any], _CallableMock,
                             None] = None,
        original_callable: Optional[Callable] = None,
        allow_private: bool = False,
        type_validation: Optional[bool] = None,
    ) -> None:
        if not _is_setup():
            raise RuntimeError(
                "TestSlide was not correctly setup before usage!\n"
                "This error happens when mock_callable, mock_async_callable or "
                "mock_constructor are attempted to be used without correct "
                "test framework integration, meaning unpatching and "
                "assertions will not work as expected.\n"
                "A common scenario for this is when testslide.TestCase is "
                "subclassed with setUp() overridden but super().setUp() was not "
                "called.")
        self._original_target = target
        self._method = method
        self._runner: Optional[_BaseRunner] = None
        self._next_runner_accepted_args: Any = None
        self.allow_private = allow_private
        self.type_validation = type_validation
        self.caller_frame_info = caller_frame_info
        self._allow_coro = False
        self._accept_partial_call = False
        if isinstance(target, str):
            self._target = testslide._importer(target)
        else:
            self._target = target

        target_method_id = (id(self._target), method)

        if target_method_id not in self.CALLABLE_MOCKS:
            if not callable_mock:
                patch = True
                callable_mock = self._get_callable_mock()
            else:
                patch = False
            self.CALLABLE_MOCKS[target_method_id] = callable_mock
            self._callable_mock = callable_mock

            def del_callable_mock() -> None:
                del self.CALLABLE_MOCKS[target_method_id]

            _unpatchers.append(del_callable_mock)

            if patch:
                original_callable, unpatcher = self._patch(callable_mock)
                _unpatchers.append(unpatcher)
            self._original_callable = original_callable
            callable_mock.original_callable = original_callable  # type: ignore
        else:
            self._callable_mock = self.CALLABLE_MOCKS[target_method_id]
            self._original_callable = self._callable_mock.original_callable  # type: ignore
コード例 #3
0
ファイル: mock_callable.py プロジェクト: xiaoxianma/TestSlide
    def __init__(
        self,
        target: Any,
        method: str,
        caller_frame_info: Traceback,
        callable_mock: Union[Callable[[Type[object]], Any], _CallableMock,
                             None] = None,
        original_callable: Optional[Callable] = None,
        allow_private: bool = False,
        type_validation: bool = True,
    ) -> None:
        if not _is_setup():
            raise RuntimeError(
                "mock_callable() was not setup correctly before usage. "
                "Please refer to the documentation at http://testslide.readthedocs.io/ "
                "to learn how to properly do that.")
        self._original_target = target
        self._method = method
        self._runner: Optional[_BaseRunner] = None
        self._next_runner_accepted_args: Any = None
        self.allow_private = allow_private
        self.type_validation = type_validation
        self.caller_frame_info = caller_frame_info
        self._allow_coro = False
        if isinstance(target, str):
            self._target = testslide._importer(target)
        else:
            self._target = target

        target_method_id = (id(self._target), method)

        if target_method_id not in self.CALLABLE_MOCKS:
            if not callable_mock:
                patch = True
                callable_mock = self._get_callable_mock()
            else:
                patch = False
            self.CALLABLE_MOCKS[target_method_id] = callable_mock
            self._callable_mock = callable_mock

            def del_callable_mock() -> None:
                del self.CALLABLE_MOCKS[target_method_id]

            _unpatchers.append(del_callable_mock)

            if patch:
                original_callable, unpatcher = self._patch(callable_mock)
                _unpatchers.append(unpatcher)
            self._original_callable = original_callable
            callable_mock.original_callable = original_callable  # type: ignore
        else:
            self._callable_mock = self.CALLABLE_MOCKS[target_method_id]
            self._original_callable = self._callable_mock.original_callable  # type: ignore
コード例 #4
0
    def __init__(
        self,
        target,
        method,
        caller_frame_info,
        callable_mock=None,
        original_callable=None,
        allow_private=False,
        type_validation=True,
    ):
        if not _is_setup():
            raise RuntimeError(
                "mock_callable() was not setup correctly before usage. "
                "Please refer to the documentation at http://testslide.readthedocs.io/ "
                "to learn how to properly do that."
            )
        self._original_target = target
        self._method = method
        self._runner = None
        self._next_runner_accepted_args = None
        self.allow_private = allow_private
        self.type_validation = type_validation
        self.caller_frame_info = caller_frame_info
        if isinstance(target, str):
            self._target = testslide._importer(target)
        else:
            self._target = target

        target_method_id = (id(self._target), method)

        if target_method_id not in self.CALLABLE_MOCKS:
            if not callable_mock:
                patch = True
                callable_mock = self._get_callable_mock()
            else:
                patch = False
            self.CALLABLE_MOCKS[target_method_id] = callable_mock
            self._callable_mock = callable_mock

            def del_callable_mock():
                del self.CALLABLE_MOCKS[target_method_id]

            _unpatchers.append(del_callable_mock)

            if patch:
                original_callable, unpatcher = self._patch(callable_mock)
                _unpatchers.append(unpatcher)
            self._original_callable = original_callable
            callable_mock.original_callable = original_callable
        else:
            self._callable_mock = self.CALLABLE_MOCKS[target_method_id]
            self._original_callable = self._callable_mock.original_callable
コード例 #5
0
def patch_attribute(target, attribute, new_value, allow_private=False):
    """
    Patch target's attribute with new_value. The target can be any Python
    object, such as modules, classes or instances; attribute is a string with
    the attribute name and new_value.. is the value to be patched.

    patch_attribute() has special mechanics so it "just works" for all cases.

    For example, patching a @property at an instance requires changes in the
    class, which may affect other instances. patch_attribute() takes care of
    what's needed, so only the target instance is affected.
    """
    if isinstance(target, str):
        target = testslide._importer(target)

    if isinstance(target, testslide.StrictMock):
        template_class = target._template
        if template_class:
            value = getattr(template_class, attribute)
            if not isinstance(value, type) and callable(value):
                raise ValueError(
                    "Attribute can not be callable!\n"
                    "You can either use mock_callable() / mock_async_callable() instead."
                )

        def sm_hasattr(obj, name):
            try:
                return hasattr(obj, name)
            except UndefinedAttribute:
                return False

        if sm_hasattr(target, attribute):
            restore = True
            restore_value = getattr(target, attribute)
        else:
            restore = False
            restore_value = None
    else:
        restore = True
        restore_value = getattr(target, attribute)
        if isinstance(restore_value, type):
            raise ValueError("Attribute can not be a class!\n"
                             "You can use mock_constructor() instead.")
        if callable(restore_value):
            raise ValueError(
                "Attribute can not be callable!\n"
                "You can either use mock_callable() / mock_async_callable() instead."
            )
    _bail_if_private(attribute, allow_private)
    unpatcher = _patch(target, attribute, new_value, restore, restore_value)
    _unpatchers.append(unpatcher)
コード例 #6
0
def mock_constructor(target, class_name):
    if not _is_string(class_name):
        raise ValueError(
            "Second argument must be a string with the name of the class.")
    if _is_string(target):
        target = testslide._importer(target)

    mocked_class_id = (id(target), class_name)

    if mocked_class_id in _mocked_classes:
        original_class, mocked_class = _mocked_classes[mocked_class_id]
        if not getattr(target, class_name) is mocked_class:
            raise AssertionError(
                "The class {} at {} was changed after mock_constructor() mocked "
                "it!".format(class_name, target))
        callable_mock = getattr(mocked_class, "__new__")
    else:

        original_class = getattr(target, class_name)
        if not inspect.isclass(original_class):
            raise ValueError("Target must be a class.")

        def unpatcher():
            setattr(target, class_name, original_class)
            del _mocked_classes[mocked_class_id]

        _unpatchers.append(unpatcher)

        callable_mock = _CallableMock(original_class, "__new__")

        mocked_class = type(
            str(original_class.__name__ + "Mock"),
            (original_class, ),
            {"__new__": callable_mock},
        )
        setattr(target, class_name, mocked_class)
        _mocked_classes[mocked_class_id] = (original_class, mocked_class)

    def original_callable(_, *args, **kwargs):
        return original_class(*args, **kwargs)

    return _MockCallableDSL(
        mocked_class,
        "__new__",
        callable_mock=callable_mock,
        original_callable=original_callable,
        prepend_first_arg=ANY,
    )
コード例 #7
0
    def __init__(
        self,
        target,
        method,
        callable_mock=None,
        original_callable=None,
        prepend_first_arg=None,
    ):
        self._original_target = target
        self._method = method
        self._runner = None
        self._next_runner_accepted_args = None
        self.prepend_first_arg = prepend_first_arg

        if isinstance(target, six.string_types):
            self._target = testslide._importer(target)
        else:
            self._target = target

        target_method_id = (id(self._target), method)

        if target_method_id not in self.CALLABLE_MOCKS:
            if not callable_mock:
                patch = True
                callable_mock = _CallableMock(self._original_target,
                                              self._method)
            else:
                patch = False
            self.CALLABLE_MOCKS[target_method_id] = callable_mock
            self._callable_mock = callable_mock

            def del_callable_mock():
                del self.CALLABLE_MOCKS[target_method_id]

            _unpatchers.append(del_callable_mock)

            if patch:
                original_callable, unpatcher = _patch(target, method,
                                                      callable_mock)
                _unpatchers.append(unpatcher)
            self._original_callable = original_callable
            callable_mock.original_callable = original_callable
        else:
            self._callable_mock = self.CALLABLE_MOCKS[target_method_id]
            self._original_callable = self._callable_mock.original_callable
コード例 #8
0
def _patch(target, method, new_value):
    if isinstance(target, str):
        target = testslide._importer(target)

    if isinstance(target, StrictMock):
        original_callable = None
    else:
        original_callable = getattr(target, method)
        if not callable(original_callable):
            raise ValueError(
                "mock_callable() can only be used with callable attributes and {} is not."
                .format(repr(original_callable)))
        if inspect.isclass(original_callable):
            raise ValueError(
                "mock_callable() can not be used with with classes: {}. Perhaps you want to use mock_constructor() instead."
                .format(repr(original_callable)))

    if not isinstance(target, StrictMock):
        new_value = _add_signature_validation(new_value, target, method)
    restore_value = target.__dict__.get(method, None)

    if inspect.isclass(target):
        if _is_instance_method(target, method):
            raise ValueError(
                "Patching an instance method at the class is not supported: "
                "bugs are easy to introduce, as patch is not scoped for an "
                "instance, which can potentially even break class behavior; "
                "assertions on calls are ambiguous (for every instance or one "
                "global assertion?).")
        new_value = staticmethod(new_value)

    if _is_instance_method(target, method):
        unpatcher = _mock_instance_attribute(target, method, new_value)
    else:
        setattr(target, method, new_value)

        def unpatcher():
            if restore_value:
                setattr(target, method, restore_value)
            else:
                delattr(target, method)

    return original_callable, unpatcher
コード例 #9
0
def mock_constructor(target, class_name, allow_private=False):
    if not isinstance(class_name, str):
        raise ValueError(
            "Second argument must be a string with the name of the class.")
    _bail_if_private(class_name, allow_private)
    if isinstance(target, str):
        target = testslide._importer(target)
    target_class_id = (id(target), class_name)

    if target_class_id in _mocked_target_classes:
        original_class, mocked_class = _mocked_target_classes[target_class_id]
        if not getattr(target, class_name) is mocked_class:
            raise AssertionError(
                "The class {} at {} was changed after mock_constructor() mocked "
                "it!".format(class_name, target))
        callable_mock = mocked_class.__new__
    else:
        original_class = getattr(target, class_name)

        if "__new__" in original_class.__dict__:
            raise NotImplementedError(
                "Usage with classes that define __new__() is currently not supported."
            )

        gc.collect()
        instances = [
            obj for obj in gc.get_referrers(original_class)
            if type(obj) is original_class
        ]
        if instances:
            raise RuntimeError(
                "mock_constructor() can not be used after instances of {} were created: {}"
                .format(class_name, instances))

        if not inspect.isclass(original_class):
            raise ValueError("Target must be a class.")
        elif not issubclass(original_class, object):
            raise ValueError("Old style classes are not supported.")
        callable_mock = _CallableMock(original_class, "__new__")
        mocked_class = _patch_and_return_mocked_class(target, class_name,
                                                      target_class_id,
                                                      original_class,
                                                      callable_mock)

    def original_callable(cls, *args, **kwargs):
        global _init_args_from_original_callable, _init_kwargs_from_original_callable
        assert cls is mocked_class
        # Python unconditionally calls __init__ with the same arguments as
        # __new__ once it is invoked. We save the correct arguments here,
        # so that __init__ can use them when invoked for the first time.
        _init_args_from_original_callable = args
        _init_kwargs_from_original_callable = kwargs
        return object.__new__(cls)

    return _MockConstructorDSL(
        target=mocked_class,
        method="__new__",
        cls=mocked_class,
        callable_mock=callable_mock,
        original_callable=original_callable,
    )
コード例 #10
0
def patch_attribute(
    target: Any,
    attribute: str,
    new_value: Any,
    allow_private: bool = False,
    type_validation: bool = True,
) -> None:
    """
    Patch target's attribute with new_value. The target can be any Python
    object, such as modules, classes or instances; attribute is a string with
    the attribute name and new_value.. is the value to be patched.

    patch_attribute() has special mechanics so it "just works" for all cases.

    For example, patching a @property at an instance requires changes in the
    class, which may affect other instances. patch_attribute() takes care of
    what's needed, so only the target instance is affected.
    """
    _bail_if_private(attribute, allow_private)

    if isinstance(target, str):
        target = testslide._importer(target)

    key = (id(target), attribute)

    if isinstance(target, testslide.StrictMock):
        if not type_validation:
            target.__dict__["_attributes_to_skip_type_validation"].append(
                attribute)
        template_class = target._template
        if template_class and attribute not in target._runtime_attrs:
            value = getattr(template_class, attribute)
            if not isinstance(value, type) and callable(value):
                raise ValueError(
                    "Attribute can not be callable!\n"
                    "You can either use mock_callable() / mock_async_callable() instead."
                )

        def strict_mock_hasattr(obj: object, name: str) -> bool:
            try:
                return hasattr(obj, name)
            except UndefinedAttribute:
                return False

        if strict_mock_hasattr(target, attribute) and key not in _unpatchers:
            restore = True
            restore_value = getattr(target, attribute)
        else:
            restore = False
            restore_value = None
        skip_unpatcher = False
    else:
        if key in _unpatchers:
            restore = False
            restore_value = _restore_values[key]
            skip_unpatcher = True
        else:
            restore = True
            restore_value = getattr(target, attribute)
            skip_unpatcher = False
        if isinstance(restore_value, type):
            raise ValueError("Attribute can not be a class!\n"
                             "You can use mock_constructor() instead.")
        if callable(restore_value):
            raise ValueError(
                "Attribute can not be callable!\n"
                "You can either use mock_callable() / mock_async_callable() instead."
            )
        if type_validation:
            _validate_argument_type(type(restore_value), attribute, new_value)

    if restore:
        _restore_values[key] = restore_value

    unpatcher = _patch(target, attribute, new_value, restore, restore_value)

    if not skip_unpatcher:
        _unpatchers[key] = unpatcher
コード例 #11
0
def mock_constructor(
    target: str,
    class_name: str,
    allow_private: bool = False,
    type_validation: bool = True,
) -> _MockConstructorDSL:
    if not isinstance(class_name, str):
        raise ValueError("Second argument must be a string with the name of the class.")
    _bail_if_private(class_name, allow_private)
    if isinstance(target, str):
        target = testslide._importer(target)
    target_class_id = (id(target), class_name)

    if target_class_id in _mocked_target_classes:
        original_class, mocked_class = _mocked_target_classes[target_class_id]
        if not getattr(target, class_name) is mocked_class:
            raise AssertionError(
                "The class {} at {} was changed after mock_constructor() mocked "
                "it!".format(class_name, target)
            )
        callable_mock = mocked_class.__new__
    else:
        original_class = getattr(target, class_name)

        if "__new__" in original_class.__dict__:
            raise NotImplementedError(
                "Usage with classes that define __new__() is currently not supported."
            )

        gc.collect()
        instances = [
            obj
            for obj in gc.get_referrers(original_class)
            if type(obj) is original_class
        ]
        if instances:
            raise RuntimeError(
                "mock_constructor() can not be used after instances of {} were created: {}".format(
                    class_name, instances
                )
            )

        if not inspect.isclass(original_class):
            raise ValueError("Target must be a class.")
        elif not issubclass(original_class, object):
            raise ValueError("Old style classes are not supported.")

        caller_frame = inspect.currentframe().f_back  # type: ignore
        # loading the context ends up reading files from disk and that might block
        # the event loop, so we don't do it.
        caller_frame_info = inspect.getframeinfo(caller_frame, context=0)  # type: ignore
        callable_mock = _CallableMock(original_class, "__new__", caller_frame_info)
        mocked_class = _patch_and_return_mocked_class(
            target,
            class_name,
            target_class_id,
            original_class,
            callable_mock,
            type_validation,
        )

    def original_callable(cls: type, *args: Any, **kwargs: Any) -> Any:
        global _init_args_from_original_callable, _init_kwargs_from_original_callable
        assert cls is mocked_class
        # Python unconditionally calls __init__ with the same arguments as
        # __new__ once it is invoked. We save the correct arguments here,
        # so that __init__ can use them when invoked for the first time.
        _init_args_from_original_callable = args
        _init_kwargs_from_original_callable = kwargs
        return object.__new__(cls)

    return _MockConstructorDSL(
        target=mocked_class,
        method="__new__",
        cls=mocked_class,
        callable_mock=callable_mock,
        original_callable=original_callable,
    )