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