From d1118fbc2a6044121625a9bc4a9a46a308b08f01 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:00:48 +0000 Subject: [PATCH 01/70] update list of supported versions. --- .circleci/config.yml | 32 +++++++++----------------------- README.rst | 2 +- docs/index.txt | 16 ++-------------- setup.cfg | 11 ++--------- tox.ini | 2 +- 5 files changed, 15 insertions(+), 48 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8416914f..d3c62843 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,15 +24,6 @@ jobs: common: &common jobs: - - python/pip-run-tests: - name: python27 - image: circleci/python:2.7 - - python/pip-run-tests: - name: python34 - image: circleci/python:3.4 - - python/pip-run-tests: - name: python35 - image: circleci/python:3.5 - python/pip-run-tests: name: python36 image: circleci/python:3.6 @@ -40,8 +31,8 @@ common: &common name: python37 image: circleci/python:3.7 - python/pip-run-tests: - name: pypy27 - image: pypy:2.7 + name: python38 + image: circleci/python:3.8 - python/pip-run-tests: name: pypy36 image: pypy:3.6 @@ -49,12 +40,9 @@ common: &common - python/coverage: name: coverage requires: - - python27 - - python34 - - python35 - python36 - python37 - - pypy27 + - python38 - pypy36 - python/pip-docs: @@ -72,7 +60,7 @@ common: &common - check-package: name: check-package-python27 - image: circleci/python:2.7 + image: circleci/python:3.7 requires: - package @@ -83,16 +71,15 @@ common: &common - package - check-package: - name: check-package-pypy27 - image: pypy:2.7 - python: pypy + name: check-package-python38 + image: circleci/python:3.8 requires: - package - check-package: name: check-package-pypy36 - image: pypy:3.6 - python: pypy3 + image: pypy:2.7 + python: pypy requires: - package @@ -100,9 +87,8 @@ common: &common name: release config: .carthorse.yml requires: - - check-package-python27 - check-package-python37 - - check-package-pypy27 + - check-package-python38 - check-package-pypy36 workflows: diff --git a/README.rst b/README.rst index b4f3163c..279f2dc0 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ mock is now part of the Python standard library, available as `unittest.mock onwards. This package contains a rolling backport of the standard library mock code -compatible with Python 2.7 and 3.4 and up. +compatible with Python 3.6 and up. Please see the standard library documentation for more details. diff --git a/docs/index.txt b/docs/index.txt index 27008a0d..f03f838d 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -25,6 +25,8 @@ Python Version Compatibility * Version 2.0.0 is the last version offering official Jython support. +* version 3.0.5 is the last version supporting Python 3.5 and lower. + .. index:: installing .. _installing: @@ -77,9 +79,6 @@ Checkout from git (see :ref:`installing`) and submit pull requests. Committers can just push as desired: since all semantic development takes place in cPython, the backport process is as lightweight as we can make it. -mock is CI tested using Travis-CI on Python versions 2.7, 3.4, -3.5, 3.6, pypy, pypy3. - If you end up fixing anything backport-specific, please add an entry to the top of ``CHANGELOG.rst`` so it shows up in the next release notes. @@ -104,17 +103,6 @@ non-bugfix changes, patch on bugfix only changes. Backporting rules ----------------- -- ``isinstance`` checks in cPython to ``type`` need to check ``ClassTypes``. - Code calling ``obj.isidentifier`` needs to change to ``_isidentifier(obj)``. - -- f-strings need to be rewritten using some other string substitution. - -- ``assertRaisesRegex`` needs to be ``assertRaisesRegexp`` for Python 2. - -- If test code won't compile on a particular version of Python, move it to - a matching ``_py{version}.py`` file. If ``{version}`` isn't 3, adjust - ``conftest.py``. - - If code such as this causes coverage checking to drop below 100%: .. code-block:: python diff --git a/setup.cfg b/setup.cfg index 42ba277c..647943ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,13 +12,9 @@ classifiers = License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries @@ -28,10 +24,7 @@ keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles [options] -install_requires = - six - funcsigs>=1;python_version<"3.3" -python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +python_requires=>=3.6 packages = mock [options.extras_require] diff --git a/tox.ini b/tox.ini index 90ca455d..14eb4f43 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py34,py35,py36,py37,docs +envlist = py36,py37,py38,docs [testenv] commands = From 4680a9654e8485dd5b8ed809af4ff0c62df3b493 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:02:15 +0000 Subject: [PATCH 02/70] move __version__ to __init__.py so we don't have to modify the mock.py from cpython. --- mock/__init__.py | 7 ++++++- mock/mock.py | 4 ---- release.py | 2 +- setup.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mock/__init__.py b/mock/__init__.py index 8f383f0e..0dfb1653 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -1,4 +1,9 @@ from __future__ import absolute_import import mock.mock as _mock from mock.mock import * -__all__ = _mock.__all__ + +__version__ = '4.0.0b1' +version_info = tuple(int(p) for p in __version__.split('.')) + + +__all__ = ('__version__', 'version_info') + _mock.__all__ diff --git a/mock/mock.py b/mock/mock.py index 2d39253e..79da3e59 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -34,8 +34,6 @@ from __future__ import absolute_import __all__ = ( - '__version__', - 'version_info', 'Mock', 'MagicMock', 'patch', @@ -69,8 +67,6 @@ import six from six import wraps -__version__ = '3.0.5' -version_info = tuple(int(p) for p in __version__.split('.')) import mock diff --git a/release.py b/release.py index 2556d509..13ab6a5f 100644 --- a/release.py +++ b/release.py @@ -50,7 +50,7 @@ def news_to_changelog(version): def update_version(new_version): - path = join('mock', 'mock.py') + path = join('mock', '__init__.py') with open(path) as source: text = source.read() diff --git a/setup.py b/setup.py index d47345f0..6f5ff41d 100755 --- a/setup.py +++ b/setup.py @@ -5,6 +5,6 @@ setuptools.setup( version=re.search("__version__ = '([^']+)'", - open(join('mock', 'mock.py')).read()).group(1), + open(join('mock', '__init__.py')).read()).group(1), long_description=open('README.rst').read(), ) From f8d22bc2a2cf4c41e51c7f99bb063195b8111501 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:02:35 +0000 Subject: [PATCH 03/70] fix release script to blank out lower version segments. --- release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.py b/release.py index 13ab6a5f..7ef4f16b 100644 --- a/release.py +++ b/release.py @@ -12,7 +12,7 @@ def incremented_version(version_info, type_): type_index = VERSION_TYPES.index(type_) - version_info = tuple(e+(1 if i==type_index else 0) + version_info = tuple(0 if i>type_index else (e+(1 if i==type_index else 0)) for i, e in enumerate(version_info)) return '.'.join(str(p) for p in version_info) From 4195207c56eaa0f1707d1e64acc48967554f115f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:11:28 +0000 Subject: [PATCH 04/70] support pre-releases in version_info --- mock/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mock/__init__.py b/mock/__init__.py index 0dfb1653..1f29771d 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -1,9 +1,13 @@ from __future__ import absolute_import + +import re + import mock.mock as _mock from mock.mock import * __version__ = '4.0.0b1' -version_info = tuple(int(p) for p in __version__.split('.')) +version_info = tuple(int(p) for p in + re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) __all__ = ('__version__', 'version_info') + _mock.__all__ From ce5b6961d0b38b9f26e6bffabf46fadfc55696ba Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:19:08 +0000 Subject: [PATCH 05/70] new cut from cpython as at 4a686504eb2bbf69adf78077458508a7ba131667 --- lastsync.txt | 2 +- mock/mock.py | 847 +++++++++++++++++++++------------ mock/tests/__init__.py | 20 +- mock/tests/conftest.py | 6 - mock/tests/support.py | 30 -- mock/tests/testasync.py | 626 ++++++++++++++++++++++++ mock/tests/testcallable.py | 12 +- mock/tests/testhelpers.py | 171 ++----- mock/tests/testhelpers_py3.py | 23 - mock/tests/testmagicmethods.py | 168 ++----- mock/tests/testmock.py | 182 +++---- mock/tests/testpatch.py | 133 ++---- mock/tests/testsealable.py | 2 +- mock/tests/testsentinel.py | 7 +- mock/tests/testsupport.py | 14 - mock/tests/testwith.py | 34 +- 16 files changed, 1424 insertions(+), 853 deletions(-) delete mode 100644 mock/tests/conftest.py create mode 100644 mock/tests/testasync.py delete mode 100644 mock/tests/testhelpers_py3.py delete mode 100644 mock/tests/testsupport.py diff --git a/lastsync.txt b/lastsync.txt index 1f18392f..ff52fa89 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -11a8832c98b3db78727312154dd1d3ba76d639ec +4a686504eb2bbf69adf78077458508a7ba131667 diff --git a/mock/mock.py b/mock/mock.py index 79da3e59..be961947 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1,37 +1,8 @@ # mock.py # Test tools for mocking and patching. -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# -# http://www.voidspace.org.uk/python/mock/ -# -# Copyright (c) 2007-2013, Michael Foord & the mock team -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import absolute_import +# Maintained by Michael Foord +# Backport for other versions of Python available from +# https://pypi.org/project/mock __all__ = ( 'Mock', @@ -42,8 +13,8 @@ 'ANY', 'call', 'create_autospec', + 'AsyncMock', 'FILTER_DIR', - 'CallableMixin', 'NonCallableMock', 'NonCallableMagicMock', 'mock_open', @@ -52,77 +23,42 @@ ) -from functools import partial +__version__ = '1.0' + +import asyncio +import contextlib import io import inspect import pprint import sys -try: - import builtins -except ImportError: - import __builtin__ as builtins -from types import ModuleType, MethodType +import builtins +from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr - -import six -from six import wraps - - -import mock - -try: - inspectsignature = inspect.signature -except AttributeError: - import funcsigs - inspectsignature = funcsigs.signature - - -# TODO: use six. -try: - unicode -except NameError: - # Python 3 - basestring = unicode = str - -try: - long -except NameError: - # Python 3 - long = int - -if six.PY2: - # Python 2's next() can't handle a non-iterator with a __next__ method. - _next = next - def next(obj, _next=_next): - if getattr(obj, '__next__', None): - return obj.__next__() - return _next(obj) - - del _next +from functools import wraps, partial _builtins = {name for name in dir(builtins) if not name.startswith('_')} -try: - _isidentifier = str.isidentifier -except AttributeError: - # Python 2.X - import keyword - import re - regex = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) - def _isidentifier(string): - if string in keyword.kwlist: - return False - return regex.match(string) - - -# NOTE: This FILTER_DIR is not used. The binding in mock.FILTER_DIR is. FILTER_DIR = True -# Workaround for Python issue #12370 +# Workaround for issue #12370 # Without this, the __class__ properties wouldn't be set correctly _safe_super = super +def _is_async_obj(obj): + if getattr(obj, '__code__', None): + return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) + else: + return False + + +def _is_async_func(func): + if getattr(func, '__code__', None): + return asyncio.iscoroutinefunction(func) + else: + return False + + def _is_instance_mock(obj): # can't use isinstance on Mock objects because they override __class__ # The base class for all mocks is NonCallableMock @@ -132,34 +68,19 @@ def _is_instance_mock(obj): def _is_exception(obj): return ( isinstance(obj, BaseException) or - isinstance(obj, ClassTypes) and issubclass(obj, BaseException) + isinstance(obj, type) and issubclass(obj, BaseException) ) -class _slotted(object): - __slots__ = ['a'] - - -# Do not use this tuple. It was never documented as a public API. -# It will be removed. It has no obvious signs of users on github. -DescriptorTypes = ( - type(_slotted.a), - property, -) - - def _get_signature_object(func, as_instance, eat_self): """ Given an arbitrary, possibly callable object, try to create a suitable signature object. Return a (reduced func, signature) tuple, or None. """ - if isinstance(func, ClassTypes) and not as_instance: + if isinstance(func, type) and not as_instance: # If it's a type and should be modelled as a type, use __init__. - try: - func = func.__init__ - except AttributeError: - return None + func = func.__init__ # Skip the `self` argument in __init__ eat_self = True elif not isinstance(func, FunctionTypes): @@ -173,9 +94,8 @@ def _get_signature_object(func, as_instance, eat_self): sig_func = partial(func, None) else: sig_func = func - try: - return func, inspectsignature(sig_func) + return func, inspect.signature(sig_func) except ValueError: # Certain callable types are not supported by inspect.signature() return None @@ -204,15 +124,10 @@ def _copy_func_details(func, funcopy): setattr(funcopy, attribute, getattr(func, attribute)) except AttributeError: pass - if six.PY2: - try: - funcopy.func_defaults = func.func_defaults - except AttributeError: - pass def _callable(obj): - if isinstance(obj, ClassTypes): + if isinstance(obj, type): return True if isinstance(obj, (staticmethod, classmethod, MethodType)): return _callable(obj.__func__) @@ -230,25 +145,15 @@ def _is_list(obj): def _instance_callable(obj): """Given an object, return True if the object is callable. For classes, return True if instances would be callable.""" - if not isinstance(obj, ClassTypes): + if not isinstance(obj, type): # already an instance return getattr(obj, '__call__', None) is not None - if six.PY3: - # *could* be broken by a class overriding __mro__ or __dict__ via - # a metaclass - for base in (obj,) + obj.__mro__: - if base.__dict__.get('__call__') is not None: - return True - else: - klass = obj - # uses __bases__ instead of __mro__ so that we work with old style classes - if klass.__dict__.get('__call__') is not None: + # *could* be broken by a class overriding __mro__ or __dict__ via + # a metaclass + for base in (obj,) + obj.__mro__: + if base.__dict__.get('__call__') is not None: return True - - for base in klass.__bases__: - if _instance_callable(base): - return True return False @@ -257,7 +162,7 @@ def _set_signature(mock, original, instance=False): # mock. It still does signature checking by calling a lambda with the same # signature as the original. - skipfirst = isinstance(original, ClassTypes) + skipfirst = isinstance(original, type) result = _get_signature_object(original, instance, skipfirst) if result is None: return mock @@ -267,13 +172,13 @@ def checksig(*args, **kwargs): _copy_func_details(func, checksig) name = original.__name__ - if not _isidentifier(name): + if not name.isidentifier(): name = 'funcopy' context = {'_checksig_': checksig, 'mock': mock} src = """def %s(*args, **kwargs): _checksig_(*args, **kwargs) return mock(*args, **kwargs)""" % name - six.exec_(src, context) + exec (src, context) funcopy = context[name] _setup_func(funcopy, mock, sig) return funcopy @@ -282,14 +187,14 @@ def checksig(*args, **kwargs): def _setup_func(funcopy, mock, sig): funcopy.mock = mock + def assert_called_with(*args, **kwargs): + return mock.assert_called_with(*args, **kwargs) def assert_called(*args, **kwargs): return mock.assert_called(*args, **kwargs) def assert_not_called(*args, **kwargs): return mock.assert_not_called(*args, **kwargs) def assert_called_once(*args, **kwargs): return mock.assert_called_once(*args, **kwargs) - def assert_called_with(*args, **kwargs): - return mock.assert_called_with(*args, **kwargs) def assert_called_once_with(*args, **kwargs): return mock.assert_called_once_with(*args, **kwargs) def assert_has_calls(*args, **kwargs): @@ -328,6 +233,34 @@ def reset_mock(): mock._mock_delegate = funcopy +def _setup_async_mock(mock): + mock._is_coroutine = asyncio.coroutines._is_coroutine + mock.await_count = 0 + mock.await_args = None + mock.await_args_list = _CallList() + mock.awaited = _AwaitEvent(mock) + + # Mock is not configured yet so the attributes are set + # to a function and then the corresponding mock helper function + # is called when the helper is accessed similar to _setup_func. + def wrapper(attr, *args, **kwargs): + return getattr(mock.mock, attr)(*args, **kwargs) + + for attribute in ('assert_awaited', + 'assert_awaited_once', + 'assert_awaited_with', + 'assert_awaited_once_with', + 'assert_any_await', + 'assert_has_awaits', + 'assert_not_awaited'): + + # setattr(mock, attribute, wrapper) causes late binding + # hence attribute will always be the last value in the loop + # Use partial(wrapper, attribute) to ensure the attribute is bound + # correctly. + setattr(mock, attribute, partial(wrapper, attribute)) + + def _is_magic(name): return '__%s__' % name[2:-2] == name @@ -341,11 +274,7 @@ def __repr__(self): return 'sentinel.%s' % self.name def __reduce__(self): - return _unpickle_sentinel, (self.name, ) - - -def _unpickle_sentinel(name): - return getattr(sentinel, name) + return 'sentinel.%s' % self.name class _Sentinel(object): @@ -359,6 +288,9 @@ def __getattr__(self, name): raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) + def __reduce__(self): + return 'sentinel' + sentinel = _Sentinel() @@ -367,15 +299,6 @@ def __getattr__(self, name): _deleted = sentinel.DELETED -class OldStyleClass: - pass -ClassType = type(OldStyleClass) - - -ClassTypes = (type,) -if six.PY2: - ClassTypes = (type, ClassType) - _allowed_names = { 'return_value', '_mock_return_value', 'side_effect', '_mock_side_effect', '_mock_parent', '_mock_new_parent', @@ -476,7 +399,20 @@ def __new__(cls, *args, **kw): # every instance has its own class # so we can create magic methods on the # class without stomping on other mocks - new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) + bases = (cls,) + if not issubclass(cls, AsyncMock): + # Check if spec is an async object or function + sig = inspect.signature(NonCallableMock.__init__) + bound_args = sig.bind_partial(cls, *args, **kw).arguments + spec_arg = [ + arg for arg in bound_args.keys() + if arg.startswith('spec') + ] + if spec_arg: + # what if spec_set is different than spec? + if _is_async_obj(bound_args[spec_arg[0]]): + bases = (AsyncMockMixin, cls,) + new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = object.__new__(new) return instance @@ -552,9 +488,14 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _eat_self=False): _spec_class = None _spec_signature = None + _spec_asyncs = [] + + for attr in dir(spec): + if asyncio.iscoroutinefunction(getattr(spec, attr, None)): + _spec_asyncs.append(attr) if spec is not None and not _is_list(spec): - if isinstance(spec, ClassTypes): + if isinstance(spec, type): _spec_class = spec else: _spec_class = type(spec) @@ -569,7 +510,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, __dict__['_spec_set'] = spec_set __dict__['_spec_signature'] = _spec_signature __dict__['_mock_methods'] = spec - + __dict__['_spec_asyncs'] = _spec_asyncs def __get_return_value(self): ret = self._mock_return_value @@ -631,7 +572,7 @@ def __set_side_effect(self, value): side_effect = property(__get_side_effect, __set_side_effect) - def reset_mock(self, visited=None, return_value=False, side_effect=False): + def reset_mock(self, visited=None,*, return_value=False, side_effect=False): "Restore the mock object to its initial state." if visited is None: visited = [] @@ -684,7 +625,7 @@ def configure_mock(self, **kwargs): def __getattr__(self, name): - if name in ('_mock_methods', '_mock_unsafe'): + if name in {'_mock_methods', '_mock_unsafe'}: raise AttributeError(name) elif self._mock_methods is not None: if name not in self._mock_methods or name in _all_magics: @@ -693,7 +634,8 @@ def __getattr__(self, name): raise AttributeError(name) if not self._mock_unsafe: if name.startswith(('assert', 'assret')): - raise AttributeError(name) + raise AttributeError("Attributes cannot start with 'assert' " + "or 'assret'") result = self._mock_children.get(name) if result is _deleted: @@ -761,7 +703,7 @@ def __repr__(self): if self._spec_set: spec_string = ' spec_set=%r' spec_string = spec_string % self._spec_class.__name__ - return "<{}{}{} id='{}'>".format( + return "<%s%s%s id='%s'>" % ( type(self).__name__, name_string, spec_string, @@ -771,8 +713,7 @@ def __repr__(self): def __dir__(self): """Filter the output of `dir(mock)` to only useful members.""" - if not mock.FILTER_DIR and getattr(object, '__dir__', None): - # object.__dir__ is not in 2.7 + if not FILTER_DIR: return object.__dir__(self) extras = self._mock_methods or [] @@ -782,12 +723,9 @@ def __dir__(self): m_name for m_name, m_value in self._mock_children.items() if m_value is not _deleted] - if mock.FILTER_DIR: - # object.__dir__ is not in 2.7 - from_type = [e for e in from_type if not e.startswith('_')] - from_dict = [e for e in from_dict if not e.startswith('_') or - _is_magic(e)] - + from_type = [e for e in from_type if not e.startswith('_')] + from_dict = [e for e in from_dict if not e.startswith('_') or + _is_magic(e)] return sorted(set(extras + from_type + from_dict + from_child_mocks)) @@ -824,8 +762,8 @@ def __setattr__(self, name, value): self._mock_children[name] = value if self._mock_sealed and not hasattr(self, name): - mock_name = self._extract_mock_name()+'.'+name - raise AttributeError('Cannot set '+mock_name) + mock_name = f'{self._extract_mock_name()}.{name}' + raise AttributeError(f'Cannot set {mock_name}') return object.__setattr__(self, name, value) @@ -853,12 +791,12 @@ def _format_mock_call_signature(self, args, kwargs): return _format_call_signature(name, args, kwargs) - def _format_mock_failure_message(self, args, kwargs): - message = 'expected call not found.\nExpected: %s\nActual: %s' + def _format_mock_failure_message(self, args, kwargs, action='call'): + message = 'expected %s not found.\nExpected: %s\nActual: %s' expected_string = self._format_mock_call_signature(args, kwargs) call_args = self.call_args actual_string = self._format_mock_call_signature(*call_args) - return message % (expected_string, actual_string) + return message % (action, expected_string, actual_string) def _call_matcher(self, _call): @@ -878,8 +816,7 @@ def _call_matcher(self, _call): try: return name, sig.bind(*args, **kwargs) except TypeError as e: - e.__traceback__ = None - return e + return e.with_traceback(None) else: return _call @@ -924,20 +861,17 @@ def assert_called_with(_mock_self, *args, **kwargs): expected = self._format_mock_call_signature(args, kwargs) actual = 'not called.' error_message = ('expected call not found.\nExpected: %s\nActual: %s' - % (expected, actual)) + % (expected, actual)) raise AssertionError(error_message) - def _error_message(cause): + def _error_message(): msg = self._format_mock_failure_message(args, kwargs) - if six.PY2 and cause is not None: - # Tack on some diagnostics for Python without __cause__ - msg = '{}\n{}'.format(msg, str(cause)) return msg expected = self._call_matcher((args, kwargs)) actual = self._call_matcher(self.call_args) if expected != actual: cause = expected if isinstance(expected, Exception) else None - six.raise_from(AssertionError(_error_message(cause)), cause) + raise AssertionError(_error_message()) from cause def assert_called_once_with(_mock_self, *args, **kwargs): @@ -968,10 +902,10 @@ def assert_has_calls(self, calls, any_order=False): all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls) if not any_order: if expected not in all_calls: - six.raise_from(AssertionError( + raise AssertionError( 'Calls not found.\nExpected: %r%s' % (_CallList(calls), self._calls_repr(prefix="Actual")) - ), cause) + ) from cause return all_calls = list(all_calls) @@ -983,11 +917,11 @@ def assert_has_calls(self, calls, any_order=False): except ValueError: not_found.append(kall) if not_found: - six.raise_from(AssertionError( + raise AssertionError( '%r does not contain all of %r in its call list, ' 'found %r instead' % (self._mock_name or 'mock', tuple(not_found), all_calls) - ), cause) + ) from cause def assert_any_call(self, *args, **kwargs): @@ -1001,9 +935,9 @@ def assert_any_call(self, *args, **kwargs): if expected not in actual: cause = expected if isinstance(expected, Exception) else None expected_string = self._format_mock_call_signature(args, kwargs) - six.raise_from(AssertionError( + raise AssertionError( '%s call not found' % expected_string - ), cause) + ) from cause def _get_child_mock(self, **kw): @@ -1014,7 +948,15 @@ def _get_child_mock(self, **kw): For non-callable mocks the callable variant will be used (rather than any custom subclass).""" + _new_name = kw.get("_new_name") + if _new_name in self.__dict__['_spec_asyncs']: + return AsyncMock(**kw) + _type = type(self) + if issubclass(_type, MagicMock) and _new_name in _async_method_magics: + klass = AsyncMock + if issubclass(_type, AsyncMockMixin): + klass = MagicMock if not issubclass(_type, CallableMixin): if issubclass(_type, NonCallableMagicMock): klass = MagicMock @@ -1041,7 +983,7 @@ def _calls_repr(self, prefix="Calls"): """ if not self.mock_calls: return "" - return "\n"+prefix+": "+safe_repr(self.mock_calls)+"." + return f"\n{prefix}: {safe_repr(self.mock_calls)}." @@ -1060,14 +1002,12 @@ def _try_iter(obj): return obj - class CallableMixin(Base): def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, **kwargs): self.__dict__['_mock_return_value'] = return_value - _safe_super(CallableMixin, self).__init__( spec, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, **kwargs @@ -1182,9 +1122,6 @@ class or instance) that acts as the specification for the mock object. If arguments as the mock, and unless it returns `DEFAULT`, the return value of this function is used as the return value. - Alternatively `side_effect` can be an exception class or instance. In - this case the exception will be raised when the mock is called. - If `side_effect` is an iterable then each call to the mock will return the next value from the iterable. If any of the members of the iterable are exceptions they will be raised instead of returned. @@ -1212,7 +1149,6 @@ class or instance) that acts as the specification for the mock object. If """ - def _dot_lookup(thing, comp, import_path): try: return getattr(thing, comp) @@ -1283,8 +1219,10 @@ def copy(self): def __call__(self, func): - if isinstance(func, ClassTypes): + if isinstance(func, type): return self.decorate_class(func) + if inspect.iscoroutinefunction(func): + return self.decorate_async_callable(func) return self.decorate_callable(func) @@ -1302,41 +1240,68 @@ def decorate_class(self, klass): return klass + @contextlib.contextmanager + def decoration_helper(self, patched, args, keywargs): + extra_args = [] + entered_patchers = [] + patching = None + + exc_info = tuple() + try: + for patching in patched.patchings: + arg = patching.__enter__() + entered_patchers.append(patching) + if patching.attribute_name is not None: + keywargs.update(arg) + elif patching.new is DEFAULT: + extra_args.append(arg) + + args += tuple(extra_args) + yield (args, keywargs) + except: + if (patching not in entered_patchers and + _is_started(patching)): + # the patcher may have been started, but an exception + # raised whilst entering one of its additional_patchers + entered_patchers.append(patching) + # Pass the exception to __exit__ + exc_info = sys.exc_info() + # re-raise the exception + raise + finally: + for patching in reversed(entered_patchers): + patching.__exit__(*exc_info) + + def decorate_callable(self, func): + # NB. Keep the method in sync with decorate_async_callable() if hasattr(func, 'patchings'): func.patchings.append(self) return func @wraps(func) def patched(*args, **keywargs): - extra_args = [] - entered_patchers = [] + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return func(*newargs, **newkeywargs) + + patched.patchings = [self] + return patched - exc_info = tuple() - try: - for patching in patched.patchings: - arg = patching.__enter__() - entered_patchers.append(patching) - if patching.attribute_name is not None: - keywargs.update(arg) - elif patching.new is DEFAULT: - extra_args.append(arg) - - args += tuple(extra_args) - return func(*args, **keywargs) - except: - if (patching not in entered_patchers and - _is_started(patching)): - # the patcher may have been started, but an exception - # raised whilst entering one of its additional_patchers - entered_patchers.append(patching) - # Pass the exception to __exit__ - exc_info = sys.exc_info() - # re-raise the exception - raise - finally: - for patching in reversed(entered_patchers): - patching.__exit__(*exc_info) + + def decorate_async_callable(self, func): + # NB. Keep the method in sync with decorate_callable() + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + @wraps(func) + async def patched(*args, **keywargs): + with self.decoration_helper(patched, + args, + keywargs) as (newargs, newkeywargs): + return await func(*newargs, **newkeywargs) patched.patchings = [self] return patched @@ -1361,7 +1326,7 @@ def get_original(self): if not self.create and original is DEFAULT: raise AttributeError( - "{} does not have the attribute {!r}".format(target, name) + "%s does not have the attribute %r" % (target, name) ) return original, local @@ -1407,11 +1372,13 @@ def __enter__(self): if spec is not None or spec_set is not None: if original is DEFAULT: raise TypeError("Can't use 'spec' with create=True") - if isinstance(original, ClassTypes): + if isinstance(original, type): # If we're patching out a class and there is a spec inherit = True - - Klass = MagicMock + if spec is None and _is_async_obj(original): + Klass = AsyncMock + else: + Klass = MagicMock _kwargs = {} if new_callable is not None: Klass = new_callable @@ -1422,8 +1389,10 @@ def __enter__(self): if _is_list(this_spec): not_callable = '__call__' not in this_spec else: - not_callable = not _callable(this_spec) - if not_callable: + not_callable = not callable(this_spec) + if _is_async_obj(this_spec): + Klass = AsyncMock + elif not_callable: Klass = NonCallableMagicMock if spec is not None: @@ -1592,7 +1561,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` for choosing which methods to wrap. """ - if type(target) in (unicode, str): + if type(target) is str: getter = lambda: _importer(target) else: getter = lambda: target @@ -1734,7 +1703,7 @@ def __init__(self, in_dict, values=(), clear=False, **kwargs): def __call__(self, f): - if isinstance(f, ClassTypes): + if isinstance(f, type): return self.decorate_class(f) @wraps(f) def _inner(*args, **kw): @@ -1761,11 +1730,12 @@ def decorate_class(self, klass): def __enter__(self): """Patch the dict.""" self._patch_dict() + return self.in_dict def _patch_dict(self): values = self.values - if isinstance(self.in_dict, basestring): + if isinstance(self.in_dict, str): self.in_dict = _importer(self.in_dict) in_dict = self.in_dict clear = self.clear @@ -1845,34 +1815,26 @@ def _patch_stopall(): "divmod rdivmod neg pos abs invert " "complex int float index " "round trunc floor ceil " + "bool next " + "fspath " ) numerics = ( - "add sub mul matmul div floordiv mod lshift rshift and xor or pow" + "add sub mul matmul div floordiv mod lshift rshift and xor or pow truediv" ) -if six.PY3: - numerics += ' truediv' inplace = ' '.join('i%s' % n for n in numerics.split()) right = ' '.join('r%s' % n for n in numerics.split()) -extra = '' -if six.PY3: - extra = 'bool next ' - if sys.version_info >= (3, 6): - extra += 'fspath ' -else: - extra = 'unicode long nonzero oct hex truediv rtruediv ' # not including __prepare__, __instancecheck__, __subclasscheck__ # (as they are metaclass methods) # __del__ is not supported at all as it causes problems if it exists _non_defaults = { - '__cmp__', '__getslice__', '__setslice__', '__coerce__', # <3.x '__get__', '__set__', '__delete__', '__reversed__', '__missing__', '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', '__getstate__', '__setstate__', '__getformat__', '__setformat__', '__repr__', '__dir__', '__subclasses__', '__format__', - '__getnewargs_ex__', + '__getnewargs_ex__', '__aenter__', '__aexit__', '__anext__', '__aiter__', } @@ -1886,9 +1848,14 @@ def method(self, *args, **kw): _magics = { '__%s__' % method for method in - ' '.join([magic_methods, numerics, inplace, right, extra]).split() + ' '.join([magic_methods, numerics, inplace, right]).split() } +# Magic methods used for async `with` statements +_async_method_magics = {"__aenter__", "__aexit__", "__anext__"} +# `__aiter__` is a plain function but used with async calls +_async_magics = _async_method_magics | {"__aiter__"} + _all_magics = _magics | _non_defaults _unsupported_magics = { @@ -1902,8 +1869,7 @@ def method(self, *args, **kw): '__hash__': lambda self: object.__hash__(self), '__str__': lambda self: object.__str__(self), '__sizeof__': lambda self: object.__sizeof__(self), - '__unicode__': lambda self: unicode(object.__str__(self)), - '__fspath__': lambda self: type(self).__name__+'/'+self._extract_mock_name()+'/'+str(id(self)), + '__fspath__': lambda self: f"{type(self).__name__}/{self._extract_mock_name()}/{id(self)}", } _return_values = { @@ -1918,11 +1884,8 @@ def method(self, *args, **kw): '__complex__': 1j, '__float__': 1.0, '__bool__': True, - '__nonzero__': True, - '__oct__': '1', - '__hex__': '0x1', - '__long__': long(1), '__index__': 1, + '__aexit__': False, } @@ -1955,10 +1918,19 @@ def __iter__(): return iter(ret_val) return __iter__ +def _get_async_iter(self): + def __aiter__(): + ret_val = self.__aiter__._mock_return_value + if ret_val is DEFAULT: + return _AsyncIterator(iter([])) + return _AsyncIterator(iter(ret_val)) + return __aiter__ + _side_effect_methods = { '__eq__': _get_eq, '__ne__': _get_ne, '__iter__': _get_iter, + '__aiter__': _get_async_iter } @@ -1971,13 +1943,7 @@ def _set_return_value(mock, method, name): return_calulator = _calculate_return_value.get(name) if return_calulator is not None: - try: - return_value = return_calulator(mock) - except AttributeError: - # XXXX why do we return AttributeError here? - # set it as a side_effect instead? - # Answer: it makes magic mocks work on pypy?! - return_value = AttributeError(name) + return_value = return_calulator(mock) method.return_value = return_value return @@ -2029,8 +1995,33 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() +class AsyncMagicMixin: + def __init__(self, *args, **kw): + self._mock_set_async_magics() # make magic work for kwargs in init + _safe_super(AsyncMagicMixin, self).__init__(*args, **kw) + self._mock_set_async_magics() # fix magic broken by upper level init + + def _mock_set_async_magics(self): + these_magics = _async_magics + + if getattr(self, "_mock_methods", None) is not None: + these_magics = _async_magics.intersection(self._mock_methods) + remove_magics = _async_magics - these_magics + + for entry in remove_magics: + if entry in type(self).__dict__: + # remove unneeded magic methods + delattr(self, entry) + + # don't overwrite existing attributes if called a second time + these_magics = these_magics - set(type(self).__dict__) + + _type = type(self) + for entry in these_magics: + setattr(_type, entry, MagicProxy(entry, self)) + -class MagicMock(MagicMixin, Mock): +class MagicMock(MagicMixin, AsyncMagicMixin, Mock): """ MagicMock is a subclass of Mock with default implementations of most of the magic methods. You can use MagicMock without having to @@ -2070,6 +2061,218 @@ def __get__(self, obj, _type=None): return self.create_mock() +class AsyncMockMixin(Base): + awaited = _delegating_property('awaited') + await_count = _delegating_property('await_count') + await_args = _delegating_property('await_args') + await_args_list = _delegating_property('await_args_list') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # asyncio.iscoroutinefunction() checks _is_coroutine property to say if an + # object is a coroutine. Without this check it looks to see if it is a + # function/method, which in this case it is not (since it is an + # AsyncMock). + # It is set through __dict__ because when spec_set is True, this + # attribute is likely undefined. + self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine + self.__dict__['_mock_awaited'] = _AwaitEvent(self) + self.__dict__['_mock_await_count'] = 0 + self.__dict__['_mock_await_args'] = None + self.__dict__['_mock_await_args_list'] = _CallList() + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_COROUTINE + self.__dict__['__code__'] = code_mock + + async def _mock_call(_mock_self, *args, **kwargs): + self = _mock_self + try: + result = super()._mock_call(*args, **kwargs) + except (BaseException, StopIteration) as e: + side_effect = self.side_effect + if side_effect is not None and not callable(side_effect): + raise + return await _raise(e) + + _call = self.call_args + + async def proxy(): + try: + if inspect.isawaitable(result): + return await result + else: + return result + finally: + self.await_count += 1 + self.await_args = _call + self.await_args_list.append(_call) + await self.awaited._notify() + + return await proxy() + + def assert_awaited(_mock_self): + """ + Assert that the mock was awaited at least once. + """ + self = _mock_self + if self.await_count == 0: + msg = f"Expected {self._mock_name or 'mock'} to have been awaited." + raise AssertionError(msg) + + def assert_awaited_once(_mock_self): + """ + Assert that the mock was awaited exactly once. + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def assert_awaited_with(_mock_self, *args, **kwargs): + """ + Assert that the last await was with the specified arguments. + """ + self = _mock_self + if self.await_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'Expected await: {expected}\nNot awaited') + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs, action='await') + return msg + + expected = self._call_matcher((args, kwargs)) + actual = self._call_matcher(self.await_args) + if expected != actual: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + def assert_awaited_once_with(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once and with the specified + arguments. + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + return self.assert_awaited_with(*args, **kwargs) + + def assert_any_await(_mock_self, *args, **kwargs): + """ + Assert the mock has ever been awaited with the specified arguments. + """ + self = _mock_self + expected = self._call_matcher((args, kwargs)) + actual = [self._call_matcher(c) for c in self.await_args_list] + if expected not in actual: + cause = expected if isinstance(expected, Exception) else None + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s await not found' % expected_string + ) from cause + + def assert_has_awaits(_mock_self, calls, any_order=False): + """ + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + """ + self = _mock_self + expected = [self._call_matcher(c) for c in calls] + cause = expected if isinstance(expected, Exception) else None + all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) + if not any_order: + if expected not in all_awaits: + raise AssertionError( + f'Awaits not found.\nExpected: {_CallList(calls)}\n' + f'Actual: {self.await_args_list}' + ) from cause + return + + all_awaits = list(all_awaits) + + not_found = [] + for kall in expected: + try: + all_awaits.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in await list' % (tuple(not_found),) + ) from cause + + def assert_not_awaited(_mock_self): + """ + Assert that the mock was never awaited. + """ + self = _mock_self + if self.await_count != 0: + msg = (f"Expected {self._mock_name or 'mock'} to not have been awaited." + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def reset_mock(self, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.await_count = 0 + self.await_args = None + self.await_args_list = _CallList() + + +class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): + """ + Enhance :class:`Mock` with features allowing to mock + an async function. + + The :class:`AsyncMock` object will behave so the object is + recognized as an async function, and the result of a call is an awaitable: + + >>> mock = AsyncMock() + >>> asyncio.iscoroutinefunction(mock) + True + >>> inspect.isawaitable(mock()) + True + + + The result of ``mock()`` is an async function which will have the outcome + of ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the async function will return the + result of that function, + - if ``side_effect`` is an exception, the async function will raise the + exception, + - if ``side_effect`` is an iterable, the async function will return the + next value of the iterable, however, if the sequence of result is + exhausted, ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the async function will return the + value defined by ``return_value``, hence, by default, the async function + returns a new :class:`AsyncMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is an async function, + the mock async function obtained when the mock object is called will be this + async function itself (and not an async function returning an async + function). + + The test author can also specify a wrapped object with ``wraps``. In this + case, the :class:`Mock` object behavior is the same as with an + :class:`.Mock` object: the wrapped object may have methods + defined as async function functions. + + Based on Martin Richard's asynctest project. + """ + class _ANY(object): "A helper object that compares equal to everything." @@ -2083,8 +2286,6 @@ def __ne__(self, other): def __repr__(self): return '' - __hash__ = None - ANY = _ANY() @@ -2093,15 +2294,8 @@ def _format_call_signature(name, args, kwargs): message = '%s(%%s)' % name formatted_args = '' args_string = ', '.join([repr(arg) for arg in args]) - - def encode_item(item): - if six.PY2 and isinstance(item, unicode): - return item.encode("utf-8") - else: - return item - kwargs_string = ', '.join([ - '{}={!r}'.format(encode_item(key), value) for key, value in sorted(kwargs.items()) + '%s=%r' % (key, value) for key, value in sorted(kwargs.items()) ]) if args_string: formatted_args = args_string @@ -2142,7 +2336,7 @@ def __new__(cls, value=(), name='', parent=None, two=False, name, args, kwargs = value elif _len == 2: first, second = value - if isinstance(first, basestring): + if isinstance(first, str): name = first if isinstance(second, tuple): args = second @@ -2152,7 +2346,7 @@ def __new__(cls, value=(), name='', parent=None, two=False, args, kwargs = first, second elif _len == 1: value, = value - if isinstance(value, basestring): + if isinstance(value, str): name = value elif isinstance(value, tuple): args = value @@ -2200,7 +2394,7 @@ def __eq__(self, other): if isinstance(value, tuple): other_args = value other_kwargs = {} - elif isinstance(value, basestring): + elif isinstance(value, str): other_name = value other_args, other_kwargs = (), {} else: @@ -2209,7 +2403,7 @@ def __eq__(self, other): elif len_other == 2: # could be (name, args) or (name, kwargs) or (args, kwargs) first, second = other - if isinstance(first, basestring): + if isinstance(first, str): other_name = first if isinstance(second, tuple): other_args, other_kwargs = second, {} @@ -2227,10 +2421,8 @@ def __eq__(self, other): return (other_args, other_kwargs) == (self_args, self_kwargs) - def __ne__(self, other): - return not self.__eq__(other) + __ne__ = object.__ne__ - __hash__ = None def __call__(self, *args, **kwargs): if self._mock_name is None: @@ -2243,7 +2435,7 @@ def __call__(self, *args, **kwargs): def __getattr__(self, attr): if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '{}.{}'.format(self._mock_name, attr) + name = '%s.%s' % (self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) @@ -2306,7 +2498,6 @@ def call_list(self): call = _Call(from_kall=False) - def create_autospec(spec, spec_set=False, instance=False, _parent=None, _name=None, **kwargs): """Create a mock object using another object as a spec. Attributes on the @@ -2331,8 +2522,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # interpreted as a list of strings spec = type(spec) - is_type = isinstance(spec, ClassTypes) - + is_type = isinstance(spec, type) + is_async_func = _is_async_func(spec) _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} @@ -2349,6 +2540,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # descriptors don't have a spec # because we don't know what type they return _kwargs = {} + elif is_async_func: + if instance: + raise RuntimeError("Instance can not be True when create_autospec " + "is mocking an async function") + Klass = AsyncMock elif not _callable(spec): Klass = NonCallableMagicMock elif is_type and instance and not _instance_callable(spec): @@ -2368,6 +2564,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # should only happen at the top level because we don't # recurse for functions mock = _set_signature(mock, spec) + if is_async_func: + _setup_async_mock(mock) else: _check_signature(spec, mock, is_type, instance) @@ -2379,12 +2577,6 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _name='()', _parent=mock) for entry in dir(spec): - - # This are __ and so treated as magic on Py3, on Py2 we need to - # explicitly ignore them: - if six.PY2 and (entry.startswith('im_') or entry.startswith('func_')): - continue - if _is_magic(entry): # MagicMock already does the useful magic methods for us continue @@ -2417,9 +2609,13 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, skipfirst = _must_skip(spec, entry, is_type) kwargs['_eat_self'] = skipfirst - new = MagicMock(parent=parent, name=entry, _new_name=entry, - _new_parent=parent, - **kwargs) + if asyncio.iscoroutinefunction(original): + child_klass = AsyncMock + else: + child_klass = MagicMock + new = child_klass(parent=parent, name=entry, _new_name=entry, + _new_parent=parent, + **kwargs) mock._mock_children[entry] = new _check_signature(original, new, skipfirst=skipfirst) @@ -2438,14 +2634,11 @@ def _must_skip(spec, entry, is_type): Return whether we should skip the first argument on spec's `entry` attribute. """ - if not isinstance(spec, ClassTypes): + if not isinstance(spec, type): if entry in getattr(spec, '__dict__', {}): # instance attribute - shouldn't skip return False spec = spec.__class__ - if not hasattr(spec, '__mro__'): - # old style class: can't have descriptors anyway - return is_type for klass in spec.__mro__: result = klass.__dict__.get(entry, DEFAULT) @@ -2523,9 +2716,8 @@ def _read_side_effect(*args, **kwargs): return handle.read.return_value return _state[0].read(*args, **kwargs) - def _readline_side_effect(*args, **kwargs): - for item in _iter_side_effect(): - yield item + def _readline_side_effect(*args, **kwargs): + yield from _iter_side_effect() while True: yield _state[0].readline(*args, **kwargs) @@ -2536,14 +2728,15 @@ def _iter_side_effect(): for line in _state[0]: yield line + def _next_side_effect(): + if handle.readline.return_value is not None: + return handle.readline.return_value + return next(_state[0]) + global file_spec if file_spec is None: - # set on first use - if six.PY3: - import _io - file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) - else: - file_spec = file + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) if mock is None: mock = MagicMock(name='open', spec=open) @@ -2561,6 +2754,7 @@ def _iter_side_effect(): handle.readline.side_effect = _state[1] handle.readlines.side_effect = _readlines_side_effect handle.__iter__.side_effect = _iter_side_effect + handle.__next__.side_effect = _next_side_effect def reset_data(*args, **kwargs): _state[0] = _to_stream(read_data) @@ -2613,3 +2807,60 @@ def seal(mock): continue if m._mock_new_parent is mock: seal(m) + + +async def _raise(exception): + raise exception + + +class _AsyncIterator: + """ + Wraps an iterator in an asynchronous iterator. + """ + def __init__(self, iterator): + self.iterator = iterator + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE + self.__dict__['__code__'] = code_mock + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self.iterator) + except StopIteration: + pass + raise StopAsyncIteration + + +class _AwaitEvent: + def __init__(self, mock): + self._mock = mock + self._condition = None + + async def _notify(self): + condition = self._get_condition() + try: + await condition.acquire() + condition.notify_all() + finally: + condition.release() + + def _get_condition(self): + """ + Creation of condition is delayed, to minimize the chance of using the + wrong loop. + A user may create a mock with _AwaitEvent before selecting the + execution loop. Requiring a user to delay creation is error-prone and + inflexible. Instead, condition is created when user actually starts to + use the mock. + """ + # No synchronization is needed: + # - asyncio is thread unsafe + # - there are no awaits here, method will be executed without + # switching asyncio context. + if self._condition is None: + self._condition = asyncio.Condition() + + return self._condition diff --git a/mock/tests/__init__.py b/mock/tests/__init__.py index 54ddf2ec..87d7ae99 100644 --- a/mock/tests/__init__.py +++ b/mock/tests/__init__.py @@ -1,3 +1,17 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ +import os +import sys +import unittest + + +here = os.path.dirname(__file__) +loader = unittest.defaultTestLoader + +def load_tests(*args): + suite = unittest.TestSuite() + for fn in os.listdir(here): + if fn.startswith("test") and fn.endswith(".py"): + modname = "unittest.test.testmock." + fn[:-3] + __import__(modname) + module = sys.modules[modname] + suite.addTest(loader.loadTestsFromModule(module)) + return suite diff --git a/mock/tests/conftest.py b/mock/tests/conftest.py deleted file mode 100644 index 78831f6f..00000000 --- a/mock/tests/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -import six - - -def pytest_ignore_collect(path): - if 'py3' in path.basename and six.PY2: - return True diff --git a/mock/tests/support.py b/mock/tests/support.py index d57a372b..49986d65 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -1,7 +1,3 @@ -import contextlib -import sys - - target = {'foo': 'FOO'} @@ -18,29 +14,3 @@ def wibble(self): pass class X(object): pass - - -@contextlib.contextmanager -def uncache(*names): - """Uncache a module from sys.modules. - - A basic sanity check is performed to prevent uncaching modules that either - cannot/shouldn't be uncached. - - """ - for name in names: - if name in ('sys', 'marshal', 'imp'): - raise ValueError( - "cannot uncache {0}".format(name)) - try: - del sys.modules[name] - except KeyError: - pass - try: - yield - finally: - for name in names: - try: - del sys.modules[name] - except KeyError: - pass diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py new file mode 100644 index 00000000..fa906e4f --- /dev/null +++ b/mock/tests/testasync.py @@ -0,0 +1,626 @@ +import asyncio +import inspect +import unittest + +from unittest.mock import (call, AsyncMock, patch, MagicMock, create_autospec, + _AwaitEvent) + + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + + +class AsyncClass: + def __init__(self): + pass + async def async_method(self): + pass + def normal_method(self): + pass + +async def async_func(): + pass + +async def async_func_args(a, b, *, c): + pass + +def normal_func(): + pass + +class NormalClass(object): + def a(self): + pass + + +async_foo_name = f'{__name__}.AsyncClass' +normal_foo_name = f'{__name__}.NormalClass' + + +class AsyncPatchDecoratorTest(unittest.TestCase): + def test_is_coroutine_function_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + test_async() + + def test_is_async_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + @patch(f'{async_foo_name}.async_method') + def test_no_parent_attribute(mock_method): + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + test_async() + test_no_parent_attribute() + + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_patch(self): + @patch(f"{__name__}.async_func", AsyncMock()) + async def test_async(): + self.assertIsInstance(async_func, AsyncMock) + + asyncio.run(test_async()) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + +class AsyncPatchCMTest(unittest.TestCase): + def test_is_async_function_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + + test_async() + + def test_is_async_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + test_async() + + def test_is_AsyncMock_cm(self): + def test_async(): + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_async_def_cm(self): + async def test_async(): + with patch(f"{__name__}.async_func", AsyncMock()): + self.assertIsInstance(async_func, AsyncMock) + self.assertTrue(inspect.iscoroutinefunction(async_func)) + + asyncio.run(test_async()) + + +class AsyncMockTest(unittest.TestCase): + def test_iscoroutinefunction_default(self): + mock = AsyncMock() + self.assertTrue(asyncio.iscoroutinefunction(mock)) + + def test_iscoroutinefunction_function(self): + async def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_isawaitable(self): + mock = AsyncMock() + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + self.assertIn('assert_awaited', dir(mock)) + + def test_iscoroutinefunction_normal_function(self): + def foo(): pass + mock = AsyncMock(foo) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + + def test_future_isfuture(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + fut = asyncio.Future() + loop.stop() + loop.close() + mock = AsyncMock(fut) + self.assertIsInstance(mock, asyncio.Future) + + +class AsyncAutospecTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch(async_foo_name, autospec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method.async_method, AsyncMock) + self.assertIsInstance(mock_method, MagicMock) + + @patch(async_foo_name, autospec=True) + def test_normal_method(mock_method): + self.assertIsInstance(mock_method.normal_method, MagicMock) + + test_async() + test_normal_method() + + def test_create_autospec_instance(self): + with self.assertRaises(RuntimeError): + create_autospec(async_func, instance=True) + + def test_create_autospec(self): + spec = create_autospec(async_func_args) + awaitable = spec(1, 2, c=3) + async def main(): + await awaitable + + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + self.assertIsInstance(spec.awaited, _AwaitEvent) + spec.assert_not_awaited() + + asyncio.run(main()) + + self.assertTrue(asyncio.iscoroutinefunction(spec)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertEqual(spec.await_count, 1) + self.assertEqual(spec.await_args, call(1, 2, c=3)) + self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) + spec.assert_awaited_once() + spec.assert_awaited_once_with(1, 2, c=3) + spec.assert_awaited_with(1, 2, c=3) + spec.assert_awaited() + + def test_patch_with_autospec(self): + + async def test_async(): + with patch(f"{__name__}.async_func_args", autospec=True) as mock_method: + awaitable = mock_method(1, 2, c=3) + self.assertIsInstance(mock_method.mock, AsyncMock) + + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.isawaitable(awaitable)) + + # Verify the default values during mock setup + self.assertEqual(mock_method.await_count, 0) + self.assertEqual(mock_method.await_args_list, []) + self.assertIsNone(mock_method.await_args) + self.assertIsInstance(mock_method.awaited, _AwaitEvent) + mock_method.assert_not_awaited() + + await awaitable + + self.assertEqual(mock_method.await_count, 1) + self.assertEqual(mock_method.await_args, call(1, 2, c=3)) + self.assertEqual(mock_method.await_args_list, [call(1, 2, c=3)]) + mock_method.assert_awaited_once() + mock_method.assert_awaited_once_with(1, 2, c=3) + mock_method.assert_awaited_with(1, 2, c=3) + mock_method.assert_awaited() + + mock_method.reset_mock() + self.assertEqual(mock_method.await_count, 0) + self.assertIsNone(mock_method.await_args) + self.assertEqual(mock_method.await_args_list, []) + + asyncio.run(test_async()) + + +class AsyncSpecTest(unittest.TestCase): + def test_spec_as_async_positional_magicmock(self): + mock = MagicMock(async_func) + self.assertIsInstance(mock, MagicMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_as_async_kw_magicmock(self): + mock = MagicMock(spec=async_func) + self.assertIsInstance(mock, MagicMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_as_async_kw_AsyncMock(self): + mock = AsyncMock(spec=async_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_as_async_positional_AsyncMock(self): + mock = AsyncMock(async_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_as_normal_kw_AsyncMock(self): + mock = AsyncMock(spec=normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_as_normal_positional_AsyncMock(self): + mock = AsyncMock(normal_func) + self.assertIsInstance(mock, AsyncMock) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) + + def test_spec_async_mock(self): + @patch.object(AsyncClass, 'async_method', spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_spec_parent_not_async_attribute_is(self): + @patch(async_foo_name, spec=True) + def test_async(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertIsInstance(mock_method.async_method, AsyncMock) + + test_async() + + def test_target_async_spec_not(self): + @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) + def test_async_attribute(mock_method): + self.assertIsInstance(mock_method, MagicMock) + self.assertFalse(inspect.iscoroutine(mock_method)) + self.assertFalse(inspect.isawaitable(mock_method)) + + test_async_attribute() + + def test_target_not_async_spec_is(self): + @patch.object(NormalClass, 'a', spec=async_func) + def test_attribute_not_async_spec_is(mock_async_func): + self.assertIsInstance(mock_async_func, AsyncMock) + test_attribute_not_async_spec_is() + + def test_spec_async_attributes(self): + @patch(normal_foo_name, spec=AsyncClass) + def test_async_attributes_coroutines(MockNormalClass): + self.assertIsInstance(MockNormalClass.async_method, AsyncMock) + self.assertIsInstance(MockNormalClass, MagicMock) + + test_async_attributes_coroutines() + + +class AsyncSpecSetTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method', spec_set=True) + def test_async(async_method): + self.assertIsInstance(async_method, AsyncMock) + + def test_is_async_AsyncMock(self): + mock = AsyncMock(spec_set=AsyncClass.async_method) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertIsInstance(mock, AsyncMock) + + def test_is_child_AsyncMock(self): + mock = MagicMock(spec_set=AsyncClass) + self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) + self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + self.assertIsInstance(mock, MagicMock) + + +class AsyncArguments(unittest.TestCase): + def test_add_return_value(self): + async def addition(self, var): + return var + 1 + + mock = AsyncMock(addition, return_value=10) + output = asyncio.run(mock(5)) + + self.assertEqual(output, 10) + + def test_add_side_effect_exception(self): + async def addition(var): + return var + 1 + mock = AsyncMock(addition, side_effect=Exception('err')) + with self.assertRaises(Exception): + asyncio.run(mock(5)) + + def test_add_side_effect_function(self): + async def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = asyncio.run(mock(5)) + self.assertEqual(result, 6) + + def test_add_side_effect_iterable(self): + vals = [1, 2, 3] + mock = AsyncMock(side_effect=vals) + for item in vals: + self.assertEqual(item, asyncio.run(mock())) + + with self.assertRaises(RuntimeError) as e: + asyncio.run(mock()) + self.assertEqual( + e.exception, + RuntimeError('coroutine raised StopIteration') + ) + + +class AsyncContextManagerTest(unittest.TestCase): + class WithAsyncContextManager: + def __init__(self): + self.entered = False + self.exited = False + + async def __aenter__(self, *args, **kwargs): + self.entered = True + return self + + async def __aexit__(self, *args, **kwargs): + self.exited = True + + def test_magic_methods_are_async_mocks(self): + mock = MagicMock(self.WithAsyncContextManager()) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + def test_mock_supports_async_context_manager(self): + called = False + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + async def use_context_manager(): + nonlocal called + async with mock_instance as result: + called = True + return result + + result = asyncio.run(use_context_manager()) + self.assertFalse(instance.entered) + self.assertFalse(instance.exited) + self.assertTrue(called) + self.assertTrue(mock_instance.entered) + self.assertTrue(mock_instance.exited) + self.assertTrue(mock_instance.__aenter__.called) + self.assertTrue(mock_instance.__aexit__.called) + self.assertIsNot(mock_instance, result) + self.assertIsInstance(result, AsyncMock) + + def test_mock_customize_async_context_manager(self): + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + expected_result = object() + mock_instance.__aenter__.return_value = expected_result + + async def use_context_manager(): + async with mock_instance as result: + return result + + self.assertIs(asyncio.run(use_context_manager()), expected_result) + + def test_mock_customize_async_context_manager_with_coroutine(self): + enter_called = False + exit_called = False + + async def enter_coroutine(*args): + nonlocal enter_called + enter_called = True + + async def exit_coroutine(*args): + nonlocal exit_called + exit_called = True + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + mock_instance.__aenter__ = enter_coroutine + mock_instance.__aexit__ = exit_coroutine + + async def use_context_manager(): + async with mock_instance: + pass + + asyncio.run(use_context_manager()) + self.assertTrue(enter_called) + self.assertTrue(exit_called) + + def test_context_manager_raise_exception_by_default(self): + async def raise_in(context_manager): + async with context_manager: + raise TypeError() + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + with self.assertRaises(TypeError): + asyncio.run(raise_in(mock_instance)) + + +class AsyncIteratorTest(unittest.TestCase): + class WithAsyncIterator(object): + def __init__(self): + self.items = ["foo", "NormalFoo", "baz"] + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return self.items.pop() + except IndexError: + pass + + raise StopAsyncIteration + + def test_mock_aiter_and_anext(self): + instance = self.WithAsyncIterator() + mock_instance = MagicMock(instance) + + self.assertEqual(asyncio.iscoroutine(instance.__aiter__), + asyncio.iscoroutine(mock_instance.__aiter__)) + self.assertEqual(asyncio.iscoroutine(instance.__anext__), + asyncio.iscoroutine(mock_instance.__anext__)) + + iterator = instance.__aiter__() + if asyncio.iscoroutine(iterator): + iterator = asyncio.run(iterator) + + mock_iterator = mock_instance.__aiter__() + if asyncio.iscoroutine(mock_iterator): + mock_iterator = asyncio.run(mock_iterator) + + self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), + asyncio.iscoroutine(mock_iterator.__aiter__)) + self.assertEqual(asyncio.iscoroutine(iterator.__anext__), + asyncio.iscoroutine(mock_iterator.__anext__)) + + def test_mock_async_for(self): + async def iterate(iterator): + accumulator = [] + async for item in iterator: + accumulator.append(item) + + return accumulator + + expected = ["FOO", "BAR", "BAZ"] + with self.subTest("iterate through default value"): + mock_instance = MagicMock(self.WithAsyncIterator()) + self.assertEqual([], asyncio.run(iterate(mock_instance))) + + with self.subTest("iterate through set return_value"): + mock_instance = MagicMock(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = expected[:] + self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + + with self.subTest("iterate through set return_value iterator"): + mock_instance = MagicMock(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = iter(expected[:]) + self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + + +class AsyncMockAssert(unittest.TestCase): + def setUp(self): + self.mock = AsyncMock() + + async def _runnable_test(self, *args): + if not args: + await self.mock() + else: + await self.mock(*args) + + def test_assert_awaited(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + asyncio.run(self._runnable_test()) + self.mock.assert_awaited() + + def test_assert_awaited_once(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + asyncio.run(self._runnable_test()) + self.mock.assert_awaited_once() + + asyncio.run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once() + + def test_assert_awaited_with(self): + asyncio.run(self._runnable_test()) + msg = 'expected await not found' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + + asyncio.run(self._runnable_test('foo')) + self.mock.assert_awaited_with('foo') + + asyncio.run(self._runnable_test('SomethingElse')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_with('foo') + + def test_assert_awaited_once_with(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + asyncio.run(self._runnable_test('foo')) + self.mock.assert_awaited_once_with('foo') + + asyncio.run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_awaited_once_with('foo') + + def test_assert_any_wait(self): + with self.assertRaises(AssertionError): + self.mock.assert_any_await('NormalFoo') + + asyncio.run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_any_await('NormalFoo') + + asyncio.run(self._runnable_test('NormalFoo')) + self.mock.assert_any_await('NormalFoo') + + asyncio.run(self._runnable_test('SomethingElse')) + self.mock.assert_any_await('NormalFoo') + + def test_assert_has_awaits_no_order(self): + calls = [call('NormalFoo'), call('baz')] + + with self.assertRaises(AssertionError) as cm: + self.mock.assert_has_awaits(calls) + self.assertEqual(len(cm.exception.args), 1) + + asyncio.run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + asyncio.run(self._runnable_test('NormalFoo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) + + asyncio.run(self._runnable_test('baz')) + self.mock.assert_has_awaits(calls) + + asyncio.run(self._runnable_test('SomethingElse')) + self.mock.assert_has_awaits(calls) + + def test_assert_has_awaits_ordered(self): + calls = [call('NormalFoo'), call('baz')] + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + asyncio.run(self._runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + asyncio.run(self._runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) + + asyncio.run(self._runnable_test('NormalFoo')) + self.mock.assert_has_awaits(calls, any_order=True) + + asyncio.run(self._runnable_test('qux')) + self.mock.assert_has_awaits(calls, any_order=True) + + def test_assert_not_awaited(self): + self.mock.assert_not_awaited() + + asyncio.run(self._runnable_test()) + with self.assertRaises(AssertionError): + self.mock.assert_not_awaited() diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 729947e9..5eadc007 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -3,9 +3,9 @@ # http://www.voidspace.org.uk/python/mock/ import unittest -from mock.tests.support import is_instance, X, SomeClass +from unittest.test.testmock.support import is_instance, X, SomeClass -from mock import ( +from unittest.mock import ( Mock, MagicMock, NonCallableMagicMock, NonCallableMock, patch, create_autospec, CallableMixin @@ -106,14 +106,8 @@ class Sub(CallableX): class Multi(SomeClass, Sub): pass - class OldStyle: - def __call__(self): pass - - class OldStyleSub(OldStyle): - pass - for arg in 'spec', 'spec_set': - for Klass in CallableX, Sub, Multi, OldStyle, OldStyleSub: + for Klass in CallableX, Sub, Multi: with patch('%s.X' % __name__, **{arg: Klass}) as mock: instance = mock() mock.assert_called_once_with() diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index a5654ada..301bca43 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1,28 +1,16 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ -import socket - import inspect -import six -import sys import time +import types import unittest -from mock import ( - call, create_autospec, MagicMock, - Mock, ANY, patch, PropertyMock +from unittest.mock import ( + call, _Call, create_autospec, MagicMock, + Mock, ANY, _CallList, patch, PropertyMock, _callable ) -from mock.mock import _Call, _CallList, _callable from datetime import datetime from functools import partial - -if six.PY2: - import funcsigs - - class SomeClass(object): def one(self, a, b): pass def two(self): pass @@ -418,12 +406,9 @@ class Foo(object): m = create_autospec(Foo, a='3') self.assertEqual(m.a, '3') - @unittest.skipUnless(six.PY3, "Keyword only arguments Python 3 specific") + def test_create_autospec_keyword_only_arguments(self): - func_def = "def foo(a, *, b=None): pass\n" - namespace = {} - exec (func_def, namespace) - foo = namespace['foo'] + def foo(a, *, b=None): pass m = create_autospec(foo) m(1) @@ -433,6 +418,7 @@ def test_create_autospec_keyword_only_arguments(self): m(2, b=3) m.assert_called_with(2, b=3) + def test_function_as_instance_attribute(self): obj = SomeClass() def f(a): pass @@ -471,16 +457,16 @@ class Sub(SomeClass): self._check_someclass_mock(mock) - @unittest.skipIf('PyPy' in sys.version, - "This fails on pypy, " - "see https://github.com/testing-cabal/mock/issues/452") def test_spec_has_descriptor_returning_function(self): + class CrazyDescriptor(object): + def __get__(self, obj, type_): if obj is None: return lambda x: None class MyClass(object): + some_attr = CrazyDescriptor() mock = create_autospec(MyClass) @@ -490,11 +476,13 @@ class MyClass(object): with self.assertRaises(TypeError): mock.some_attr(1, 2) - @unittest.skipIf(six.PY2, "object.__dir__ doesn't exist in Python 2") + def test_spec_has_function_not_in_bases(self): + class CrazyClass(object): + def __dir__(self): - return super(CrazyClass, self).__dir__() + ['crazy'] + return super(CrazyClass, self).__dir__()+['crazy'] def __getattr__(self, item): if item == 'crazy': @@ -505,6 +493,7 @@ def __getattr__(self, item): with self.assertRaises(AttributeError): inst.other self.assertEqual(inst.crazy(42), 42) + mock = create_autospec(inst) mock.crazy(42) with self.assertRaises(TypeError): @@ -513,8 +502,6 @@ def __getattr__(self, item): mock.crazy(1, 2) - @unittest.skipIf('PyPy' in sys.version and sys.version_info < (3, 0), - "Fails on pypy2 due to incorrect signature for dict.pop from funcsigs") def test_builtin_functions_types(self): # we could replace builtin functions / methods with a function # with *args / **kwargs signature. Using the builtin method type @@ -611,27 +598,6 @@ class Baz(SomeClass, Bar): pass mock.g.assert_called_once_with(3, 4) - @unittest.skipIf(six.PY3, "No old style classes in Python 3") - def test_old_style_classes(self): - class Foo: - def f(self, a, b): pass - - class Bar(Foo): - g = Foo() - - for spec in (Foo, Foo(), Bar, Bar()): - mock = create_autospec(spec) - mock.f(1, 2) - mock.f.assert_called_once_with(1, 2) - - self.assertRaises(AttributeError, getattr, mock, 'foo') - self.assertRaises(AttributeError, getattr, mock.f, 'foo') - - mock.g.f(1, 2) - mock.g.f.assert_called_once_with(1, 2) - self.assertRaises(AttributeError, getattr, mock.g, 'foo') - - def test_recursive(self): class A(object): def a(self): pass @@ -785,21 +751,6 @@ def __init__(self, a, b=3): pass mock = create_autospec(Foo) - self.assertRaises(TypeError, mock) - mock(1) - mock.assert_called_once_with(1) - - mock(4, 5) - mock.assert_called_with(4, 5) - - - @unittest.skipIf(six.PY3, 'no old style classes in Python 3') - def test_signature_old_style_class(self): - class Foo: - def __init__(self, a, b=3): pass - - mock = create_autospec(Foo) - self.assertRaises(TypeError, mock) mock(1) mock.assert_called_once_with(1) @@ -820,15 +771,6 @@ class Foo(object): create_autospec(Foo) - @unittest.skipIf(six.PY3, 'no old style classes in Python 3') - def test_old_style_class_with_no_init(self): - # this used to raise an exception - # due to Foo.__init__ raising an AttributeError - class Foo: - pass - create_autospec(Foo) - - def test_signature_callable(self): class Callable(object): def __init__(self, x, y): pass @@ -899,36 +841,6 @@ def f(a, self): pass a.f.assert_called_with(self=10) - def test_autospec_property(self): - class Foo(object): - @property - def foo(self): pass - - foo = create_autospec(Foo) - mock_property = foo.foo - - # no spec on properties - self.assertIsInstance(mock_property, MagicMock) - mock_property(1, 2, 3) - mock_property.abc(4, 5, 6) - mock_property.assert_called_once_with(1, 2, 3) - mock_property.abc.assert_called_once_with(4, 5, 6) - - - def test_autospec_slots(self): - class Foo(object): - __slots__ = ['a'] - - foo = create_autospec(Foo) - mock_slot = foo.a - - # no spec on slots - mock_slot(1, 2, 3) - mock_slot.abc(4, 5, 6) - mock_slot.assert_called_once_with(1, 2, 3) - mock_slot.abc.assert_called_once_with(4, 5, 6) - - def test_autospec_data_descriptor(self): class Descriptor(object): def __init__(self, value): @@ -973,10 +885,8 @@ def check_data_descriptor(mock_attr): check_data_descriptor(foo.desc) - @unittest.skipIf('PyPy' in sys.version and sys.version_info > (3, 0), - "https://bitbucket.org/pypy/pypy/issues/3010") def test_autospec_on_bound_builtin_function(self): - meth = six.create_bound_method(time.ctime, time.time()) + meth = types.MethodType(time.ctime, time.time()) self.assertIsInstance(meth(), str) mocked = create_autospec(meth) @@ -987,17 +897,15 @@ def test_autospec_on_bound_builtin_function(self): mocked(4, 5, 6) mocked.assert_called_once_with(4, 5, 6) - def test_autospec_socket(self): - sock_class = create_autospec(socket.socket) - self.assertRaises(TypeError, sock_class, foo=1) - def test_autospec_getattr_partial_function(self): # bpo-32153 : getattr returning partial functions without # __name__ should not create AttributeError in create_autospec - class Foo(object): + class Foo: + def __getattr__(self, attribute): return partial(lambda name: name, attribute) + proxy = Foo() autospec = create_autospec(proxy) self.assertFalse(hasattr(autospec, '__name__')) @@ -1011,24 +919,29 @@ def myfunc(x, y): pass mock(1, 2) mock(x=1, y=2) - if six.PY2: - self.assertEqual(funcsigs.signature(mock), funcsigs.signature(myfunc)) - else: - self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc)) + self.assertEqual(inspect.signature(mock), inspect.signature(myfunc)) self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) self.assertRaises(TypeError, mock, 1) - def test_spec_function_no_name(self): - func = lambda: 'nope' - mock = create_autospec(func) - self.assertEqual(mock.__name__, 'funcopy') + def test_spec_inspect_signature_annotations(self): + + def foo(a: int, b: int=10, *, c:int) -> int: + return a + b + c + + self.assertEqual(foo(1, 2 , c=3), 6) + mock = create_autospec(foo) + mock(1, 2, c=3) + mock(1, c=3) + + self.assertEqual(inspect.signature(mock), inspect.signature(foo)) + self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) + self.assertRaises(TypeError, mock, 1) + self.assertRaises(TypeError, mock, 1, 2, 3, c=4) - @unittest.skipIf(six.PY3, "Here to test our Py2 _isidentifier") - def test_spec_function_has_identifier_name(self): + def test_spec_function_no_name(self): func = lambda: 'nope' - func.__name__ = 'global' mock = create_autospec(func) self.assertEqual(mock.__name__, 'funcopy') @@ -1105,20 +1018,6 @@ def test_call_list_str(self): self.assertEqual(str(mock.mock_calls), expected) - @unittest.skipIf(six.PY3, "Unicode is properly handled with Python 3") - def test_call_list_unicode(self): - # See github issue #328 - mock = Mock() - - class NonAsciiRepr(object): - def __repr__(self): - return "\xe9" - - mock(**{unicode("a"): NonAsciiRepr()}) - - self.assertEqual(str(mock.mock_calls), "[call(a=\xe9)]") - - def test_propertymock(self): p = patch('%s.SomeClass.one' % __name__, new_callable=PropertyMock) mock = p.start() diff --git a/mock/tests/testhelpers_py3.py b/mock/tests/testhelpers_py3.py deleted file mode 100644 index 64d62f89..00000000 --- a/mock/tests/testhelpers_py3.py +++ /dev/null @@ -1,23 +0,0 @@ -import inspect -import unittest - -from mock import call, create_autospec - - -class CallTest(unittest.TestCase): - - - def test_spec_inspect_signature_annotations(self): - - def foo(a: int, b: int=10, *, c:int) -> int: - return a + b + c - - self.assertEqual(foo(1, 2, c=3), 6) - mock = create_autospec(foo) - mock(1, 2, c=3) - mock(1, c=3) - - self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(foo)) - self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) - self.assertRaises(TypeError, mock, 1) - self.assertRaises(TypeError, mock, 1, 2, 3, c=4) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index f6c25fbc..130a3397 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -1,26 +1,8 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - -from __future__ import division - -try: - unicode -except NameError: - # Python 3 - unicode = str - long = int - import math +import unittest import os import sys -import textwrap -import unittest - -import six - -from mock import Mock, MagicMock -from mock.mock import _magics +from unittest.mock import Mock, MagicMock, _magics @@ -87,15 +69,6 @@ def test_str(self): self.assertEqual(str(mock), 'foo') - @unittest.skipIf(six.PY3, "no unicode in Python 3") - def test_unicode(self): - mock = Mock() - self.assertEqual(unicode(mock), unicode(str(mock))) - - mock.__unicode__ = lambda s: unicode('foo') - self.assertEqual(unicode(mock), unicode('foo')) - - def test_dict_methods(self): mock = Mock() @@ -167,16 +140,13 @@ def truediv(self, other): self.assertEqual(mock.value, 16) del mock.__truediv__ - if six.PY3: - def itruediv(mock): - mock /= 4 - self.assertRaises(TypeError, itruediv, mock) - mock.__itruediv__ = truediv - mock /= 8 - self.assertEqual(mock, original) - self.assertEqual(mock.value, 2) - else: - mock.value = 2 + def itruediv(mock): + mock /= 4 + self.assertRaises(TypeError, itruediv, mock) + mock.__itruediv__ = truediv + mock /= 8 + self.assertEqual(mock, original) + self.assertEqual(mock.value, 2) self.assertRaises(TypeError, lambda: 8 / mock) mock.__rtruediv__ = truediv @@ -198,12 +168,7 @@ def test_nonzero(self): m = Mock() self.assertTrue(bool(m)) - nonzero = lambda s: False - if six.PY2: - m.__nonzero__ = nonzero - else: - m.__bool__ = nonzero - + m.__bool__ = lambda s: False self.assertFalse(bool(m)) @@ -217,25 +182,18 @@ def comp(s, o): self. assertTrue(mock <= 3) self. assertTrue(mock >= 3) - if six.PY2: - # incomparable in Python 3 - self.assertEqual(Mock() < 3, object() < 3) - self.assertEqual(Mock() > 3, object() > 3) - self.assertEqual(Mock() <= 3, object() <= 3) - self.assertEqual(Mock() >= 3, object() >= 3) - else: - self.assertRaises(TypeError, lambda: MagicMock() < object()) - self.assertRaises(TypeError, lambda: object() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > object()) - self.assertRaises(TypeError, lambda: object() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= object()) - self.assertRaises(TypeError, lambda: object() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= object()) - self.assertRaises(TypeError, lambda: object() >= MagicMock()) - self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() < object()) + self.assertRaises(TypeError, lambda: object() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() < MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > object()) + self.assertRaises(TypeError, lambda: object() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() > MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= object()) + self.assertRaises(TypeError, lambda: object() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() <= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= object()) + self.assertRaises(TypeError, lambda: object() >= MagicMock()) + self.assertRaises(TypeError, lambda: MagicMock() >= MagicMock()) def test_equality(self): @@ -293,12 +251,8 @@ def test_magicmock(self): mock.__iter__.return_value = iter([1, 2, 3]) self.assertEqual(list(mock), [1, 2, 3]) - name = '__nonzero__' - other = '__bool__' - if six.PY3: - name, other = other, name - getattr(mock, name).return_value = False - self.assertFalse(hasattr(mock, other)) + getattr(mock, '__bool__').return_value = False + self.assertFalse(hasattr(mock, '__nonzero__')) self.assertFalse(bool(mock)) for entry in _magics: @@ -322,55 +276,31 @@ def test_magicmock_defaults(self): self.assertEqual(int(mock), 1) self.assertEqual(complex(mock), 1j) self.assertEqual(float(mock), 1.0) - self.assertEqual(long(mock), long(1)) self.assertNotIn(object(), mock) self.assertEqual(len(mock), 0) self.assertEqual(list(mock), []) self.assertEqual(hash(mock), object.__hash__(mock)) self.assertEqual(str(mock), object.__str__(mock)) - self.assertEqual(unicode(mock), object.__str__(mock)) - self.assertIsInstance(unicode(mock), unicode) self.assertTrue(bool(mock)) + self.assertEqual(round(mock), mock.__round__()) self.assertEqual(math.trunc(mock), mock.__trunc__()) - if six.PY2: - # These fall back to __float__ in Python 2: - self.assertEqual(round(mock), 1.0) - self.assertEqual(math.floor(mock), 1.0) - self.assertEqual(math.ceil(mock), 1.0) - else: - self.assertEqual(round(mock), mock.__round__()) - self.assertEqual(math.floor(mock), mock.__floor__()) - self.assertEqual(math.ceil(mock), mock.__ceil__()) - if six.PY2: - self.assertEqual(oct(mock), '1') - else: - # in Python 3 oct and hex use __index__ - # so these tests are for __index__ in py3k - self.assertEqual(oct(mock), '0o1') + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') self.assertEqual(hex(mock), '0x1') # how to test __sizeof__ ? - @unittest.skipIf(six.PY3, "no __cmp__ in Python 3") - def test_non_default_magic_methods(self): - mock = MagicMock() - self.assertRaises(AttributeError, lambda: mock.__cmp__) - - mock = Mock() - mock.__cmp__ = lambda s, o: 0 - - self.assertEqual(mock, object()) - - def test_magic_methods_fspath(self): mock = MagicMock() - if sys.version_info < (3, 6): - self.assertRaises(AttributeError, lambda: mock.__fspath__) - else: - expected_path = mock.__fspath__() - mock.reset_mock() - self.assertEqual(os.fspath(mock), expected_path) - mock.__fspath__.assert_called_once() + expected_path = mock.__fspath__() + mock.reset_mock() + + self.assertEqual(os.fspath(mock), expected_path) + mock.__fspath__.assert_called_once() def test_magic_methods_and_spec(self): @@ -425,7 +355,7 @@ def test_setting_unsupported_magic_method(self): mock = MagicMock() def set_setattr(): mock.__setattr__ = lambda self, name: None - self.assertRaisesRegexp(AttributeError, + self.assertRaisesRegex(AttributeError, "Attempting to set unsupported magic method '__setattr__'.", set_setattr ) @@ -459,6 +389,7 @@ def test_magic_method_reset_mock(self): mock.reset_mock() self.assertFalse(mock.__str__.called) + def test_dir(self): # overriding the default implementation for mock in Mock(), MagicMock(): @@ -504,20 +435,17 @@ def test_iterable_as_iter_return_value(self): self.assertEqual(list(m), [4, 5, 6]) self.assertEqual(list(m), []) - @unittest.skipIf(sys.version_info < (3, 5), "@ added in Python 3.5") + def test_matmul(self): - src = textwrap.dedent("""\ - m = MagicMock() - self.assertIsInstance(m @ 1, MagicMock) - m.__matmul__.return_value = 42 - m.__rmatmul__.return_value = 666 - m.__imatmul__.return_value = 24 - self.assertEqual(m @ 1, 42) - self.assertEqual(1 @ m, 666) - m @= 24 - self.assertEqual(m, 24) - """) - exec(src) + m = MagicMock() + self.assertIsInstance(m @ 1, MagicMock) + m.__matmul__.return_value = 42 + m.__rmatmul__.return_value = 666 + m.__imatmul__.return_value = 24 + self.assertEqual(m @ 1, 42) + self.assertEqual(1 @ m, 666) + m @= 24 + self.assertEqual(m, 24) def test_divmod_and_rdivmod(self): m = MagicMock() diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 15bac2ec..0f30bccc 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1,30 +1,17 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - import copy -import pickle import re import sys import tempfile -import six import unittest - -import mock -from mock.mock import ( +from unittest.test.testmock.support import is_instance +from unittest import mock +from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, _Call, _CallList, + NonCallableMagicMock, AsyncMock, _Call, _CallList, create_autospec ) -from mock.tests.support import is_instance - - -try: - unicode -except NameError: - unicode = str class Iter(object): @@ -50,23 +37,13 @@ def cmeth(cls, a, b, c, d=None): pass def smeth(a, b, c, d=None): pass -class Subclass(MagicMock): - pass - - -class Thing(object): - attribute = 6 - foo = 'bar' - - - class MockTest(unittest.TestCase): def test_all(self): # if __all__ is badly defined then import * will raise an error # We have to exec it because you can't import * inside a method # in Python 3 - exec("from mock import *") + exec("from unittest.mock import *") def test_constructor(self): @@ -215,7 +192,8 @@ def f(): pass mock = create_autospec(f) mock.side_effect = ValueError('Bazinga!') - self.assertRaisesRegexp(ValueError, 'Bazinga!', mock) + self.assertRaisesRegex(ValueError, 'Bazinga!', mock) + def test_reset_mock(self): parent = Mock() @@ -384,8 +362,7 @@ def f(a, b, c, d=None): pass # Expected call doesn't match the spec's signature with self.assertRaises(AssertionError) as cm: mock.assert_called_with(e=8) - if hasattr(cm.exception, '__cause__'): - self.assertIsInstance(cm.exception.__cause__, TypeError) + self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_called_with_method_spec(self): @@ -435,7 +412,7 @@ def test_assert_called_once_with_call_list(self): m = Mock() m(1) m(2) - self.assertRaisesRegexp(AssertionError, + self.assertRaisesRegex(AssertionError, re.escape("Calls: [call(1), call(2)]"), lambda: m.assert_called_once_with(2)) @@ -453,8 +430,7 @@ def f(a, b, c, d=None): pass # Expected call doesn't match the spec's signature with self.assertRaises(AssertionError) as cm: mock.assert_called_once_with(e=8) - if hasattr(cm.exception, '__cause__'): - self.assertIsInstance(cm.exception.__cause__, TypeError) + self.assertIsInstance(cm.exception.__cause__, TypeError) # Mock called more than once => always fails mock(4, 5, 6) self.assertRaises(AssertionError, mock.assert_called_once_with, @@ -535,7 +511,7 @@ def test_only_allowed_methods_exist(self): # this should be allowed mock.something - self.assertRaisesRegexp( + self.assertRaisesRegex( AttributeError, "Mock object has no attribute 'something_else'", getattr, mock, 'something_else' @@ -553,12 +529,12 @@ def test_attributes(mock): mock.x mock.y mock.__something__ - self.assertRaisesRegexp( + self.assertRaisesRegex( AttributeError, "Mock object has no attribute 'z'", getattr, mock, 'z' ) - self.assertRaisesRegexp( + self.assertRaisesRegex( AttributeError, "Mock object has no attribute '__foobar__'", getattr, mock, '__foobar__' @@ -738,13 +714,13 @@ def test_baseexceptional_side_effect(self): def test_assert_called_with_message(self): mock = Mock() - self.assertRaisesRegexp(AssertionError, 'not called', + self.assertRaisesRegex(AssertionError, 'not called', mock.assert_called_with) def test_assert_called_once_with_message(self): mock = Mock(name='geoffrey') - self.assertRaisesRegexp(AssertionError, + self.assertRaisesRegex(AssertionError, r"Expected 'geoffrey' to be called once\.", mock.assert_called_once_with) @@ -794,10 +770,8 @@ class X: mock = Mock(spec=X) self.assertIsInstance(mock, X) - if not six.PY2: - # This isn't true on Py2, we should fix if anyone complains: - mock = Mock(spec=X()) - self.assertIsInstance(mock, X) + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) self.assertIs(mock.__class__, X) self.assertEqual(Mock().__class__.__name__, 'Mock') @@ -805,10 +779,8 @@ class X: mock = Mock(spec_set=X) self.assertIsInstance(mock, X) - if not six.PY2: - # This isn't true on Py2, we should fix if anyone complains: - mock = Mock(spec_set=X()) - self.assertIsInstance(mock, X) + mock = Mock(spec_set=X()) + self.assertIsInstance(mock, X) def test_setting_attribute_with_spec_set(self): @@ -826,7 +798,6 @@ def set_attr(): self.assertRaises(AttributeError, set_attr) - @unittest.skipIf('PyPy' in sys.version, "https://bitbucket.org/pypy/pypy/issues/3094") def test_copy(self): current = sys.getrecursionlimit() self.addCleanup(sys.setrecursionlimit, current) @@ -837,42 +808,6 @@ def test_copy(self): copy.copy(Mock()) - @unittest.skipIf(six.PY3, "no old style classes in Python 3") - def test_spec_old_style_classes(self): - class Foo: - bar = 7 - - mock = Mock(spec=Foo) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - mock = Mock(spec=Foo()) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - - @unittest.skipIf(six.PY3, "no old style classes in Python 3") - def test_spec_set_old_style_classes(self): - class Foo: - bar = 7 - - mock = Mock(spec_set=Foo) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - def _set(): - mock.foo = 3 - self.assertRaises(AttributeError, _set) - - mock = Mock(spec_set=Foo()) - mock.bar = 6 - self.assertRaises(AttributeError, lambda: mock.foo) - - def _set(): - mock.foo = 3 - self.assertRaises(AttributeError, _set) - - def test_subclass_with_properties(self): class SubClass(Mock): def _get(self): @@ -908,7 +843,7 @@ def __call__(self, a): def test_dir(self): mock = Mock() attrs = set(dir(mock)) - type_attrs = {m for m in dir(Mock) if not m.startswith('_')} + type_attrs = set([m for m in dir(Mock) if not m.startswith('_')]) # all public attributes from the type are included self.assertEqual(set(), type_attrs - attrs) @@ -1181,7 +1116,7 @@ def test_mock_call_repr_loop(self): m = Mock() m.foo = m repr(m.foo()) - self.assertRegexpMatches(repr(m.foo()), r"") + self.assertRegex(repr(m.foo()), r"") def test_mock_calls_contains(self): @@ -1281,6 +1216,16 @@ class Foo(object): self.assertRaises(StopIteration, mock) + def test_side_effect_iterator_exceptions(self): + for Klass in Mock, MagicMock: + iterable = (ValueError, 3, KeyError, 6) + m = Klass(side_effect=iterable) + self.assertRaises(ValueError, m) + self.assertEqual(m(), 3) + self.assertRaises(KeyError, m) + self.assertEqual(m(), 6) + + def test_side_effect_setting_iterator(self): mock = Mock() mock.side_effect = iter([1, 2, 3]) @@ -1302,17 +1247,6 @@ def test_side_effect_setting_iterator(self): self.assertRaises(StopIteration, mock) self.assertIs(mock.side_effect, this_iter) - - def test_side_effect_iterator_exceptions(self): - for Klass in Mock, MagicMock: - iterable = (ValueError, 3, KeyError, 6) - m = Klass(side_effect=iterable) - self.assertRaises(ValueError, m) - self.assertEqual(m(), 3) - self.assertRaises(KeyError, m) - self.assertEqual(m(), 6) - - def test_side_effect_iterator_default(self): mock = Mock(return_value=2) mock.side_effect = iter([1, DEFAULT]) @@ -1476,8 +1410,7 @@ def f(a, b, c, d=None): pass # Expected call doesn't match the spec's signature with self.assertRaises(AssertionError) as cm: mock.assert_any_call(e=8) - if hasattr(cm.exception, '__cause__'): - self.assertIsInstance(cm.exception.__cause__, TypeError) + self.assertIsInstance(cm.exception.__cause__, TypeError) def test_mock_calls_create_autospec(self): @@ -1511,6 +1444,7 @@ def class_method(cls): pass @staticmethod def static_method(): pass for method in ('class_method', 'static_method'): + with self.subTest(method=method): mock_method = mock.create_autospec(getattr(TestClass, method)) mock_method() mock_method.assert_called_once_with() @@ -1519,9 +1453,10 @@ def static_method(): pass #Issue21238 def test_mock_unsafe(self): m = Mock() - with self.assertRaises(AttributeError): + msg = "Attributes cannot start with 'assert' or 'assret'" + with self.assertRaisesRegex(AttributeError, msg): m.assert_foo_call() - with self.assertRaises(AttributeError): + with self.assertRaisesRegex(AttributeError, msg): m.assret_foo_call() m = Mock(unsafe=True) m.assert_foo_call() @@ -1538,7 +1473,7 @@ def test_assert_not_called(self): def test_assert_not_called_message(self): m = Mock() m(1, 2) - self.assertRaisesRegexp(AssertionError, + self.assertRaisesRegex(AssertionError, re.escape("Calls: [call(1, 2)]"), m.assert_not_called) @@ -1567,7 +1502,7 @@ def test_assert_called_once_message(self): m = Mock() m(1, 2) m(3) - self.assertRaisesRegexp(AssertionError, + self.assertRaisesRegex(AssertionError, re.escape("Calls: [call(1, 2), call(3)]"), m.assert_called_once) @@ -1683,7 +1618,8 @@ def test_mock_add_spec_magic_methods(self): def test_adding_child_mock(self): - for Klass in NonCallableMock, Mock, MagicMock, NonCallableMagicMock: + for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, + AsyncMock): mock = Klass() mock.foo = Mock() @@ -1766,6 +1702,19 @@ def test_mock_open_dunder_iter_issue(self): self.assertEqual(lines[1], 'Norwegian Blue') self.assertEqual(list(f1), []) + def test_mock_open_using_next(self): + mocked_open = mock.mock_open(read_data='1st line\n2nd line\n3rd line') + f1 = mocked_open('a-name') + line1 = next(f1) + line2 = f1.__next__() + lines = [line for line in f1] + self.assertEqual(line1, '1st line\n') + self.assertEqual(line2, '2nd line\n') + self.assertEqual(lines[0], '3rd line') + self.assertEqual(list(f1), []) + with self.assertRaises(StopIteration): + next(f1) + def test_mock_open_write(self): # Test exception in file writing write() mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) @@ -1785,7 +1734,6 @@ def test_mock_open_alter_readline(self): self.assertEqual('abc', first) self.assertEqual('abc', second) - def test_mock_open_after_eof(self): # read, readline and readlines should work after end of file. _open = mock.mock_open(read_data='foo') @@ -1798,7 +1746,6 @@ def test_mock_open_after_eof(self): self.assertEqual([], h.readlines()) self.assertEqual([], h.readlines()) - def test_mock_parents(self): for Klass in Mock, MagicMock: m = Klass() @@ -1933,12 +1880,15 @@ def test_parent_attribute_of_call(self): self.assertEqual(type(call.parent), _Call) self.assertEqual(type(call.parent().parent), _Call) + def test_parent_propagation_with_create_autospec(self): + def foo(a, b): pass mock = Mock() mock.child = create_autospec(foo) mock.child(1, 2) + self.assertRaises(TypeError, mock.child, 1) self.assertEqual(mock.mock_calls, [call.child(1, 2)]) @@ -1950,27 +1900,35 @@ def test_isinstance_under_settrace(self): # dependent on unittest.mock.patch. In testpatch.PatchTest # test_patch_dict_test_prefix and test_patch_test_prefix not restoring # causes the objects patched to go out of sync - old_patch = mock.patch + + old_patch = unittest.mock.patch + # Directly using __setattr__ on unittest.mock causes current imported # reference to be updated. Use a lambda so that during cleanup the # re-imported new reference is updated. - self.addCleanup(lambda patch: setattr(mock, 'patch', patch), + self.addCleanup(lambda patch: setattr(unittest.mock, 'patch', patch), old_patch) + with patch.dict('sys.modules'): - del sys.modules['mock.mock'] + del sys.modules['unittest.mock'] + # This trace will stop coverage being measured ;-) def trace(frame, event, arg): # pragma: no cover return trace + self.addCleanup(sys.settrace, sys.gettrace()) sys.settrace(trace) - from mock.mock import ( + + from unittest.mock import ( Mock, MagicMock, NonCallableMock, NonCallableMagicMock ) + mocks = [ Mock, MagicMock, NonCallableMock, NonCallableMagicMock ] - for mock_ in mocks: - obj = mock_(spec=Something) + + for mock in mocks: + obj = mock(spec=Something) self.assertIsInstance(obj, Something) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index bbd6d26d..27914a9d 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -5,23 +5,19 @@ import os import sys -import six import unittest - -from mock.tests import support -from mock.tests.support import SomeClass, is_instance, uncache - -from mock import ( - NonCallableMock, CallableMixin, patch, sentinel, - MagicMock, Mock, NonCallableMagicMock, - DEFAULT, call +from unittest.test.testmock import support +from unittest.test.testmock.support import SomeClass, is_instance + +from test.test_importlib.util import uncache +from unittest.mock import ( + NonCallableMock, CallableMixin, sentinel, + MagicMock, Mock, NonCallableMagicMock, patch, _patch, + DEFAULT, call, _get_target ) -from mock.mock import _patch, _get_target -builtin_string = '__builtin__' -if six.PY3: - builtin_string = 'builtins' - unicode = str + +builtin_string = 'builtins' PTModule = sys.modules[__name__] MODNAME = '%s.PTModule' % __name__ @@ -623,6 +619,13 @@ def test(): self.assertEqual(foo.values, original) + def test_patch_dict_as_context_manager(self): + foo = {'a': 'b'} + with patch.dict(foo, a='c') as patched: + self.assertEqual(patched, {'a': 'c'}) + self.assertEqual(foo, {'a': 'b'}) + + def test_name_preserved(self): foo = {} @@ -656,21 +659,15 @@ def test(): test() - def test_patch_dict_with_unicode(self): - @patch.dict(u'os.environ', {'konrad_delong': 'some value'}) - def test(): - self.assertIn('konrad_delong', os.environ) - - test() - - def test_patch_dict_decorator_resolution(self): # bpo-35512: Ensure that patch with a string target resolves to # the new dictionary during function call original = support.target.copy() - @patch.dict('mock.tests.support.target', {'bar': 'BAR'}) + + @patch.dict('unittest.test.testmock.support.target', {'bar': 'BAR'}) def test(): self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) + try: support.target = {'foo': 'BAZ'} test() @@ -1329,7 +1326,7 @@ def test_patch_multiple_create_mocks_patcher(self): try: f = result['f'] foo = result['foo'] - self.assertEqual(set(result), {'f', 'foo'}) + self.assertEqual(set(result), set(['f', 'foo'])) self.assertIs(Foo, original_foo) self.assertIs(Foo.f, f) @@ -1533,18 +1530,17 @@ def func(): pass def test_patch_multiple_string_subclasses(self): - for base in (str, unicode): - Foo = type('Foo', (base,), {'fish': 'tasty'}) - foo = Foo() - @patch.multiple(foo, fish='nearly gone') - def test(): - self.assertEqual(foo.fish, 'nearly gone') + Foo = type('Foo', (str,), {'fish': 'tasty'}) + foo = Foo() + @patch.multiple(foo, fish='nearly gone') + def test(): + self.assertEqual(foo.fish, 'nearly gone') - test() - self.assertEqual(foo.fish, 'tasty') + test() + self.assertEqual(foo.fish, 'tasty') - @patch('mock.patch.TEST_PREFIX', 'foo') + @patch('unittest.mock.patch.TEST_PREFIX', 'foo') def test_patch_test_prefix(self): class Foo(object): thing = 'original' @@ -1567,7 +1563,7 @@ def test_two(self): self.assertEqual(foo.test_two(), 'original') - @patch('mock.patch.TEST_PREFIX', 'bar') + @patch('unittest.mock.patch.TEST_PREFIX', 'bar') def test_patch_dict_test_prefix(self): class Foo(object): def bar_one(self): @@ -1605,15 +1601,12 @@ def test_patch_with_spec_mock_repr(self): def test_patch_nested_autospec_repr(self): - p = patch('mock.tests.support', autospec=True) - m = p.start() - try: + with patch('unittest.test.testmock.support', autospec=True) as m: self.assertIn(" name='support.SomeClass.wibble()'", repr(m.SomeClass.wibble())) self.assertIn(" name='support.SomeClass().wibble()'", repr(m.SomeClass().wibble())) - finally: - p.stop() + def test_mock_calls_with_patch(self): @@ -1793,32 +1786,6 @@ def patched(mock_path): patched() self.assertIs(os.path, path) - - def test_wrapped_patch(self): - decorated = patch('sys.modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_several_times_patch(self): - decorated = patch('sys.modules')(function) - decorated = patch('sys.modules')(decorated) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_object(self): - decorated = patch.object(sys, 'modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_dict(self): - decorated = patch.dict('sys.modules')(function) - self.assertIs(decorated.__wrapped__, function) - - - def test_wrapped_patch_multiple(self): - decorated = patch.multiple('sys', modules={})(function) - self.assertIs(decorated.__wrapped__, function) - def test_stopall_lifo(self): stopped = [] class thing(object): @@ -1851,32 +1818,32 @@ def foo(x=0): with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") - self.assertEqual(foo.__module__, __name__) + self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch') - if hasattr(self.test_special_attrs, '__annotations__'): - with patch.object(foo, '__annotations__', dict([('s', 1, )])): - self.assertEqual(foo.__annotations__, dict([('s', 1, )])) - self.assertEqual(foo.__annotations__, dict()) - - if hasattr(self.test_special_attrs, '__kwdefaults__'): - foo = eval("lambda *a, x=0: x") - with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): - self.assertEqual(foo(), 1) - self.assertEqual(foo(), 0) + with patch.object(foo, '__annotations__', dict([('s', 1, )])): + self.assertEqual(foo.__annotations__, dict([('s', 1, )])) + self.assertEqual(foo.__annotations__, dict()) + def foo(*a, x=0): + return x + with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): + self.assertEqual(foo(), 1) + self.assertEqual(foo(), 0) def test_dotted_but_module_not_loaded(self): # This exercises the AttributeError branch of _dot_lookup. + # make sure it's there - import mock.tests.support + import unittest.test.testmock.support # now make sure it's not: with patch.dict('sys.modules'): - del sys.modules['mock.tests.support'] - del sys.modules['mock.tests'] - del sys.modules['mock.mock'] - del sys.modules['mock'] + del sys.modules['unittest.test.testmock.support'] + del sys.modules['unittest.test.testmock'] + del sys.modules['unittest.test'] + del sys.modules['unittest'] + # now make sure we can patch based on a dotted path: - @patch('mock.tests.support.X') + @patch('unittest.test.testmock.support.X') def test(mock): pass test() @@ -1888,7 +1855,7 @@ def test_invalid_target(self): def test_cant_set_kwargs_when_passing_a_mock(self): - @patch('mock.tests.support.X', new=object(), x=1) + @patch('unittest.test.testmock.support.X', new=object(), x=1) def test(): pass with self.assertRaises(TypeError): test() diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 63a85414..59f52338 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -1,5 +1,5 @@ import unittest -import mock +from unittest import mock class SampleObject: diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index 14114450..de535098 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -1,11 +1,7 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - import unittest import copy import pickle -from mock import sentinel, DEFAULT +from unittest.mock import sentinel, DEFAULT class SentinelTest(unittest.TestCase): @@ -31,6 +27,7 @@ def testBases(self): def testPickle(self): for proto in range(pickle.HIGHEST_PROTOCOL+1): + with self.subTest(protocol=proto): pickled = pickle.dumps(sentinel.whatever, proto) unpickled = pickle.loads(pickled) self.assertIs(unpickled, sentinel.whatever) diff --git a/mock/tests/testsupport.py b/mock/tests/testsupport.py deleted file mode 100644 index 4882572b..00000000 --- a/mock/tests/testsupport.py +++ /dev/null @@ -1,14 +0,0 @@ -# Tests to make sure helpers we backport are actually working! -from unittest import TestCase - -from .support import uncache - - -class TestUncache(TestCase): - - def test_cant_uncache_sys(self): - with self.assertRaises(ValueError): - with uncache('sys'): pass - - def test_uncache_non_existent(self): - with uncache('mock.tests.support.bad'): pass diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 587fde9c..42ebf389 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -1,13 +1,9 @@ -# Copyright (C) 2007-2012 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk -# http://www.voidspace.org.uk/python/mock/ - +import unittest from warnings import catch_warnings -import unittest +from unittest.test.testmock.support import is_instance +from unittest.mock import MagicMock, Mock, patch, sentinel, mock_open, call -from mock.tests.support import is_instance -from mock import MagicMock, Mock, patch, sentinel, mock_open, call something = sentinel.Something @@ -52,11 +48,10 @@ class Foo(object): def test_with_statement_nested(self): with catch_warnings(record=True): - with patch('%s.something' % __name__) as mock_something: - with patch('%s.something_else' % __name__) as mock_something_else: - self.assertEqual(something, mock_something, "unpatched") - self.assertEqual(something_else, mock_something_else, - "unpatched") + with patch('%s.something' % __name__) as mock_something, patch('%s.something_else' % __name__) as mock_something_else: + self.assertEqual(something, mock_something, "unpatched") + self.assertEqual(something_else, mock_something_else, + "unpatched") self.assertEqual(something, sentinel.Something) self.assertEqual(something_else, sentinel.SomethingElse) @@ -235,7 +230,22 @@ def test_dunder_iter_data(self): self.assertEqual(lines[1], 'bar\n') self.assertEqual(lines[2], 'baz\n') self.assertEqual(h.readline(), '') + with self.assertRaises(StopIteration): + next(h) + def test_next_data(self): + # Check that next will correctly return the next available + # line and plays well with the dunder_iter part. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = next(h) + line2 = next(h) + lines = [l for l in h] + self.assertEqual(line1, 'foo\n') + self.assertEqual(line2, 'bar\n') + self.assertEqual(lines[0], 'baz\n') + self.assertEqual(h.readline(), '') def test_readlines_data(self): # Test that emulating a file that ends in a newline character works From b81f7b60f4478b83f8614db4263ab7e4b2593783 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:27:06 +0000 Subject: [PATCH 06/70] not needed in backport --- .coveragerc | 1 - mock/tests/__init__.py | 17 ----------------- mock/tests/__main__.py | 18 ------------------ 3 files changed, 36 deletions(-) delete mode 100644 mock/tests/__main__.py diff --git a/.coveragerc b/.coveragerc index 5a292192..f3f4763b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,5 @@ [run] source = mock -omit = mock/tests/__main__.py [report] exclude_lines = diff --git a/mock/tests/__init__.py b/mock/tests/__init__.py index 87d7ae99..e69de29b 100644 --- a/mock/tests/__init__.py +++ b/mock/tests/__init__.py @@ -1,17 +0,0 @@ -import os -import sys -import unittest - - -here = os.path.dirname(__file__) -loader = unittest.defaultTestLoader - -def load_tests(*args): - suite = unittest.TestSuite() - for fn in os.listdir(here): - if fn.startswith("test") and fn.endswith(".py"): - modname = "unittest.test.testmock." + fn[:-3] - __import__(modname) - module = sys.modules[modname] - suite.addTest(loader.loadTestsFromModule(module)) - return suite diff --git a/mock/tests/__main__.py b/mock/tests/__main__.py deleted file mode 100644 index 45c633a4..00000000 --- a/mock/tests/__main__.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import unittest - - -def load_tests(loader, standard_tests, pattern): - # top level directory cached on loader instance - this_dir = os.path.dirname(__file__) - pattern = pattern or "test*.py" - # We are inside unittest.test.testmock, so the top-level is three notches up - top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir))) - package_tests = loader.discover(start_dir=this_dir, pattern=pattern, - top_level_dir=top_level_dir) - standard_tests.addTests(package_tests) - return standard_tests - - -if __name__ == '__main__': - unittest.main() From 389e48e48edca1a04f4b5a9fa29d434669b4d2e8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 12:42:08 +0000 Subject: [PATCH 07/70] fix imports --- mock/tests/testasync.py | 4 ++-- mock/tests/testcallable.py | 7 +++---- mock/tests/testhelpers.py | 8 ++++---- mock/tests/testmagicmethods.py | 4 ++-- mock/tests/testmock.py | 10 ++++----- mock/tests/testpatch.py | 37 +++++++++++++++++----------------- mock/tests/testsealable.py | 2 +- mock/tests/testsentinel.py | 2 +- mock/tests/testwith.py | 4 ++-- 9 files changed, 38 insertions(+), 40 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index fa906e4f..1910fba4 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -2,8 +2,8 @@ import inspect import unittest -from unittest.mock import (call, AsyncMock, patch, MagicMock, create_autospec, - _AwaitEvent) +from mock import call, AsyncMock, patch, MagicMock, create_autospec +from mock.mock import _AwaitEvent def tearDownModule(): diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 5eadc007..41715ed1 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -3,14 +3,13 @@ # http://www.voidspace.org.uk/python/mock/ import unittest -from unittest.test.testmock.support import is_instance, X, SomeClass +from mock.tests.support import is_instance, X, SomeClass -from unittest.mock import ( +from mock import ( Mock, MagicMock, NonCallableMagicMock, NonCallableMock, patch, create_autospec, - CallableMixin ) - +from mock.mock import CallableMixin class TestCallable(unittest.TestCase): diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 301bca43..c58e55a4 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -3,10 +3,11 @@ import types import unittest -from unittest.mock import ( - call, _Call, create_autospec, MagicMock, - Mock, ANY, _CallList, patch, PropertyMock, _callable +from mock import ( + call, create_autospec, MagicMock, + Mock, ANY, patch, PropertyMock ) +from mock.mock import _Call, _CallList, _callable from datetime import datetime from functools import partial @@ -17,7 +18,6 @@ def two(self): pass def three(self, a=None): pass - class AnyTest(unittest.TestCase): def test_any(self): diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 130a3397..fdb6f196 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -2,8 +2,8 @@ import unittest import os import sys -from unittest.mock import Mock, MagicMock, _magics - +from mock import Mock, MagicMock +from mock.mock import _magics class TestMockingMagicMethods(unittest.TestCase): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 0f30bccc..8a2f0e7d 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -4,14 +4,14 @@ import tempfile import unittest -from unittest.test.testmock.support import is_instance -from unittest import mock -from unittest.mock import ( +from mock.tests.support import is_instance +from mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, AsyncMock, _Call, _CallList, - create_autospec + NonCallableMagicMock, AsyncMock, + create_autospec, mock ) +from mock.mock import _Call, _CallList class Iter(object): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 27914a9d..15b30688 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -6,16 +6,16 @@ import sys import unittest -from unittest.test.testmock import support -from unittest.test.testmock.support import SomeClass, is_instance +from mock.tests import support +from mock.tests.support import SomeClass, is_instance from test.test_importlib.util import uncache -from unittest.mock import ( - NonCallableMock, CallableMixin, sentinel, - MagicMock, Mock, NonCallableMagicMock, patch, _patch, - DEFAULT, call, _get_target +from mock import ( + NonCallableMock, sentinel, + MagicMock, Mock, NonCallableMagicMock, patch, + DEFAULT, call ) - +from mock.mock import CallableMixin, _patch, _get_target builtin_string = 'builtins' @@ -664,7 +664,7 @@ def test_patch_dict_decorator_resolution(self): # the new dictionary during function call original = support.target.copy() - @patch.dict('unittest.test.testmock.support.target', {'bar': 'BAR'}) + @patch.dict('mock.tests.support.target', {'bar': 'BAR'}) def test(): self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) @@ -1540,7 +1540,7 @@ def test(): self.assertEqual(foo.fish, 'tasty') - @patch('unittest.mock.patch.TEST_PREFIX', 'foo') + @patch('mock.patch.TEST_PREFIX', 'foo') def test_patch_test_prefix(self): class Foo(object): thing = 'original' @@ -1563,7 +1563,7 @@ def test_two(self): self.assertEqual(foo.test_two(), 'original') - @patch('unittest.mock.patch.TEST_PREFIX', 'bar') + @patch('mock.patch.TEST_PREFIX', 'bar') def test_patch_dict_test_prefix(self): class Foo(object): def bar_one(self): @@ -1601,7 +1601,7 @@ def test_patch_with_spec_mock_repr(self): def test_patch_nested_autospec_repr(self): - with patch('unittest.test.testmock.support', autospec=True) as m: + with patch('mock.tests.support', autospec=True) as m: self.assertIn(" name='support.SomeClass.wibble()'", repr(m.SomeClass.wibble())) self.assertIn(" name='support.SomeClass().wibble()'", @@ -1818,7 +1818,7 @@ def foo(x=0): with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") - self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch') + self.assertEqual(foo.__module__, 'mock.tests.testpatch') with patch.object(foo, '__annotations__', dict([('s', 1, )])): self.assertEqual(foo.__annotations__, dict([('s', 1, )])) @@ -1834,16 +1834,15 @@ def test_dotted_but_module_not_loaded(self): # This exercises the AttributeError branch of _dot_lookup. # make sure it's there - import unittest.test.testmock.support + import mock.tests.support # now make sure it's not: with patch.dict('sys.modules'): - del sys.modules['unittest.test.testmock.support'] - del sys.modules['unittest.test.testmock'] - del sys.modules['unittest.test'] - del sys.modules['unittest'] + del sys.modules['mock.tests.support'] + del sys.modules['mock.tests'] + del sys.modules['mock'] # now make sure we can patch based on a dotted path: - @patch('unittest.test.testmock.support.X') + @patch('mock.tests.support.X') def test(mock): pass test() @@ -1855,7 +1854,7 @@ def test_invalid_target(self): def test_cant_set_kwargs_when_passing_a_mock(self): - @patch('unittest.test.testmock.support.X', new=object(), x=1) + @patch('mock.tests.support.X', new=object(), x=1) def test(): pass with self.assertRaises(TypeError): test() diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 59f52338..63a85414 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -1,5 +1,5 @@ import unittest -from unittest import mock +import mock class SampleObject: diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index de535098..56664341 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -1,7 +1,7 @@ import unittest import copy import pickle -from unittest.mock import sentinel, DEFAULT +from mock import sentinel, DEFAULT class SentinelTest(unittest.TestCase): diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 42ebf389..825387b7 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -1,8 +1,8 @@ import unittest from warnings import catch_warnings -from unittest.test.testmock.support import is_instance -from unittest.mock import MagicMock, Mock, patch, sentinel, mock_open, call +from mock.tests.support import is_instance +from mock import MagicMock, Mock, patch, sentinel, mock_open, call From b3d20d40b2477816e43cd4cef4f417632b09614b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 13:04:06 +0000 Subject: [PATCH 08/70] vendor uncache helper in to our package. --- mock/tests/support.py | 29 +++++++++++++++++++++++++++++ mock/tests/testpatch.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/mock/tests/support.py b/mock/tests/support.py index 49986d65..40fd3a02 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -1,3 +1,6 @@ +import contextlib +import sys + target = {'foo': 'FOO'} @@ -14,3 +17,29 @@ def wibble(self): pass class X(object): pass + + +@contextlib.contextmanager +def uncache(*names): + """Uncache a module from sys.modules. + + A basic sanity check is performed to prevent uncaching modules that either + cannot/shouldn't be uncached. + + """ + for name in names: + if name in ('sys', 'marshal', 'imp'): + raise ValueError( + "cannot uncache {0}".format(name)) + try: + del sys.modules[name] + except KeyError: + pass + try: + yield + finally: + for name in names: + try: + del sys.modules[name] + except KeyError: + pass diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 15b30688..e9f76a4c 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -9,7 +9,7 @@ from mock.tests import support from mock.tests.support import SomeClass, is_instance -from test.test_importlib.util import uncache +from .support import uncache from mock import ( NonCallableMock, sentinel, MagicMock, Mock, NonCallableMagicMock, patch, From cdf2d7979a653dda4a2ad6b06bb068d20b3427d6 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 19:02:52 +0000 Subject: [PATCH 09/70] py3.6 needs the inner mock module to be deleted too --- mock/tests/testpatch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index e9f76a4c..4518f87d 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1839,6 +1839,7 @@ def test_dotted_but_module_not_loaded(self): with patch.dict('sys.modules'): del sys.modules['mock.tests.support'] del sys.modules['mock.tests'] + del sys.modules['mock.mock'] del sys.modules['mock'] # now make sure we can patch based on a dotted path: From 45c1e65142cf605bb2686a130a9f92331c697378 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 19:04:01 +0000 Subject: [PATCH 10/70] run() implementation for py3.6, where it's missing --- mock/tests/testasync.py | 109 ++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 1910fba4..5843bba3 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -1,3 +1,4 @@ + import asyncio import inspect import unittest @@ -6,6 +7,18 @@ from mock.mock import _AwaitEvent +try: + from asyncio import run +except ImportError: + def run(main): + loop = asyncio.new_event_loop() + try: + return_value = loop.run_until_complete(main) + finally: + loop.close() + return return_value + + def tearDownModule(): asyncio.set_event_loop_policy(None) @@ -48,13 +61,13 @@ def test_is_async_patch(self): def test_async(mock_method): m = mock_method() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) @patch(f'{async_foo_name}.async_method') def test_no_parent_attribute(mock_method): m = mock_method() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) test_async() test_no_parent_attribute() @@ -71,7 +84,7 @@ def test_async_def_patch(self): async def test_async(): self.assertIsInstance(async_func, AsyncMock) - asyncio.run(test_async()) + run(test_async()) self.assertTrue(inspect.iscoroutinefunction(async_func)) @@ -88,7 +101,7 @@ def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: m = mock_method() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) test_async() @@ -105,7 +118,7 @@ async def test_async(): self.assertIsInstance(async_func, AsyncMock) self.assertTrue(inspect.iscoroutinefunction(async_func)) - asyncio.run(test_async()) + run(test_async()) class AsyncMockTest(unittest.TestCase): @@ -123,7 +136,7 @@ def test_isawaitable(self): mock = AsyncMock() m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) self.assertIn('assert_awaited', dir(mock)) def test_iscoroutinefunction_normal_function(self): @@ -172,7 +185,7 @@ async def main(): self.assertIsInstance(spec.awaited, _AwaitEvent) spec.assert_not_awaited() - asyncio.run(main()) + run(main()) self.assertTrue(asyncio.iscoroutinefunction(spec)) self.assertTrue(asyncio.iscoroutine(awaitable)) @@ -217,7 +230,7 @@ async def test_async(): self.assertIsNone(mock_method.await_args) self.assertEqual(mock_method.await_args_list, []) - asyncio.run(test_async()) + run(test_async()) class AsyncSpecTest(unittest.TestCase): @@ -226,42 +239,42 @@ def test_spec_as_async_positional_magicmock(self): self.assertIsInstance(mock, MagicMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_as_async_kw_magicmock(self): mock = MagicMock(spec=async_func) self.assertIsInstance(mock, MagicMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_as_async_kw_AsyncMock(self): mock = AsyncMock(spec=async_func) self.assertIsInstance(mock, AsyncMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_as_async_positional_AsyncMock(self): mock = AsyncMock(async_func) self.assertIsInstance(mock, AsyncMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_as_normal_kw_AsyncMock(self): mock = AsyncMock(spec=normal_func) self.assertIsInstance(mock, AsyncMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_as_normal_positional_AsyncMock(self): mock = AsyncMock(normal_func) self.assertIsInstance(mock, AsyncMock) m = mock() self.assertTrue(inspect.isawaitable(m)) - asyncio.run(m) + run(m) def test_spec_async_mock(self): @patch.object(AsyncClass, 'async_method', spec=True) @@ -328,7 +341,7 @@ async def addition(self, var): return var + 1 mock = AsyncMock(addition, return_value=10) - output = asyncio.run(mock(5)) + output = run(mock(5)) self.assertEqual(output, 10) @@ -337,23 +350,23 @@ async def addition(var): return var + 1 mock = AsyncMock(addition, side_effect=Exception('err')) with self.assertRaises(Exception): - asyncio.run(mock(5)) + run(mock(5)) def test_add_side_effect_function(self): async def addition(var): return var + 1 mock = AsyncMock(side_effect=addition) - result = asyncio.run(mock(5)) + result = run(mock(5)) self.assertEqual(result, 6) def test_add_side_effect_iterable(self): vals = [1, 2, 3] mock = AsyncMock(side_effect=vals) for item in vals: - self.assertEqual(item, asyncio.run(mock())) + self.assertEqual(item, run(mock())) with self.assertRaises(RuntimeError) as e: - asyncio.run(mock()) + run(mock()) self.assertEqual( e.exception, RuntimeError('coroutine raised StopIteration') @@ -389,7 +402,7 @@ async def use_context_manager(): called = True return result - result = asyncio.run(use_context_manager()) + result = run(use_context_manager()) self.assertFalse(instance.entered) self.assertFalse(instance.exited) self.assertTrue(called) @@ -411,7 +424,7 @@ async def use_context_manager(): async with mock_instance as result: return result - self.assertIs(asyncio.run(use_context_manager()), expected_result) + self.assertIs(run(use_context_manager()), expected_result) def test_mock_customize_async_context_manager_with_coroutine(self): enter_called = False @@ -435,7 +448,7 @@ async def use_context_manager(): async with mock_instance: pass - asyncio.run(use_context_manager()) + run(use_context_manager()) self.assertTrue(enter_called) self.assertTrue(exit_called) @@ -447,7 +460,7 @@ async def raise_in(context_manager): instance = self.WithAsyncContextManager() mock_instance = MagicMock(instance) with self.assertRaises(TypeError): - asyncio.run(raise_in(mock_instance)) + run(raise_in(mock_instance)) class AsyncIteratorTest(unittest.TestCase): @@ -477,11 +490,11 @@ def test_mock_aiter_and_anext(self): iterator = instance.__aiter__() if asyncio.iscoroutine(iterator): - iterator = asyncio.run(iterator) + iterator = run(iterator) mock_iterator = mock_instance.__aiter__() if asyncio.iscoroutine(mock_iterator): - mock_iterator = asyncio.run(mock_iterator) + mock_iterator = run(mock_iterator) self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), asyncio.iscoroutine(mock_iterator.__aiter__)) @@ -499,17 +512,17 @@ async def iterate(iterator): expected = ["FOO", "BAR", "BAZ"] with self.subTest("iterate through default value"): mock_instance = MagicMock(self.WithAsyncIterator()) - self.assertEqual([], asyncio.run(iterate(mock_instance))) + self.assertEqual([], run(iterate(mock_instance))) with self.subTest("iterate through set return_value"): mock_instance = MagicMock(self.WithAsyncIterator()) mock_instance.__aiter__.return_value = expected[:] - self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + self.assertEqual(expected, run(iterate(mock_instance))) with self.subTest("iterate through set return_value iterator"): mock_instance = MagicMock(self.WithAsyncIterator()) mock_instance.__aiter__.return_value = iter(expected[:]) - self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + self.assertEqual(expected, run(iterate(mock_instance))) class AsyncMockAssert(unittest.TestCase): @@ -526,30 +539,30 @@ def test_assert_awaited(self): with self.assertRaises(AssertionError): self.mock.assert_awaited() - asyncio.run(self._runnable_test()) + run(self._runnable_test()) self.mock.assert_awaited() def test_assert_awaited_once(self): with self.assertRaises(AssertionError): self.mock.assert_awaited_once() - asyncio.run(self._runnable_test()) + run(self._runnable_test()) self.mock.assert_awaited_once() - asyncio.run(self._runnable_test()) + run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_awaited_once() def test_assert_awaited_with(self): - asyncio.run(self._runnable_test()) + run(self._runnable_test()) msg = 'expected await not found' with self.assertRaisesRegex(AssertionError, msg): self.mock.assert_awaited_with('foo') - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) self.mock.assert_awaited_with('foo') - asyncio.run(self._runnable_test('SomethingElse')) + run(self._runnable_test('SomethingElse')) with self.assertRaises(AssertionError): self.mock.assert_awaited_with('foo') @@ -557,10 +570,10 @@ def test_assert_awaited_once_with(self): with self.assertRaises(AssertionError): self.mock.assert_awaited_once_with('foo') - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) self.mock.assert_awaited_once_with('foo') - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_awaited_once_with('foo') @@ -568,14 +581,14 @@ def test_assert_any_wait(self): with self.assertRaises(AssertionError): self.mock.assert_any_await('NormalFoo') - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_any_await('NormalFoo') - asyncio.run(self._runnable_test('NormalFoo')) + run(self._runnable_test('NormalFoo')) self.mock.assert_any_await('NormalFoo') - asyncio.run(self._runnable_test('SomethingElse')) + run(self._runnable_test('SomethingElse')) self.mock.assert_any_await('NormalFoo') def test_assert_has_awaits_no_order(self): @@ -585,18 +598,18 @@ def test_assert_has_awaits_no_order(self): self.mock.assert_has_awaits(calls) self.assertEqual(len(cm.exception.args), 1) - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - asyncio.run(self._runnable_test('NormalFoo')) + run(self._runnable_test('NormalFoo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - asyncio.run(self._runnable_test('baz')) + run(self._runnable_test('baz')) self.mock.assert_has_awaits(calls) - asyncio.run(self._runnable_test('SomethingElse')) + run(self._runnable_test('SomethingElse')) self.mock.assert_has_awaits(calls) def test_assert_has_awaits_ordered(self): @@ -604,23 +617,23 @@ def test_assert_has_awaits_ordered(self): with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self._runnable_test('baz')) + run(self._runnable_test('baz')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self._runnable_test('foo')) + run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self._runnable_test('NormalFoo')) + run(self._runnable_test('NormalFoo')) self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self._runnable_test('qux')) + run(self._runnable_test('qux')) self.mock.assert_has_awaits(calls, any_order=True) def test_assert_not_awaited(self): self.mock.assert_not_awaited() - asyncio.run(self._runnable_test()) + run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_not_awaited() From 12bd6d5cbc494d8e2e8dc7b0815a4c5ff92a802b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 15 Jan 2020 08:20:45 +0000 Subject: [PATCH 11/70] PyPy's object class has no __sizeof__. --- mock/__init__.py | 4 +++- mock/mock.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mock/__init__.py b/mock/__init__.py index 1f29771d..cdf5a163 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -1,6 +1,8 @@ from __future__ import absolute_import -import re +import re, sys + +IS_PYPY = 'PyPy' in sys.version import mock.mock as _mock from mock.mock import * diff --git a/mock/mock.py b/mock/mock.py index be961947..aaf0452d 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -36,6 +36,7 @@ from unittest.util import safe_repr from functools import wraps, partial +from mock import IS_PYPY _builtins = {name for name in dir(builtins) if not name.startswith('_')} @@ -1819,6 +1820,10 @@ def _patch_stopall(): "fspath " ) +if IS_PYPY: + # PyPy has no __sizeof__: http://doc.pypy.org/en/latest/cpython_differences.html + magic_methods = magic_methods.replace('sizeof ', '') + numerics = ( "add sub mul matmul div floordiv mod lshift rshift and xor or pow truediv" ) From 3d44cb87bfbee040fdd219d1460ce186fb06882d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 15 Jan 2020 08:21:09 +0000 Subject: [PATCH 12/70] re-introduce skips for https://bitbucket.org/pypy/pypy/issues/3010 --- mock/tests/testhelpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index c58e55a4..8b4d7092 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -8,10 +8,14 @@ Mock, ANY, patch, PropertyMock ) from mock.mock import _Call, _CallList, _callable +from mock import IS_PYPY from datetime import datetime from functools import partial +import pytest + + class SomeClass(object): def one(self, a, b): pass def two(self): pass @@ -457,6 +461,8 @@ class Sub(SomeClass): self._check_someclass_mock(mock) + @pytest.mark.skipif(IS_PYPY, + reason="https://bitbucket.org/pypy/pypy/issues/3010") def test_spec_has_descriptor_returning_function(self): class CrazyDescriptor(object): @@ -885,6 +891,8 @@ def check_data_descriptor(mock_attr): check_data_descriptor(foo.desc) + @pytest.mark.skipif(IS_PYPY, + reason="https://bitbucket.org/pypy/pypy/issues/3010") def test_autospec_on_bound_builtin_function(self): meth = types.MethodType(time.ctime, time.time()) self.assertIsInstance(meth(), str) From 9b35e34a8cd0cfea7465c7b251d982f31726d7c5 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 15 Jan 2020 08:34:43 +0000 Subject: [PATCH 13/70] Skip cpython's 2085bd0877e17ad4d98a4586d5eabb6faecbb190 as PEP 570 syntax for positional-only parameters would limit this codebase to being Py3.8+ only. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index ff52fa89..fbd0d7cf 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -4a686504eb2bbf69adf78077458508a7ba131667 +2085bd0877e17ad4d98a4586d5eabb6faecbb190 From 20abe16d0dad5be502be77e61cbfa882d83fe58c Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 22 Jul 2019 13:08:22 +0530 Subject: [PATCH 14/70] bpo-21478: Record calls to parent when autospecced objects are used as child with attach_mock (GH 14688) * Clear name and parent of mock in autospecced objects used with attach_mock * Add NEWS entry * Fix reversed order of comparison * Test child and standalone function calls * Use a helper function extracting mock to avoid code duplication and refactor tests. Backports: 7397cda99795a4a8d96193d710105e77a07b7411 Signed-off-by: Chris Withers --- .../2019-07-10-23-07-11.bpo-21478.cCw9rF.rst | 2 + mock/mock.py | 27 ++++++++------ mock/tests/testmock.py | 37 +++++++++++++++++++ 3 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst diff --git a/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst b/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst new file mode 100644 index 00000000..0ac9b8ea --- /dev/null +++ b/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst @@ -0,0 +1,2 @@ +Record calls to parent when autospecced object is attached to a mock using +:func:`unittest.mock.attach_mock`. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index aaf0452d..437f2d11 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -73,6 +73,15 @@ def _is_exception(obj): ) +def _extract_mock(obj): + # Autospecced functions will return a FunctionType with "mock" attribute + # which is the actual mock object that needs to be used. + if isinstance(obj, FunctionTypes) and hasattr(obj, 'mock'): + return obj.mock + else: + return obj + + def _get_signature_object(func, as_instance, eat_self): """ Given an arbitrary, possibly callable object, try to create a suitable @@ -347,13 +356,7 @@ def __repr__(self): def _check_and_set_parent(parent, value, name, new_name): - # function passed to create_autospec will have mock - # attribute attached to which parent must be set - if isinstance(value, FunctionTypes): - try: - value = value.mock - except AttributeError: - pass + value = _extract_mock(value) if not _is_instance_mock(value): return False @@ -468,10 +471,12 @@ def attach_mock(self, mock, attribute): Attach a mock as an attribute of this one, replacing its name and parent. Calls to the attached mock will be recorded in the `method_calls` and `mock_calls` attributes of this one.""" - mock._mock_parent = None - mock._mock_new_parent = None - mock._mock_name = '' - mock._mock_new_name = None + inner_mock = _extract_mock(mock) + + inner_mock._mock_parent = None + inner_mock._mock_new_parent = None + inner_mock._mock_name = '' + inner_mock._mock_new_name = None setattr(self, attribute, mock) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 8a2f0e7d..5ad1016e 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -37,6 +37,9 @@ def cmeth(cls, a, b, c, d=None): pass def smeth(a, b, c, d=None): pass +def something(a): pass + + class MockTest(unittest.TestCase): def test_all(self): @@ -1808,6 +1811,26 @@ def test_attach_mock_return_value(self): self.assertEqual(m.mock_calls, call().foo().call_list()) + def test_attach_mock_patch_autospec(self): + parent = Mock() + + with mock.patch(f'{__name__}.something', autospec=True) as mock_func: + self.assertEqual(mock_func.mock._extract_mock_name(), 'something') + parent.attach_mock(mock_func, 'child') + parent.child(1) + something(2) + mock_func(3) + + parent_calls = [call.child(1), call.child(2), call.child(3)] + child_calls = [call(1), call(2), call(3)] + self.assertEqual(parent.mock_calls, parent_calls) + self.assertEqual(parent.child.mock_calls, child_calls) + self.assertEqual(something.mock_calls, child_calls) + self.assertEqual(mock_func.mock_calls, child_calls) + self.assertIn('mock.child', repr(parent.child.mock)) + self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child') + + def test_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): @@ -1891,6 +1914,20 @@ def foo(a, b): pass self.assertRaises(TypeError, mock.child, 1) self.assertEqual(mock.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(mock.child.mock)) + + def test_parent_propagation_with_autospec_attach_mock(self): + + def foo(a, b): pass + + parent = Mock() + parent.attach_mock(create_autospec(foo, name='bar'), 'child') + parent.child(1, 2) + + self.assertRaises(TypeError, parent.child, 1) + self.assertEqual(parent.child.mock_calls, [call.child(1, 2)]) + self.assertIn('mock.child', repr(parent.child.mock)) + def test_isinstance_under_settrace(self): # bpo-36593 : __class__ is not set for a class that has __class__ From ae02b3025132cc0c4adf3eb5085e5c91f04dbba2 Mon Sep 17 00:00:00 2001 From: Min ho Kim Date: Wed, 31 Jul 2019 08:16:13 +1000 Subject: [PATCH 15/70] Fix typos in comments, docs and test names (#15018) * Fix typos in comments, docs and test names * Update test_pyparse.py account for change in string length * Apply suggestion: splitable -> splittable Co-Authored-By: Terry Jan Reedy * Apply suggestion: splitable -> splittable Co-Authored-By: Terry Jan Reedy * Apply suggestion: Dealloccte -> Deallocate Co-Authored-By: Terry Jan Reedy * Update posixmodule checksum. * Reverse idlelib changes. Backports: c4cacc8c5eab50db8da3140353596f38a01115ca Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/tests/testmock.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index fbd0d7cf..d1e44184 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -2085bd0877e17ad4d98a4586d5eabb6faecbb190 +7397cda99795a4a8d96193d710105e77a07b7411 diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 5ad1016e..3d196e20 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -262,7 +262,7 @@ def test_call(self): ret_val = mock(sentinel.Arg) self.assertTrue(mock.called, "called not set") - self.assertEqual(mock.call_count, 1, "call_count incoreect") + self.assertEqual(mock.call_count, 1, "call_count incorrect") self.assertEqual(mock.call_args, ((sentinel.Arg,), {}), "call_args not set") self.assertEqual(mock.call_args.args, (sentinel.Arg,), From cd18e01ba0f9ac50e06e6131cde73d16b191bfca Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 8 Aug 2019 08:42:54 +0300 Subject: [PATCH 16/70] bpo-37685: Fixed __eq__, __lt__ etc implementations in some classes. (GH-14952) They now return NotImplemented for unsupported type of the other operand. Backports: 662db125cddbca1db68116c547c290eb3943d98e Signed-off-by: Chris Withers --- NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst | 4 ++++ lastsync.txt | 2 +- mock/mock.py | 4 +--- mock/tests/support.py | 12 ++++++++++++ mock/tests/testmock.py | 8 ++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst diff --git a/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst b/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst new file mode 100644 index 00000000..d1179a62 --- /dev/null +++ b/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst @@ -0,0 +1,4 @@ +Fixed ``__eq__``, ``__lt__`` etc implementations in some classes. They now +return :data:`NotImplemented` for unsupported type of the other operand. +This allows the other operand to play role (for example the equality +comparison with :data:`~unittest.mock.ANY` will return ``True``). diff --git a/lastsync.txt b/lastsync.txt index d1e44184..69bd2fe3 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -7397cda99795a4a8d96193d710105e77a07b7411 +c4cacc8c5eab50db8da3140353596f38a01115ca diff --git a/mock/mock.py b/mock/mock.py index 437f2d11..b989561c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2377,12 +2377,10 @@ def __init__(self, value=(), name=None, parent=None, two=False, def __eq__(self, other): - if other is ANY: - return True try: len_other = len(other) except TypeError: - return False + return NotImplemented self_name = '' if len(self) == 2: diff --git a/mock/tests/support.py b/mock/tests/support.py index 40fd3a02..79576dd9 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -43,3 +43,15 @@ def uncache(*names): del sys.modules[name] except KeyError: pass + + +class _ALWAYS_EQ: + """ + Object that is equal to anything. + """ + def __eq__(self, other): + return True + def __ne__(self, other): + return False + +ALWAYS_EQ = _ALWAYS_EQ() diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 3d196e20..5819ece2 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -4,6 +4,7 @@ import tempfile import unittest +from mock.tests.support import ALWAYS_EQ from mock.tests.support import is_instance from mock import ( call, DEFAULT, patch, sentinel, @@ -322,6 +323,8 @@ def test_calls_equal_with_any(self): self.assertFalse(mm != mock.ANY) self.assertTrue(mock.ANY == mm) self.assertFalse(mock.ANY != mm) + self.assertTrue(mm == ALWAYS_EQ) + self.assertFalse(mm != ALWAYS_EQ) call1 = mock.call(mock.MagicMock()) call2 = mock.call(mock.ANY) @@ -330,6 +333,11 @@ def test_calls_equal_with_any(self): self.assertTrue(call2 == call1) self.assertFalse(call2 != call1) + self.assertTrue(call1 == ALWAYS_EQ) + self.assertFalse(call1 != ALWAYS_EQ) + self.assertFalse(call1 == 1) + self.assertTrue(call1 != 1) + def test_assert_called_with(self): mock = Mock() From 9f43738d3d2bfc4eb1a660f9f8c43f3f4e2881fd Mon Sep 17 00:00:00 2001 From: Xtreak Date: Thu, 29 Aug 2019 11:39:01 +0530 Subject: [PATCH 17/70] bpo-36871: Ensure method signature is used when asserting mock calls to a method (GH13261) * Fix call_matcher for mock when using methods * Add NEWS entry * Use None check and convert doctest to unittest * Use better name for mock in tests. Handle _SpecState when the attribute was not accessed and add tests. * Use reset_mock instead of reinitialization. Change inner class constructor signature for check * Reword comment regarding call object lookup logic Backports: c96127821ebda50760e788b1213975a0d5bea37f Signed-off-by: Chris Withers --- .../2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst | 3 ++ mock/mock.py | 36 +++++++++++++- mock/tests/testmock.py | 48 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst diff --git a/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst b/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst new file mode 100644 index 00000000..218795f2 --- /dev/null +++ b/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst @@ -0,0 +1,3 @@ +Ensure method signature is used instead of constructor signature of a class +while asserting mock object against method calls. Patch by Karthikeyan +Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index b989561c..34316390 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -805,6 +805,35 @@ def _format_mock_failure_message(self, args, kwargs, action='call'): return message % (action, expected_string, actual_string) + def _get_call_signature_from_name(self, name): + """ + * If call objects are asserted against a method/function like obj.meth1 + then there could be no name for the call object to lookup. Hence just + return the spec_signature of the method/function being asserted against. + * If the name is not empty then remove () and split by '.' to get + list of names to iterate through the children until a potential + match is found. A child mock is created only during attribute access + so if we get a _SpecState then no attributes of the spec were accessed + and can be safely exited. + """ + if not name: + return self._spec_signature + + sig = None + names = name.replace('()', '').split('.') + children = self._mock_children + + for name in names: + child = children.get(name) + if child is None or isinstance(child, _SpecState): + break + else: + children = child._mock_children + sig = child._spec_signature + + return sig + + def _call_matcher(self, _call): """ Given a call (or simply an (args, kwargs) tuple), return a @@ -812,7 +841,12 @@ def _call_matcher(self, _call): This is a best effort method which relies on the spec's signature, if available, or falls back on the arguments themselves. """ - sig = self._spec_signature + + if isinstance(_call, tuple) and len(_call) > 2: + sig = self._get_call_signature_from_name(_call[0]) + else: + sig = self._spec_signature + if sig is not None: if len(_call) == 2: name = '' diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 5819ece2..b24890d2 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1347,6 +1347,54 @@ def test_assert_has_calls(self): ) + def test_assert_has_calls_nested_spec(self): + class Something: + + def __init__(self): pass + def meth(self, a, b, c, d=None): pass + + class Foo: + + def __init__(self, a): pass + def meth1(self, a, b): pass + + mock_class = create_autospec(Something) + + for m in [mock_class, mock_class()]: + m.meth(1, 2, 3, d=1) + m.assert_has_calls([call.meth(1, 2, 3, d=1)]) + m.assert_has_calls([call.meth(1, 2, 3, 1)]) + + mock_class.reset_mock() + + for m in [mock_class, mock_class()]: + self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()]) + m.Foo(1).meth1(1, 2) + m.assert_has_calls([call.Foo(1), call.Foo(1).meth1(1, 2)]) + m.Foo.assert_has_calls([call(1), call().meth1(1, 2)]) + + mock_class.reset_mock() + + invalid_calls = [call.meth(1), + call.non_existent(1), + call.Foo().non_existent(1), + call.Foo().meth(1, 2, 3, 4)] + + for kall in invalid_calls: + self.assertRaises(AssertionError, + mock_class.assert_has_calls, + [kall] + ) + + + def test_assert_has_calls_nested_without_spec(self): + m = MagicMock() + m().foo().bar().baz() + m.one().two().three() + calls = call.one().two().three().call_list() + m.assert_has_calls(calls) + + def test_assert_has_calls_with_function_spec(self): def f(a, b, c, d=None): pass From 202ff787f9c423ece9f20994f7798c2738add362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Thu, 29 Aug 2019 08:15:53 +0200 Subject: [PATCH 18/70] bpo-35946: Improve assert_called_with documentation (GH-11796) Backports: f5896a05edf5df91fb1b55bd481ba5b2a3682f4e Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/mock.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index 69bd2fe3..b8f1999b 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -c4cacc8c5eab50db8da3140353596f38a01115ca +c96127821ebda50760e788b1213975a0d5bea37f diff --git a/mock/mock.py b/mock/mock.py index 34316390..953ca846 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -892,7 +892,7 @@ def assert_called_once(_mock_self): raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): - """assert that the mock was called with the specified arguments. + """assert that the last call was made with the specified arguments. Raises an AssertionError if the args and keyword args passed in are different to the last call to the mock.""" From 20fce5db496c7864341665233e0f73d2df1836c8 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 29 Aug 2019 01:27:42 -0700 Subject: [PATCH 19/70] bpo-36743: __get__ is sometimes called without the owner argument (#12992) Backports: 0dac68f1e593c11612ed54af9edb865d398f3b05 Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/mock.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index b8f1999b..33fae739 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -c96127821ebda50760e788b1213975a0d5bea37f +f5896a05edf5df91fb1b55bd481ba5b2a3682f4e diff --git a/mock/mock.py b/mock/mock.py index 953ca846..adbd87d8 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2823,7 +2823,7 @@ class PropertyMock(Mock): def _get_child_mock(self, **kwargs): return MagicMock(**kwargs) - def __get__(self, obj, obj_type): + def __get__(self, obj, obj_type=None): return self() def __set__(self, obj, val): self(val) From 97bf5a41187fe84e47fe8799df1b585af73870c4 Mon Sep 17 00:00:00 2001 From: Min ho Kim Date: Sat, 31 Aug 2019 06:21:19 +1000 Subject: [PATCH 20/70] Fix typos mostly in comments, docs and test names (GH-15209) Backports: 39d87b54715197ca9dcb6902bb43461c0ed701a2 Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/tests/testpatch.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index 33fae739..34a2e3e9 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -f5896a05edf5df91fb1b55bd481ba5b2a3682f4e +0dac68f1e593c11612ed54af9edb865d398f3b05 diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 4518f87d..ae5cdff7 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1651,7 +1651,7 @@ def test_patch_imports_lazily(self): p1.stop() self.assertEqual(squizz.squozz, 3) - def test_patch_propogrates_exc_on_exit(self): + def test_patch_propagates_exc_on_exit(self): class holder: exc_info = None, None, None @@ -1680,9 +1680,9 @@ def test(mock): self.assertIs(holder.exc_info[0], RuntimeError) self.assertIsNotNone(holder.exc_info[1], - 'exception value not propgated') + 'exception value not propagated') self.assertIsNotNone(holder.exc_info[2], - 'exception traceback not propgated') + 'exception traceback not propagated') def test_create_and_specs(self): From 39b5272fb7099da0f3a3719a1ef0d2bd48c7efcc Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 9 Sep 2019 14:34:57 +0530 Subject: [PATCH 21/70] Fix assertions regarding magic methods function body that was not executed (GH-14154) Backports: aa515082749687c1e3bc9ec5e2296368191b9f84 Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/tests/testasync.py | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index 34a2e3e9..4b3f4da8 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -0dac68f1e593c11612ed54af9edb865d398f3b05 +39d87b54715197ca9dcb6902bb43461c0ed701a2 diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 5843bba3..9e0d322c 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -374,17 +374,14 @@ def test_add_side_effect_iterable(self): class AsyncContextManagerTest(unittest.TestCase): + class WithAsyncContextManager: - def __init__(self): - self.entered = False - self.exited = False async def __aenter__(self, *args, **kwargs): - self.entered = True return self async def __aexit__(self, *args, **kwargs): - self.exited = True + pass def test_magic_methods_are_async_mocks(self): mock = MagicMock(self.WithAsyncContextManager()) @@ -403,11 +400,7 @@ async def use_context_manager(): return result result = run(use_context_manager()) - self.assertFalse(instance.entered) - self.assertFalse(instance.exited) self.assertTrue(called) - self.assertTrue(mock_instance.entered) - self.assertTrue(mock_instance.exited) self.assertTrue(mock_instance.__aenter__.called) self.assertTrue(mock_instance.__aexit__.called) self.assertIsNot(mock_instance, result) From 93f85f9e5546a0de971f7cc62e156c252b044603 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 9 Sep 2019 16:25:22 +0530 Subject: [PATCH 22/70] bpo-37212: Preserve keyword argument order in unittest.mock.call and error messages (GH-14310) Backports: 9d607061c9c888913ae2c18543775cf360d55f27 Signed-off-by: Chris Withers --- NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst | 2 ++ mock/mock.py | 2 +- mock/tests/testmock.py | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst diff --git a/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst b/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst new file mode 100644 index 00000000..520a0229 --- /dev/null +++ b/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst @@ -0,0 +1,2 @@ +:func:`unittest.mock.call` now preserves the order of keyword arguments in +repr output. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index adbd87d8..bece0a56 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2339,7 +2339,7 @@ def _format_call_signature(name, args, kwargs): formatted_args = '' args_string = ', '.join([repr(arg) for arg in args]) kwargs_string = ', '.join([ - '%s=%r' % (key, value) for key, value in sorted(kwargs.items()) + '%s=%r' % (key, value) for key, value in kwargs.items() ]) if args_string: formatted_args = args_string diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index b24890d2..3947cd69 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1571,11 +1571,11 @@ def test_assert_called_once_message_not_called(self): m.assert_called_once() self.assertNotIn("Calls:", str(e.exception)) - #Issue21256 printout of keyword args should be in deterministic order - def test_sorted_call_signature(self): + #Issue37212 printout of keyword args now preserves the original order + def test_ordered_call_signature(self): m = Mock() m.hello(name='hello', daddy='hero') - text = "call(daddy='hero', name='hello')" + text = "call(name='hello', daddy='hero')" self.assertEqual(repr(m.hello.call_args), text) #Issue21270 overrides tuple methods for mock.call objects From 42ae9ebded7db37a290b6e7d3309b21738992a07 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 9 Sep 2019 15:18:06 +0100 Subject: [PATCH 23/70] docs: Add references to AsyncMock in unittest.mock.patch (#13681) Update the docs as patch can now return an AsyncMock if the patched object is an async function. Backports: f5e7f39d2916ed150e80381faed125f405a11e11 Signed-off-by: Chris Withers --- mock/mock.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index bece0a56..2d4d89ea 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1638,8 +1638,9 @@ def patch( is patched with a `new` object. When the function/with statement exits the patch is undone. - If `new` is omitted, then the target is replaced with a - `MagicMock`. If `patch` is used as a decorator and `new` is + If `new` is omitted, then the target is replaced with an + `AsyncMock if the patched object is an async function or a + `MagicMock` otherwise. If `patch` is used as a decorator and `new` is omitted, the created mock is passed in as an extra argument to the decorated function. If `patch` is used as a context manager the created mock is returned by the context manager. @@ -1657,8 +1658,8 @@ def patch( patch to pass in the object being mocked as the spec/spec_set object. `new_callable` allows you to specify a different class, or callable object, - that will be called to create the `new` object. By default `MagicMock` is - used. + that will be called to create the `new` object. By default `AsyncMock` is + used for async functions and `MagicMock` for the rest. A more powerful form of `spec` is `autospec`. If you set `autospec=True` then the mock will be created with a spec from the object being replaced. From d8170ff058661e2a96ec871b4e48bc57b41f5a44 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 10 Sep 2019 12:18:40 +0100 Subject: [PATCH 24/70] bpo-37251: Removes __code__ check from _is_async_obj. (GH-15830) Backports: f1a297acb60b88917712450ebd3cfa707e6efd6b Signed-off-by: Chris Withers --- NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst | 3 +++ mock/mock.py | 5 ++--- mock/tests/testasync.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst diff --git a/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst b/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst new file mode 100644 index 00000000..27fd1e46 --- /dev/null +++ b/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst @@ -0,0 +1,3 @@ +Remove `__code__` check in AsyncMock that incorrectly +evaluated function specs as async objects but failed to evaluate classes +with `__await__` but no `__code__` attribute defined as async objects. diff --git a/mock/mock.py b/mock/mock.py index 2d4d89ea..8a9fc9d0 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -47,10 +47,9 @@ _safe_super = super def _is_async_obj(obj): - if getattr(obj, '__code__', None): - return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) - else: + if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): return False + return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) def _is_async_func(func): diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 9e0d322c..e6f923e2 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -31,6 +31,10 @@ async def async_method(self): def normal_method(self): pass +class AwaitableClass: + def __await__(self): + yield + async def async_func(): pass @@ -173,6 +177,10 @@ def test_create_autospec_instance(self): with self.assertRaises(RuntimeError): create_autospec(async_func, instance=True) + def test_create_autospec_awaitable_class(self): + awaitable_mock = create_autospec(spec=AwaitableClass()) + self.assertIsInstance(create_autospec(awaitable_mock), AsyncMock) + def test_create_autospec(self): spec = create_autospec(async_func_args) awaitable = spec(1, 2, c=3) @@ -334,6 +342,13 @@ def test_is_child_AsyncMock(self): self.assertIsInstance(mock.normal_method, MagicMock) self.assertIsInstance(mock, MagicMock) + def test_magicmock_lambda_spec(self): + mock_obj = MagicMock() + mock_obj.mock_func = MagicMock(spec=lambda x: x) + + with patch.object(mock_obj, "mock_func") as cm: + self.assertIsInstance(cm, MagicMock) + class AsyncArguments(unittest.TestCase): def test_add_return_value(self): From 08a2de7a298ddcdf2b5f3a4a13f18f09b05df168 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 21 Jan 2020 08:19:38 +0000 Subject: [PATCH 25/70] backport fixed iscoroutinefunction from Python 3.8 --- mock/backports.py | 39 +++++++++++++++++++++++++++++++++++++++ mock/mock.py | 9 +++++---- 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 mock/backports.py diff --git a/mock/backports.py b/mock/backports.py new file mode 100644 index 00000000..fdca1972 --- /dev/null +++ b/mock/backports.py @@ -0,0 +1,39 @@ +import sys + + +if sys.version_info[:2] < (3, 8): + + import functools + from asyncio.coroutines import _is_coroutine + from inspect import ismethod, isfunction, CO_COROUTINE + + def _unwrap_partial(func): + while isinstance(func, functools.partial): + func = func.func + return func + + def _has_code_flag(f, flag): + """Return true if ``f`` is a function (or a method or functools.partial + wrapper wrapping a function) whose code object has the given ``flag`` + set in its flags.""" + while ismethod(f): + f = f.__func__ + f = _unwrap_partial(f) + if not isfunction(f): + return False + return bool(f.__code__.co_flags & flag) + + def iscoroutinefunction(obj): + """Return true if the object is a coroutine function. + + Coroutine functions are defined with "async def" syntax. + """ + return ( + _has_code_flag(obj, CO_COROUTINE) or + getattr(obj, '_is_coroutine', None) is _is_coroutine + ) + +else: + + from asyncio import iscoroutinefunction + diff --git a/mock/mock.py b/mock/mock.py index 8a9fc9d0..f8bf32a8 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -37,6 +37,7 @@ from functools import wraps, partial from mock import IS_PYPY +from .backports import iscoroutinefunction _builtins = {name for name in dir(builtins) if not name.startswith('_')} @@ -49,12 +50,12 @@ def _is_async_obj(obj): if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): return False - return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) + return iscoroutinefunction(obj) or inspect.isawaitable(obj) def _is_async_func(func): if getattr(func, '__code__', None): - return asyncio.iscoroutinefunction(func) + return iscoroutinefunction(func) else: return False @@ -2113,7 +2114,7 @@ class AsyncMockMixin(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # asyncio.iscoroutinefunction() checks _is_coroutine property to say if an + # iscoroutinefunction() checks _is_coroutine property to say if an # object is a coroutine. Without this check it looks to see if it is a # function/method, which in this case it is not (since it is an # AsyncMock). @@ -2284,7 +2285,7 @@ class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): recognized as an async function, and the result of a call is an awaitable: >>> mock = AsyncMock() - >>> asyncio.iscoroutinefunction(mock) + >>> iscoroutinefunction(mock) True >>> inspect.isawaitable(mock()) True From 702d35bd917f37a5105596d36dfa9a476d5f7155 Mon Sep 17 00:00:00 2001 From: Elizabeth Uselton Date: Fri, 13 Sep 2019 08:54:32 -0700 Subject: [PATCH 26/70] bpo-37555: Update _CallList.__contains__ to respect ANY (#14700) * Flip equality to use mock calls' __eq__ * bpo-37555: Regression test demonstrating assert_has_calls not working with ANY and spec_set Co-authored-by: Neal Finne * Revert "Flip equality to use mock calls' __eq__" This reverts commit 94ddf54c5a8aab7d00d9ab93e1cc5695c28d73e7. * bpo-37555: Add regression tests for mock ANY ordering issues Add regression tests for whether __eq__ is order agnostic on _Call and _CallList, which is useful for comparisons involving ANY, especially if the ANY comparison is to a class not defaulting __eq__ to NotImplemented. Co-authored-by: Neal Finne * bpo-37555: Fix _CallList and _Call order sensitivity _Call and _CallList depend on ordering to correctly process that an object being compared to ANY with __eq__ should return True. This fix updates the comparison to check both a == b and b == a and return True if either condition is met, fixing situations from the tests in the previous two commits where assertEqual would not be commutative if checking _Call or _CallList objects. This seems like a reasonable fix considering that the Python data model specifies that if an object doesn't know how to compare itself to another object it should return NotImplemented, and that on getting NotImplemented from a == b, it should try b == a, implying that good behavior for __eq__ is commutative. This also flips the order of comparison in _CallList's __contains__ method, guaranteeing ANY will be on the left and have it's __eq__ called for equality checking, fixing the interaction between assert_has_calls and ANY. Co-author: Neal Finne * bpo-37555: Ensure _call_matcher returns _Call object * Adding ACK and news entry * bpo-37555: Replacing __eq__ with == to sidestep NotImplemented bool(NotImplemented) returns True, so it's necessary to use == instead of __eq__ in this comparison. * bpo-37555: cleaning up changes unnecessary to the final product * bpo-37555: Fixed call on bound arguments to respect args and kwargs * Revert "bpo-37555: Add regression tests for mock ANY ordering issues" This reverts commit 49c5310ad493c4356dd3bc58c03653cd9466c4fa. * Revert "bpo-37555: cleaning up changes unnecessary to the final product" This reverts commit 18e964ba0126d8964d89842cb95534b63c2d326e. * Revert "bpo-37555: Replacing __eq__ with == to sidestep NotImplemented" This reverts commit f295eaca5bceac6636c0e2b10e6c7d9a8ee8296a. * Revert "bpo-37555: Fix _CallList and _Call order sensitivity" This reverts commit 874fb697b8376fcea130116e56189061f944fde6. * Updated NEWS.d * bpo-37555: Add tests checking every function using _call_matcher both with and without spec * bpo-37555: Ensure all assert methods using _call_matcher are actually passing calls * Remove AnyCompare and use call objects everywhere. * Revert "Remove AnyCompare and use call objects everywhere." This reverts commit 24973c0b32ce7d796a7f4eeaf259832222aae0f5. * Check for exception in assert_any_await Backports: d6a9d17d8b6c68073931dd8ffa213b4ac351a4ab Signed-off-by: Chris Withers --- .../2019-07-19-20-13-48.bpo-37555.S5am28.rst | 2 + mock/mock.py | 39 +++++++++++++------ mock/tests/testasync.py | 30 +++++++++++++- mock/tests/testhelpers.py | 21 ++++++++++ 4 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst diff --git a/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst b/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst new file mode 100644 index 00000000..16d1d62d --- /dev/null +++ b/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst @@ -0,0 +1,2 @@ +Fix `NonCallableMock._call_matcher` returning tuple instead of `_Call` object +when `self._spec_signature` exists. Patch by Elizabeth Uselton \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index f8bf32a8..03b2ce5d 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -854,7 +854,8 @@ def _call_matcher(self, _call): else: name, args, kwargs = _call try: - return name, sig.bind(*args, **kwargs) + bound_call = sig.bind(*args, **kwargs) + return call(name, bound_call.args, bound_call.kwargs) except TypeError as e: return e.with_traceback(None) else: @@ -907,9 +908,9 @@ def assert_called_with(_mock_self, *args, **kwargs): def _error_message(): msg = self._format_mock_failure_message(args, kwargs) return msg - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) actual = self._call_matcher(self.call_args) - if expected != actual: + if actual != expected: cause = expected if isinstance(expected, Exception) else None raise AssertionError(_error_message()) from cause @@ -970,10 +971,10 @@ def assert_any_call(self, *args, **kwargs): The assert passes if the mock has *ever* been called, unlike `assert_called_with` and `assert_called_once_with` that only pass if the call is the most recent one.""" - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None actual = [self._call_matcher(c) for c in self.call_args_list] - if expected not in actual: - cause = expected if isinstance(expected, Exception) else None + if cause or expected not in _AnyComparer(actual): expected_string = self._format_mock_call_signature(args, kwargs) raise AssertionError( '%s call not found' % expected_string @@ -1026,6 +1027,22 @@ def _calls_repr(self, prefix="Calls"): return f"\n{prefix}: {safe_repr(self.mock_calls)}." +class _AnyComparer(list): + """A list which checks if it contains a call which may have an + argument of ANY, flipping the components of item and self from + their traditional locations so that ANY is guaranteed to be on + the left.""" + def __contains__(self, item): + for _call in self: + if len(item) != len(_call): + continue + if all([ + expected == actual + for expected, actual in zip(item, _call) + ]): + return True + return False + def _try_iter(obj): if obj is None: @@ -2187,9 +2204,9 @@ def _error_message(): msg = self._format_mock_failure_message(args, kwargs, action='await') return msg - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) actual = self._call_matcher(self.await_args) - if expected != actual: + if actual != expected: cause = expected if isinstance(expected, Exception) else None raise AssertionError(_error_message()) from cause @@ -2210,10 +2227,10 @@ def assert_any_await(_mock_self, *args, **kwargs): Assert the mock has ever been awaited with the specified arguments. """ self = _mock_self - expected = self._call_matcher((args, kwargs)) + expected = self._call_matcher(_Call((args, kwargs), two=True)) + cause = expected if isinstance(expected, Exception) else None actual = [self._call_matcher(c) for c in self.await_args_list] - if expected not in actual: - cause = expected if isinstance(expected, Exception) else None + if cause or expected not in _AnyComparer(actual): expected_string = self._format_mock_call_signature(args, kwargs) raise AssertionError( '%s await not found' % expected_string diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index e6f923e2..71979e23 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -3,7 +3,7 @@ import inspect import unittest -from mock import call, AsyncMock, patch, MagicMock, create_autospec +from mock import ANY, call, AsyncMock, patch, MagicMock, create_autospec from mock.mock import _AwaitEvent @@ -205,6 +205,10 @@ async def main(): spec.assert_awaited_with(1, 2, c=3) spec.assert_awaited() + with self.assertRaises(AssertionError): + spec.assert_any_await(e=1) + + def test_patch_with_autospec(self): async def test_async(): @@ -620,6 +624,30 @@ def test_assert_has_awaits_no_order(self): run(self._runnable_test('SomethingElse')) self.mock.assert_has_awaits(calls) + def test_awaits_asserts_with_any(self): + class Foo: + def __eq__(self, other): pass + + run(self._runnable_test(Foo(), 1)) + + self.mock.assert_has_awaits([call(ANY, 1)]) + self.mock.assert_awaited_with(ANY, 1) + self.mock.assert_any_await(ANY, 1) + + def test_awaits_asserts_with_spec_and_any(self): + class Foo: + def __eq__(self, other): pass + + mock_with_spec = AsyncMock(spec=Foo) + + async def _custom_mock_runnable_test(*args): + await mock_with_spec(*args) + + run(_custom_mock_runnable_test(Foo(), 1)) + mock_with_spec.assert_has_awaits([call(ANY, 1)]) + mock_with_spec.assert_awaited_with(ANY, 1) + mock_with_spec.assert_any_await(ANY, 1) + def test_assert_has_awaits_ordered(self): calls = [call('NormalFoo'), call('baz')] with self.assertRaises(AssertionError): diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 8b4d7092..ae4c8479 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -68,7 +68,28 @@ def __ne__(self, other): pass self.assertEqual(expected, mock.mock_calls) self.assertEqual(mock.mock_calls, expected) + def test_any_no_spec(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock() + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) + + def test_any_and_spec_set(self): + # This is a regression test for bpo-37555 + class Foo: + def __eq__(self, other): pass + + mock = Mock(spec=Foo) + mock(Foo(), 1) + mock.assert_has_calls([call(ANY, 1)]) + mock.assert_called_with(ANY, 1) + mock.assert_any_call(ANY, 1) class CallTest(unittest.TestCase): From 21ea393db3c288d6d1450a90c1b07d11d0c0f02f Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Fri, 13 Sep 2019 18:40:56 +0200 Subject: [PATCH 27/70] bpo-38122: minor fixes to AsyncMock spec handling (GH-16099) Backports: 14fd925a18fe3db0922a7d798e373102fe7a8a9c Signed-off-by: Chris Withers --- mock/mock.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 03b2ce5d..671d9241 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -404,18 +404,12 @@ def __new__(cls, *args, **kw): # so we can create magic methods on the # class without stomping on other mocks bases = (cls,) - if not issubclass(cls, AsyncMock): + if not issubclass(cls, AsyncMockMixin): # Check if spec is an async object or function - sig = inspect.signature(NonCallableMock.__init__) - bound_args = sig.bind_partial(cls, *args, **kw).arguments - spec_arg = [ - arg for arg in bound_args.keys() - if arg.startswith('spec') - ] - if spec_arg: - # what if spec_set is different than spec? - if _is_async_obj(bound_args[spec_arg[0]]): - bases = (AsyncMockMixin, cls,) + bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments + spec_arg = bound_args.get('spec_set', bound_args.get('spec')) + if spec_arg and _is_async_obj(spec_arg): + bases = (AsyncMockMixin, cls) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = object.__new__(new) return instance @@ -1027,6 +1021,9 @@ def _calls_repr(self, prefix="Calls"): return f"\n{prefix}: {safe_repr(self.mock_calls)}." +_MOCK_SIG = inspect.signature(NonCallableMock.__init__) + + class _AnyComparer(list): """A list which checks if it contains a call which may have an argument of ANY, flipping the components of item and self from From 14aaccb5db4b977fe6968807f51a7b6286def5b3 Mon Sep 17 00:00:00 2001 From: marcoramirezmx <55331462+marcoramirezmx@users.noreply.github.com> Date: Mon, 16 Sep 2019 11:34:46 -0500 Subject: [PATCH 28/70] bpo-38100: Fix spelling error in unittest.mock code (GH-16168) Backports: a9187c31185fe7ea47271839898416400cc3d976 Signed-off-by: Chris Withers --- mock/mock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 671d9241..e012871b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2000,9 +2000,9 @@ def _set_return_value(mock, method, name): method.return_value = fixed return - return_calulator = _calculate_return_value.get(name) - if return_calulator is not None: - return_value = return_calulator(mock) + return_calculator = _calculate_return_value.get(name) + if return_calculator is not None: + return_value = return_calculator(mock) method.return_value = return_value return From 3b149c9b1e8be34c12710ed88f5e73a6469b0215 Mon Sep 17 00:00:00 2001 From: Abraham Toriz Cruz Date: Tue, 17 Sep 2019 06:16:08 -0500 Subject: [PATCH 29/70] bpo-37828: Fix default mock_name in unittest.mock.assert_called error (GH-16166) In the format string for assert_called the evaluation order is incorrect and hence for mock's without name, 'None' is printed whereas it should be 'mock' like for other messages. The error message is ("Expected '%s' to have been called." % self._mock_name or 'mock'). Backports: 5f5f11faf9de0d8dcbe1a8a4eb35d2a4232d6eaa Signed-off-by: Chris Withers --- NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst | 2 ++ lastsync.txt | 2 +- mock/mock.py | 2 +- mock/tests/testmock.py | 8 ++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst diff --git a/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst b/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst new file mode 100644 index 00000000..c364009b --- /dev/null +++ b/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst @@ -0,0 +1,2 @@ +Fix default mock name in :meth:`unittest.mock.Mock.assert_called` exceptions. +Patch by Abraham Toriz Cruz. diff --git a/lastsync.txt b/lastsync.txt index 4b3f4da8..b7a4c8dc 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -39d87b54715197ca9dcb6902bb43461c0ed701a2 +a9187c31185fe7ea47271839898416400cc3d976 diff --git a/mock/mock.py b/mock/mock.py index e012871b..7971897b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -872,7 +872,7 @@ def assert_called(_mock_self): self = _mock_self if self.call_count == 0: msg = ("Expected '%s' to have been called." % - self._mock_name or 'mock') + (self._mock_name or 'mock')) raise AssertionError(msg) def assert_called_once(_mock_self): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 3947cd69..8ab87b01 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -396,6 +396,14 @@ def _check(mock): _check(mock) + def test_assert_called_exception_message(self): + msg = "Expected '{0}' to have been called" + with self.assertRaisesRegex(AssertionError, msg.format('mock')): + Mock().assert_called() + with self.assertRaisesRegex(AssertionError, msg.format('test_name')): + Mock(name="test_name").assert_called() + + def test_assert_called_once_with(self): mock = Mock() mock() From 136175ad1590c5a11629346931759868213e1416 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Thu, 19 Sep 2019 21:04:18 -0700 Subject: [PATCH 30/70] bpo-38093: Correctly returns AsyncMock for async subclasses. (GH-15947) Backports: 8b03f943c37e07fb2394acdcfacd066647f9b1fd Signed-off-by: Chris Withers --- .../2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst | 2 + mock/mock.py | 18 +- mock/tests/testasync.py | 163 ++++++++++++------ mock/tests/testmagicmethods.py | 35 +++- 4 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst diff --git a/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst b/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst new file mode 100644 index 00000000..24a53013 --- /dev/null +++ b/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst @@ -0,0 +1,2 @@ +Fixes AsyncMock so it doesn't crash when used with AsyncContextManagers +or AsyncIterators. diff --git a/mock/mock.py b/mock/mock.py index 7971897b..85746a74 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -990,9 +990,13 @@ def _get_child_mock(self, **kw): _type = type(self) if issubclass(_type, MagicMock) and _new_name in _async_method_magics: klass = AsyncMock - if issubclass(_type, AsyncMockMixin): + elif _new_name in _sync_async_magics: + # Special case these ones b/c users will assume they are async, + # but they are actually sync (ie. __aiter__) klass = MagicMock - if not issubclass(_type, CallableMixin): + elif issubclass(_type, AsyncMockMixin): + klass = AsyncMock + elif not issubclass(_type, CallableMixin): if issubclass(_type, NonCallableMagicMock): klass = MagicMock elif issubclass(_type, NonCallableMock) : @@ -1893,7 +1897,7 @@ def _patch_stopall(): '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', '__getstate__', '__setstate__', '__getformat__', '__setformat__', '__repr__', '__dir__', '__subclasses__', '__format__', - '__getnewargs_ex__', '__aenter__', '__aexit__', '__anext__', '__aiter__', + '__getnewargs_ex__', } @@ -1912,10 +1916,12 @@ def method(self, *args, **kw): # Magic methods used for async `with` statements _async_method_magics = {"__aenter__", "__aexit__", "__anext__"} -# `__aiter__` is a plain function but used with async calls -_async_magics = _async_method_magics | {"__aiter__"} +# Magic methods that are only used with async calls but are synchronous functions themselves +_sync_async_magics = {"__aiter__"} +_async_magics = _async_method_magics | _sync_async_magics -_all_magics = _magics | _non_defaults +_all_sync_magics = _magics | _non_defaults +_all_magics = _all_sync_magics | _async_magics _unsupported_magics = { '__getattr__', '__setattr__', diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 71979e23..87b4878f 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -395,36 +395,89 @@ def test_add_side_effect_iterable(self): class AsyncContextManagerTest(unittest.TestCase): class WithAsyncContextManager: - async def __aenter__(self, *args, **kwargs): return self async def __aexit__(self, *args, **kwargs): pass - def test_magic_methods_are_async_mocks(self): - mock = MagicMock(self.WithAsyncContextManager()) - self.assertIsInstance(mock.__aenter__, AsyncMock) - self.assertIsInstance(mock.__aexit__, AsyncMock) + class WithSyncContextManager: + def __enter__(self, *args, **kwargs): + return self + + def __exit__(self, *args, **kwargs): + pass + + class ProductionCode: + # Example real-world(ish) code + def __init__(self): + self.session = None + + async def main(self): + async with self.session.post('https://python.org') as response: + val = await response.json() + return val + + def test_async_magic_methods_are_async_mocks_with_magicmock(self): + cm_mock = MagicMock(self.WithAsyncContextManager()) + self.assertIsInstance(cm_mock.__aenter__, AsyncMock) + self.assertIsInstance(cm_mock.__aexit__, AsyncMock) + + def test_magicmock_has_async_magic_methods(self): + cm = MagicMock(name='magic_cm') + self.assertTrue(hasattr(cm, "__aenter__")) + self.assertTrue(hasattr(cm, "__aexit__")) + + def test_magic_methods_are_async_functions(self): + cm = MagicMock(name='magic_cm') + self.assertIsInstance(cm.__aenter__, AsyncMock) + self.assertIsInstance(cm.__aexit__, AsyncMock) + # AsyncMocks are also coroutine functions + self.assertTrue(asyncio.iscoroutinefunction(cm.__aenter__)) + self.assertTrue(asyncio.iscoroutinefunction(cm.__aexit__)) + + def test_set_return_value_of_aenter(self): + def inner_test(mock_type): + pc = self.ProductionCode() + pc.session = MagicMock(name='sessionmock') + cm = mock_type(name='magic_cm') + response = AsyncMock(name='response') + response.json = AsyncMock(return_value={'json': 123}) + cm.__aenter__.return_value = response + pc.session.post.return_value = cm + result = run(pc.main()) + self.assertEqual(result, {'json': 123}) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test set return value of aenter with {mock_type}"): + inner_test(mock_type) def test_mock_supports_async_context_manager(self): - called = False - instance = self.WithAsyncContextManager() - mock_instance = MagicMock(instance) - async def use_context_manager(): - nonlocal called - async with mock_instance as result: - called = True - return result + def inner_test(mock_type): + called = False + cm = self.WithAsyncContextManager() + cm_mock = mock_type(cm) - result = run(use_context_manager()) - self.assertTrue(called) - self.assertTrue(mock_instance.__aenter__.called) - self.assertTrue(mock_instance.__aexit__.called) - self.assertIsNot(mock_instance, result) - self.assertIsInstance(result, AsyncMock) + async def use_context_manager(): + nonlocal called + async with cm_mock as result: + called = True + return result + cm_result = run(use_context_manager()) + self.assertTrue(called) + self.assertTrue(cm_mock.__aenter__.called) + self.assertTrue(cm_mock.__aexit__.called) + cm_mock.__aenter__.assert_awaited() + cm_mock.__aexit__.assert_awaited() + # We mock __aenter__ so it does not return self + self.assertIsNot(cm_mock, cm_result) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test context manager magics with {mock_type}"): + inner_test(mock_type) + def test_mock_customize_async_context_manager(self): instance = self.WithAsyncContextManager() mock_instance = MagicMock(instance) @@ -491,27 +544,30 @@ async def __anext__(self): raise StopAsyncIteration - def test_mock_aiter_and_anext(self): - instance = self.WithAsyncIterator() - mock_instance = MagicMock(instance) - - self.assertEqual(asyncio.iscoroutine(instance.__aiter__), - asyncio.iscoroutine(mock_instance.__aiter__)) - self.assertEqual(asyncio.iscoroutine(instance.__anext__), - asyncio.iscoroutine(mock_instance.__anext__)) - - iterator = instance.__aiter__() - if asyncio.iscoroutine(iterator): - iterator = run(iterator) - - mock_iterator = mock_instance.__aiter__() - if asyncio.iscoroutine(mock_iterator): - mock_iterator = run(mock_iterator) + def test_aiter_set_return_value(self): + mock_iter = AsyncMock(name="tester") + mock_iter.__aiter__.return_value = [1, 2, 3] + async def main(): + return [i async for i in mock_iter] + result = run(main()) + self.assertEqual(result, [1, 2, 3]) + + def test_mock_aiter_and_anext_asyncmock(self): + def inner_test(mock_type): + instance = self.WithAsyncIterator() + mock_instance = mock_type(instance) + # Check that the mock and the real thing bahave the same + # __aiter__ is not actually async, so not a coroutinefunction + self.assertFalse(asyncio.iscoroutinefunction(instance.__aiter__)) + self.assertFalse(asyncio.iscoroutinefunction(mock_instance.__aiter__)) + # __anext__ is async + self.assertTrue(asyncio.iscoroutinefunction(instance.__anext__)) + self.assertTrue(asyncio.iscoroutinefunction(mock_instance.__anext__)) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test aiter and anext corourtine with {mock_type}"): + inner_test(mock_type) - self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), - asyncio.iscoroutine(mock_iterator.__aiter__)) - self.assertEqual(asyncio.iscoroutine(iterator.__anext__), - asyncio.iscoroutine(mock_iterator.__anext__)) def test_mock_async_for(self): async def iterate(iterator): @@ -522,19 +578,30 @@ async def iterate(iterator): return accumulator expected = ["FOO", "BAR", "BAZ"] - with self.subTest("iterate through default value"): - mock_instance = MagicMock(self.WithAsyncIterator()) - self.assertEqual([], run(iterate(mock_instance))) + def test_default(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) + self.assertEqual(run(iterate(mock_instance)), []) + - with self.subTest("iterate through set return_value"): - mock_instance = MagicMock(self.WithAsyncIterator()) + def test_set_return_value(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) mock_instance.__aiter__.return_value = expected[:] - self.assertEqual(expected, run(iterate(mock_instance))) + self.assertEqual(run(iterate(mock_instance)), expected) - with self.subTest("iterate through set return_value iterator"): - mock_instance = MagicMock(self.WithAsyncIterator()) + def test_set_return_value_iter(mock_type): + mock_instance = mock_type(self.WithAsyncIterator()) mock_instance.__aiter__.return_value = iter(expected[:]) - self.assertEqual(expected, run(iterate(mock_instance))) + self.assertEqual(run(iterate(mock_instance)), expected) + + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"default value with {mock_type}"): + test_default(mock_type) + + with self.subTest(f"set return_value with {mock_type}"): + test_set_return_value(mock_type) + + with self.subTest(f"set return_value iterator with {mock_type}"): + test_set_return_value_iter(mock_type) class AsyncMockAssert(unittest.TestCase): diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index fdb6f196..d256dd3e 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -2,7 +2,8 @@ import unittest import os import sys -from mock import Mock, MagicMock +from mock import AsyncMock, Mock, MagicMock +from mock.backports import iscoroutinefunction from mock.mock import _magics @@ -271,6 +272,34 @@ def test_magic_mock_equality(self): self.assertEqual(mock != mock, False) + # This should be fixed with issue38163 + @unittest.expectedFailure + def test_asyncmock_defaults(self): + mock = AsyncMock() + self.assertEqual(int(mock), 1) + self.assertEqual(complex(mock), 1j) + self.assertEqual(float(mock), 1.0) + self.assertNotIn(object(), mock) + self.assertEqual(len(mock), 0) + self.assertEqual(list(mock), []) + self.assertEqual(hash(mock), object.__hash__(mock)) + self.assertEqual(str(mock), object.__str__(mock)) + self.assertTrue(bool(mock)) + self.assertEqual(round(mock), mock.__round__()) + self.assertEqual(math.trunc(mock), mock.__trunc__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + # in Python 3 oct and hex use __index__ + # so these tests are for __index__ in py3k + self.assertEqual(oct(mock), '0o1') + self.assertEqual(hex(mock), '0x1') + # how to test __sizeof__ ? + def test_magicmock_defaults(self): mock = MagicMock() self.assertEqual(int(mock), 1) @@ -286,6 +315,10 @@ def test_magicmock_defaults(self): self.assertEqual(math.trunc(mock), mock.__trunc__()) self.assertEqual(math.floor(mock), mock.__floor__()) self.assertEqual(math.ceil(mock), mock.__ceil__()) + self.assertTrue(iscoroutinefunction(mock.__aexit__)) + self.assertTrue(iscoroutinefunction(mock.__aenter__)) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) # in Python 3 oct and hex use __index__ # so these tests are for __index__ in py3k From d66e9d73d9d0894182aec074a6fb3c674788ff47 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Mon, 23 Sep 2019 20:49:40 -0700 Subject: [PATCH 31/70] bpo-38136: Updates await_count and call_count to be different things (GH-16192) Backports: ef048517755db1f0d211fb6dfc655a8b412cc96f Signed-off-by: Chris Withers --- .../2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst | 3 + mock/mock.py | 11 + mock/tests/testasync.py | 199 ++++++++++++++++-- mock/tests/testmock.py | 3 +- 4 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst diff --git a/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst b/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst new file mode 100644 index 00000000..78cad245 --- /dev/null +++ b/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst @@ -0,0 +1,3 @@ +Changes AsyncMock call count and await count to be two different counters. +Now await count only counts when a coroutine has been awaited, not when it +has been called, and vice-versa. Update the documentation around this. diff --git a/mock/mock.py b/mock/mock.py index 85746a74..9cfc705a 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1083,15 +1083,21 @@ def __call__(_mock_self, *args, **kwargs): # can't use self in-case a function / method we are mocking uses self # in the signature _mock_self._mock_check_sig(*args, **kwargs) + _mock_self._increment_mock_call(*args, **kwargs) return _mock_self._mock_call(*args, **kwargs) def _mock_call(_mock_self, *args, **kwargs): + return _mock_self._execute_mock_call(*args, **kwargs) + + def _increment_mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 # handle call_args + # needs to be set here so assertions on call arguments pass before + # execution in the case of awaited calls _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) @@ -1131,6 +1137,11 @@ def _mock_call(_mock_self, *args, **kwargs): # follow the parental chain: _new_parent = _new_parent._mock_new_parent + def _execute_mock_call(_mock_self, *args, **kwargs): + self = _mock_self + # seperate from _increment_mock_call so that awaited functions are + # executed seperately from their call + effect = self.side_effect if effect is not None: if _is_exception(effect): diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 87b4878f..9aea9310 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -3,8 +3,9 @@ import inspect import unittest -from mock import ANY, call, AsyncMock, patch, MagicMock, create_autospec -from mock.mock import _AwaitEvent +from mock import (ANY, call, AsyncMock, patch, MagicMock, + create_autospec, sentinel) +from mock.mock import _AwaitEvent, _CallList try: @@ -608,11 +609,173 @@ class AsyncMockAssert(unittest.TestCase): def setUp(self): self.mock = AsyncMock() - async def _runnable_test(self, *args): - if not args: - await self.mock() - else: - await self.mock(*args) + async def _runnable_test(self, *args, **kwargs): + await self.mock(*args, **kwargs) + + async def _await_coroutine(self, coroutine): + return await coroutine + + def test_assert_called_but_not_awaited(self): + mock = AsyncMock(AsyncClass) + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + mock.async_method() + self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.assert_awaited() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + def test_assert_called_then_awaited(self): + mock = AsyncMock(AsyncClass) + mock_coroutine = mock.async_method() + mock.async_method.assert_called() + mock.async_method.assert_called_once() + mock.async_method.assert_called_once_with() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + + run(self._await_coroutine(mock_coroutine)) + # Assert we haven't re-called the function + mock.async_method.assert_called_once() + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + mock.async_method.assert_awaited_once_with() + + def test_assert_called_and_awaited_at_same_time(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + + with self.assertRaises(AssertionError): + self.mock.assert_called() + + run(self._runnable_test()) + self.mock.assert_called_once() + self.mock.assert_awaited_once() + + def test_assert_called_twice_and_awaited_once(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + with self.assertWarns(RuntimeWarning): + # The first call will be awaited so no warning there + # But this call will never get awaited, so it will warn here + mock.async_method() + with self.assertRaises(AssertionError): + mock.async_method.assert_awaited() + mock.async_method.assert_called() + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + mock.async_method.assert_awaited_once() + + def test_assert_called_once_and_awaited_twice(self): + mock = AsyncMock(AsyncClass) + coroutine = mock.async_method() + mock.async_method.assert_called_once() + run(self._await_coroutine(coroutine)) + with self.assertRaises(RuntimeError): + # Cannot reuse already awaited coroutine + run(self._await_coroutine(coroutine)) + mock.async_method.assert_awaited() + + def test_assert_awaited_but_not_called(self): + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + with self.assertRaises(TypeError): + # You cannot await an AsyncMock, it must be a coroutine + run(self._await_coroutine(self.mock)) + + with self.assertRaises(AssertionError): + self.mock.assert_awaited() + with self.assertRaises(AssertionError): + self.mock.assert_called() + + def test_assert_has_calls_not_awaits(self): + kalls = [call('foo')] + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + self.mock('foo') + self.mock.assert_has_calls(kalls) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(kalls) + + def test_assert_has_mock_calls_on_async_mock_no_spec(self): + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + self.mock() + kalls_empty = [('', (), {})] + self.assertEqual(self.mock.mock_calls, kalls_empty) + + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + self.mock('foo') + self.mock('baz') + mock_kalls = ([call(), call('foo'), call('baz')]) + self.assertEqual(self.mock.mock_calls, mock_kalls) + + def test_assert_has_mock_calls_on_async_mock_with_spec(self): + a_class_mock = AsyncMock(AsyncClass) + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + a_class_mock.async_method() + kalls_empty = [('', (), {})] + self.assertEqual(a_class_mock.async_method.mock_calls, kalls_empty) + self.assertEqual(a_class_mock.mock_calls, [call.async_method()]) + + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + a_class_mock.async_method(1, 2, 3, a=4, b=5) + method_kalls = [call(), call(1, 2, 3, a=4, b=5)] + mock_kalls = [call.async_method(), call.async_method(1, 2, 3, a=4, b=5)] + self.assertEqual(a_class_mock.async_method.mock_calls, method_kalls) + self.assertEqual(a_class_mock.mock_calls, mock_kalls) + + def test_async_method_calls_recorded(self): + with self.assertWarns(RuntimeWarning): + # Will raise warnings because never awaited + self.mock.something(3, fish=None) + self.mock.something_else.something(6, cake=sentinel.Cake) + + self.assertEqual(self.mock.method_calls, [ + ("something", (3,), {'fish': None}), + ("something_else.something", (6,), {'cake': sentinel.Cake}) + ], + "method calls not recorded correctly") + self.assertEqual(self.mock.something_else.method_calls, + [("something", (6,), {'cake': sentinel.Cake})], + "method calls not recorded correctly") + + def test_async_arg_lists(self): + def assert_attrs(mock): + names = ('call_args_list', 'method_calls', 'mock_calls') + for name in names: + attr = getattr(mock, name) + self.assertIsInstance(attr, _CallList) + self.assertIsInstance(attr, list) + self.assertEqual(attr, []) + + assert_attrs(self.mock) + with self.assertWarns(RuntimeWarning): + # Will raise warnings because never awaited + self.mock() + self.mock(1, 2) + self.mock(a=3) + + self.mock.reset_mock() + assert_attrs(self.mock) + + a_mock = AsyncMock(AsyncClass) + with self.assertWarns(RuntimeWarning): + # Will raise warnings because never awaited + a_mock.async_method() + a_mock.async_method(1, a=3) + + a_mock.reset_mock() + assert_attrs(a_mock) def test_assert_awaited(self): with self.assertRaises(AssertionError): @@ -658,20 +821,20 @@ def test_assert_awaited_once_with(self): def test_assert_any_wait(self): with self.assertRaises(AssertionError): - self.mock.assert_any_await('NormalFoo') + self.mock.assert_any_await('foo') - run(self._runnable_test('foo')) + run(self._runnable_test('baz')) with self.assertRaises(AssertionError): - self.mock.assert_any_await('NormalFoo') + self.mock.assert_any_await('foo') - run(self._runnable_test('NormalFoo')) - self.mock.assert_any_await('NormalFoo') + run(self._runnable_test('foo')) + self.mock.assert_any_await('foo') run(self._runnable_test('SomethingElse')) - self.mock.assert_any_await('NormalFoo') + self.mock.assert_any_await('foo') def test_assert_has_awaits_no_order(self): - calls = [call('NormalFoo'), call('baz')] + calls = [call('foo'), call('baz')] with self.assertRaises(AssertionError) as cm: self.mock.assert_has_awaits(calls) @@ -681,7 +844,7 @@ def test_assert_has_awaits_no_order(self): with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - run(self._runnable_test('NormalFoo')) + run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) @@ -716,7 +879,7 @@ async def _custom_mock_runnable_test(*args): mock_with_spec.assert_any_await(ANY, 1) def test_assert_has_awaits_ordered(self): - calls = [call('NormalFoo'), call('baz')] + calls = [call('foo'), call('baz')] with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) @@ -724,11 +887,11 @@ def test_assert_has_awaits_ordered(self): with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - run(self._runnable_test('foo')) + run(self._runnable_test('bamf')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - run(self._runnable_test('NormalFoo')) + run(self._runnable_test('foo')) self.mock.assert_has_awaits(calls, any_order=True) run(self._runnable_test('qux')) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 8ab87b01..67a8047c 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -850,6 +850,7 @@ def test(): def test_setting_call(self): mock = Mock() def __call__(self, a): + self._increment_mock_call(a) return self._mock_call(a) type(mock).__call__ = __call__ @@ -2025,7 +2026,7 @@ def trace(frame, event, arg): # pragma: no cover ) mocks = [ - Mock, MagicMock, NonCallableMock, NonCallableMagicMock + Mock, MagicMock, NonCallableMock, NonCallableMagicMock, AsyncMock ] for mock in mocks: From e702e61a24abe7dfff374875358669fdbfaa2aa2 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 22 Jan 2020 07:25:16 +0000 Subject: [PATCH 32/70] turn warnings for async stuff into errors. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 647943ca..d70c78e8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,4 +44,5 @@ universal = 1 [tool:pytest] python_files=test*.py filterwarnings = + error::RuntimeWarning ignore::DeprecationWarning From a0e820d377a4724f5d1478a76a1b6ffbb6551e0c Mon Sep 17 00:00:00 2001 From: Samuel Freilich Date: Tue, 24 Sep 2019 15:08:31 -0400 Subject: [PATCH 33/70] bpo-36871: Handle spec errors in assert_has_calls (GH-16005) The fix in PR 13261 handled the underlying issue about the spec for specific methods not being applied correctly, but it didn't fix the issue that was causing the misleading error message. The code currently grabs a list of responses from _call_matcher (which may include exceptions). But it doesn't reach inside the list when checking if the result is an exception. This results in a misleading error message when one of the provided calls does not match the spec. https://bugs.python.org/issue36871 Automerge-Triggered-By: @gpshead Backports: b5a7a4f0c20717a4c92c371583b5521b83f40f32 Signed-off-by: Chris Withers --- .../2019-09-24-18-45-46.bpo-36871.p47knk.rst | 3 +++ mock/mock.py | 26 +++++++++++++++---- mock/tests/testasync.py | 21 +++++++++++++++ mock/tests/testmock.py | 19 ++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst diff --git a/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst b/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst new file mode 100644 index 00000000..6b7b19a0 --- /dev/null +++ b/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst @@ -0,0 +1,3 @@ +Improve error handling for the assert_has_calls and assert_has_awaits methods of +mocks. Fixed a bug where any errors encountered while binding the expected calls +to the mock's spec were silently swallowed, leading to misleading error output. diff --git a/mock/mock.py b/mock/mock.py index 9cfc705a..5337fe8a 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -933,13 +933,21 @@ def assert_has_calls(self, calls, any_order=False): If `any_order` is True then the calls can be in any order, but they must all appear in `mock_calls`.""" expected = [self._call_matcher(c) for c in calls] - cause = expected if isinstance(expected, Exception) else None + cause = next((e for e in expected if isinstance(e, Exception)), None) all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls) if not any_order: if expected not in all_calls: + if cause is None: + problem = 'Calls not found.' + else: + problem = ('Error processing expected calls.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) raise AssertionError( - 'Calls not found.\nExpected: %r%s' - % (_CallList(calls), self._calls_repr(prefix="Actual")) + f'{problem}\n' + f'Expected: {_CallList(calls)}\n' + f'Actual: {self._calls_repr(prefix="Actual")}' ) from cause return @@ -2264,12 +2272,20 @@ def assert_has_awaits(_mock_self, calls, any_order=False): """ self = _mock_self expected = [self._call_matcher(c) for c in calls] - cause = expected if isinstance(expected, Exception) else None + cause = cause = next((e for e in expected if isinstance(e, Exception)), None) all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) if not any_order: if expected not in all_awaits: + if cause is None: + problem = 'Awaits not found.' + else: + problem = ('Error processing expected awaits.\n' + 'Errors: {}').format( + [e if isinstance(e, Exception) else None + for e in expected]) raise AssertionError( - f'Awaits not found.\nExpected: {_CallList(calls)}\n' + f'{problem}\n' + f'Expected: {_CallList(calls)}\n' f'Actual: {self.await_args_list}' ) from cause return diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 9aea9310..41d1b01b 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -1,6 +1,7 @@ import asyncio import inspect +import re import unittest from mock import (ANY, call, AsyncMock, patch, MagicMock, @@ -903,3 +904,23 @@ def test_assert_not_awaited(self): run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_not_awaited() + + def test_assert_has_awaits_not_matching_spec_error(self): + async def f(): pass + + mock = AsyncMock(spec=f) + + with self.assertRaisesRegex( + AssertionError, + re.escape('Awaits not found.\nExpected:')) as cm: + mock.assert_has_awaits([call()]) + self.assertIsNone(cm.exception.__cause__) + + with self.assertRaisesRegex( + AssertionError, + re.escape('Error processing expected awaits.\n' + "Errors: [None, TypeError('too many positional " + "arguments')]\n" + 'Expected:')) as cm: + mock.assert_has_awaits([call(), call('wrong')]) + self.assertIsInstance(cm.exception.__cause__, TypeError) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 67a8047c..880cc1ee 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1435,6 +1435,25 @@ def f(a, b, c, d=None): pass mock.assert_has_calls(calls[:-1]) mock.assert_has_calls(calls[:-1], any_order=True) + def test_assert_has_calls_not_matching_spec_error(self): + def f(): pass + + mock = Mock(spec=f) + + with self.assertRaisesRegex( + AssertionError, + re.escape('Calls not found.\nExpected:')) as cm: + mock.assert_has_calls([call()]) + self.assertIsNone(cm.exception.__cause__) + + with self.assertRaisesRegex( + AssertionError, + re.escape('Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional " + "arguments')]\n" + 'Expected:')) as cm: + mock.assert_has_calls([call(), call('wrong')]) + self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_any_call(self): mock = Mock() From 3bdebf260ea3b7143e4946863fafe63dbe8cb5ee Mon Sep 17 00:00:00 2001 From: Samuel Freilich Date: Tue, 24 Sep 2019 18:04:29 -0400 Subject: [PATCH 34/70] bpo-36871: Avoid duplicated 'Actual:' in assertion message (GH-16361) Fixes an issue caught after merge of PR 16005. Tightened test assertions to check the entire assertion message. Backports: 2180f6b058effbf49ec819f7cedbe76ddd4b700c Signed-off-by: Chris Withers --- mock/mock.py | 4 ++-- mock/tests/testasync.py | 25 ++++++++++++++++--------- mock/tests/testmock.py | 21 ++++++++++++++------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 5337fe8a..f862ff99 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -946,8 +946,8 @@ def assert_has_calls(self, calls, any_order=False): for e in expected]) raise AssertionError( f'{problem}\n' - f'Expected: {_CallList(calls)}\n' - f'Actual: {self._calls_repr(prefix="Actual")}' + f'Expected: {_CallList(calls)}' + f'{self._calls_repr(prefix="Actual").rstrip(".")}' ) from cause return diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 41d1b01b..981b8011 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -906,21 +906,28 @@ def test_assert_not_awaited(self): self.mock.assert_not_awaited() def test_assert_has_awaits_not_matching_spec_error(self): - async def f(): pass + async def f(x=None): pass - mock = AsyncMock(spec=f) + self.mock = AsyncMock(spec=f) + asyncio.run(self._runnable_test(1)) with self.assertRaisesRegex( AssertionError, - re.escape('Awaits not found.\nExpected:')) as cm: - mock.assert_has_awaits([call()]) + '^{}$'.format( + re.escape('Awaits not found.\n' + 'Expected: [call()]\n' + 'Actual: [call(1)]'))) as cm: + self.mock.assert_has_awaits([call()]) self.assertIsNone(cm.exception.__cause__) with self.assertRaisesRegex( AssertionError, - re.escape('Error processing expected awaits.\n' - "Errors: [None, TypeError('too many positional " - "arguments')]\n" - 'Expected:')) as cm: - mock.assert_has_awaits([call(), call('wrong')]) + '^{}$'.format( + re.escape( + 'Error processing expected awaits.\n' + "Errors: [None, TypeError('too many positional " + "arguments')]\n" + 'Expected: [call(), call(1, 2)]\n' + 'Actual: [call(1)]'))) as cm: + self.mock.assert_has_awaits([call(), call(1, 2)]) self.assertIsInstance(cm.exception.__cause__, TypeError) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 880cc1ee..461670cd 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1436,23 +1436,30 @@ def f(a, b, c, d=None): pass mock.assert_has_calls(calls[:-1], any_order=True) def test_assert_has_calls_not_matching_spec_error(self): - def f(): pass + def f(x=None): pass mock = Mock(spec=f) + mock(1) with self.assertRaisesRegex( AssertionError, - re.escape('Calls not found.\nExpected:')) as cm: + '^{}$'.format( + re.escape('Calls not found.\n' + 'Expected: [call()]\n' + 'Actual: [call(1)]'))) as cm: mock.assert_has_calls([call()]) self.assertIsNone(cm.exception.__cause__) + with self.assertRaisesRegex( AssertionError, - re.escape('Error processing expected calls.\n' - "Errors: [None, TypeError('too many positional " - "arguments')]\n" - 'Expected:')) as cm: - mock.assert_has_calls([call(), call('wrong')]) + '^{}$'.format( + re.escape( + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments')]\n" + "Expected: [call(), call(1, 2)]\n" + 'Actual: [call(1)]'))) as cm: + mock.assert_has_calls([call(), call(1, 2)]) self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_any_call(self): From ee8bbd4effef511f527e27d1bf27978650b4ac9e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 22 Jan 2020 18:52:28 +0000 Subject: [PATCH 35/70] support for py3.6 --- mock/tests/testasync.py | 4 +++- mock/tests/testmock.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 981b8011..315322c5 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -928,6 +928,8 @@ async def f(x=None): pass "Errors: [None, TypeError('too many positional " "arguments')]\n" 'Expected: [call(), call(1, 2)]\n' - 'Actual: [call(1)]'))) as cm: + 'Actual: [call(1)]').replace( + "arguments\\'", "arguments\\',?") + )) as cm: self.mock.assert_has_awaits([call(), call(1, 2)]) self.assertIsInstance(cm.exception.__cause__, TypeError) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 461670cd..c97d7fc3 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1458,7 +1458,9 @@ def f(x=None): pass 'Error processing expected calls.\n' "Errors: [None, TypeError('too many positional arguments')]\n" "Expected: [call(), call(1, 2)]\n" - 'Actual: [call(1)]'))) as cm: + 'Actual: [call(1)]').replace( + "arguments\\'", "arguments\\',?" + ))) as cm: mock.assert_has_calls([call(), call(1, 2)]) self.assertIsInstance(cm.exception.__cause__, TypeError) From be7df8183b74c4c83da3321941a964b279976e8f Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sat, 28 Sep 2019 18:42:44 -0700 Subject: [PATCH 36/70] bpo-38108: Makes mock objects inherit from Base (GH-16060) Backports: 9a7d9519506ae807ca48ff02e2ea117ebac3450e Signed-off-by: Chris Withers --- .../2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst | 2 + mock/mock.py | 54 +++++++----------- mock/tests/testasync.py | 57 ++++++++++++------- mock/tests/testmagicmethods.py | 3 - 4 files changed, 59 insertions(+), 57 deletions(-) create mode 100644 NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst diff --git a/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst b/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst new file mode 100644 index 00000000..d7eea367 --- /dev/null +++ b/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst @@ -0,0 +1,2 @@ +Any synchronous magic methods on an AsyncMock now return a MagicMock. Any +asynchronous magic methods on a MagicMock now return an AsyncMock. diff --git a/mock/mock.py b/mock/mock.py index f862ff99..44ac03c7 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -411,7 +411,7 @@ def __new__(cls, *args, **kw): if spec_arg and _is_async_obj(spec_arg): bases = (AsyncMockMixin, cls) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) - instance = object.__new__(new) + instance = _safe_super(NonCallableMock, cls).__new__(new) return instance @@ -997,17 +997,18 @@ def _get_child_mock(self, **kw): _type = type(self) if issubclass(_type, MagicMock) and _new_name in _async_method_magics: + # Any asynchronous magic becomes an AsyncMock klass = AsyncMock - elif _new_name in _sync_async_magics: - # Special case these ones b/c users will assume they are async, - # but they are actually sync (ie. __aiter__) - klass = MagicMock elif issubclass(_type, AsyncMockMixin): - klass = AsyncMock + if _new_name in _all_sync_magics: + # Any synchronous magic becomes a MagicMock + klass = MagicMock + else: + klass = AsyncMock elif not issubclass(_type, CallableMixin): if issubclass(_type, NonCallableMagicMock): klass = MagicMock - elif issubclass(_type, NonCallableMock) : + elif issubclass(_type, NonCallableMock): klass = Mock else: klass = _type.__mro__[1] @@ -1895,6 +1896,7 @@ def _patch_stopall(): "round trunc floor ceil " "bool next " "fspath " + "aiter " ) if IS_PYPY: @@ -2037,7 +2039,7 @@ def _set_return_value(mock, method, name): -class MagicMixin(object): +class MagicMixin(Base): def __init__(self, *args, **kw): self._mock_set_magics() # make magic work for kwargs in init _safe_super(MagicMixin, self).__init__(*args, **kw) @@ -2045,13 +2047,14 @@ def __init__(self, *args, **kw): def _mock_set_magics(self): - these_magics = _magics + orig_magics = _magics | _async_method_magics + these_magics = orig_magics if getattr(self, "_mock_methods", None) is not None: - these_magics = _magics.intersection(self._mock_methods) + these_magics = orig_magics.intersection(self._mock_methods) remove_magics = set() - remove_magics = _magics - these_magics + remove_magics = orig_magics - these_magics for entry in remove_magics: if entry in type(self).__dict__: @@ -2079,33 +2082,14 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() -class AsyncMagicMixin: +class AsyncMagicMixin(MagicMixin): def __init__(self, *args, **kw): - self._mock_set_async_magics() # make magic work for kwargs in init + self._mock_set_magics() # make magic work for kwargs in init _safe_super(AsyncMagicMixin, self).__init__(*args, **kw) - self._mock_set_async_magics() # fix magic broken by upper level init - - def _mock_set_async_magics(self): - these_magics = _async_magics - - if getattr(self, "_mock_methods", None) is not None: - these_magics = _async_magics.intersection(self._mock_methods) - remove_magics = _async_magics - these_magics - - for entry in remove_magics: - if entry in type(self).__dict__: - # remove unneeded magic methods - delattr(self, entry) - - # don't overwrite existing attributes if called a second time - these_magics = these_magics - set(type(self).__dict__) - - _type = type(self) - for entry in these_magics: - setattr(_type, entry, MagicProxy(entry, self)) + self._mock_set_magics() # fix magic broken by upper level init -class MagicMock(MagicMixin, AsyncMagicMixin, Mock): +class MagicMock(MagicMixin, Mock): """ MagicMock is a subclass of Mock with default implementations of most of the magic methods. You can use MagicMock without having to @@ -2127,7 +2111,7 @@ def mock_add_spec(self, spec, spec_set=False): -class MagicProxy(object): +class MagicProxy(Base): def __init__(self, name, parent): self.name = name self.parent = parent diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 315322c5..ebf5cb21 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -393,6 +393,43 @@ def test_add_side_effect_iterable(self): RuntimeError('coroutine raised StopIteration') ) +class AsyncMagicMethods(unittest.TestCase): + def test_async_magic_methods_return_async_mocks(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + self.assertIsInstance(m_mock.__anext__, AsyncMock) + # __aiter__ is actually a synchronous object + # so should return a MagicMock + self.assertIsInstance(m_mock.__aiter__, MagicMock) + + def test_sync_magic_methods_return_magic_mocks(self): + a_mock = AsyncMock() + self.assertIsInstance(a_mock.__enter__, MagicMock) + self.assertIsInstance(a_mock.__exit__, MagicMock) + self.assertIsInstance(a_mock.__next__, MagicMock) + self.assertIsInstance(a_mock.__len__, MagicMock) + + def test_magicmock_has_async_magic_methods(self): + m_mock = MagicMock() + self.assertTrue(hasattr(m_mock, "__aenter__")) + self.assertTrue(hasattr(m_mock, "__aexit__")) + self.assertTrue(hasattr(m_mock, "__anext__")) + + def test_asyncmock_has_sync_magic_methods(self): + a_mock = AsyncMock() + self.assertTrue(hasattr(a_mock, "__enter__")) + self.assertTrue(hasattr(a_mock, "__exit__")) + self.assertTrue(hasattr(a_mock, "__next__")) + self.assertTrue(hasattr(a_mock, "__len__")) + + def test_magic_methods_are_async_functions(self): + m_mock = MagicMock() + self.assertIsInstance(m_mock.__aenter__, AsyncMock) + self.assertIsInstance(m_mock.__aexit__, AsyncMock) + # AsyncMocks are also coroutine functions + self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aenter__)) + self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aexit__)) class AsyncContextManagerTest(unittest.TestCase): @@ -420,24 +457,6 @@ async def main(self): val = await response.json() return val - def test_async_magic_methods_are_async_mocks_with_magicmock(self): - cm_mock = MagicMock(self.WithAsyncContextManager()) - self.assertIsInstance(cm_mock.__aenter__, AsyncMock) - self.assertIsInstance(cm_mock.__aexit__, AsyncMock) - - def test_magicmock_has_async_magic_methods(self): - cm = MagicMock(name='magic_cm') - self.assertTrue(hasattr(cm, "__aenter__")) - self.assertTrue(hasattr(cm, "__aexit__")) - - def test_magic_methods_are_async_functions(self): - cm = MagicMock(name='magic_cm') - self.assertIsInstance(cm.__aenter__, AsyncMock) - self.assertIsInstance(cm.__aexit__, AsyncMock) - # AsyncMocks are also coroutine functions - self.assertTrue(asyncio.iscoroutinefunction(cm.__aenter__)) - self.assertTrue(asyncio.iscoroutinefunction(cm.__aexit__)) - def test_set_return_value_of_aenter(self): def inner_test(mock_type): pc = self.ProductionCode() @@ -909,7 +928,7 @@ def test_assert_has_awaits_not_matching_spec_error(self): async def f(x=None): pass self.mock = AsyncMock(spec=f) - asyncio.run(self._runnable_test(1)) + run(self._runnable_test(1)) with self.assertRaisesRegex( AssertionError, diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index d256dd3e..afd4dbef 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -271,9 +271,6 @@ def test_magic_mock_equality(self): self.assertEqual(mock == mock, True) self.assertEqual(mock != mock, False) - - # This should be fixed with issue38163 - @unittest.expectedFailure def test_asyncmock_defaults(self): mock = AsyncMock() self.assertEqual(int(mock), 1) From 189dffbe8106c7afb5e65211a6132235f62c23ca Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sun, 29 Sep 2019 21:01:28 -0700 Subject: [PATCH 37/70] bpo-38161: Removes _AwaitEvent from AsyncMock. (GH-16443) Backports: 25e115ec00b5f75e3589c9f21013c47c21e1753f Signed-off-by: Chris Withers --- .../2019-09-27-16-31-28.bpo-38161.zehai1.rst | 1 + mock/mock.py | 36 ------------------- mock/tests/testasync.py | 4 +-- 3 files changed, 2 insertions(+), 39 deletions(-) create mode 100644 NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst diff --git a/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst b/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst new file mode 100644 index 00000000..0077033c --- /dev/null +++ b/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst @@ -0,0 +1 @@ +Removes _AwaitEvent from AsyncMock. diff --git a/mock/mock.py b/mock/mock.py index 44ac03c7..909e646b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -248,7 +248,6 @@ def _setup_async_mock(mock): mock.await_count = 0 mock.await_args = None mock.await_args_list = _CallList() - mock.awaited = _AwaitEvent(mock) # Mock is not configured yet so the attributes are set # to a function and then the corresponding mock helper function @@ -2130,7 +2129,6 @@ def __get__(self, obj, _type=None): class AsyncMockMixin(Base): - awaited = _delegating_property('awaited') await_count = _delegating_property('await_count') await_args = _delegating_property('await_args') await_args_list = _delegating_property('await_args_list') @@ -2144,7 +2142,6 @@ def __init__(self, *args, **kwargs): # It is set through __dict__ because when spec_set is True, this # attribute is likely undefined. self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine - self.__dict__['_mock_awaited'] = _AwaitEvent(self) self.__dict__['_mock_await_count'] = 0 self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = _CallList() @@ -2174,7 +2171,6 @@ async def proxy(): self.await_count += 1 self.await_args = _call self.await_args_list.append(_call) - await self.awaited._notify() return await proxy() @@ -2906,35 +2902,3 @@ async def __anext__(self): except StopIteration: pass raise StopAsyncIteration - - -class _AwaitEvent: - def __init__(self, mock): - self._mock = mock - self._condition = None - - async def _notify(self): - condition = self._get_condition() - try: - await condition.acquire() - condition.notify_all() - finally: - condition.release() - - def _get_condition(self): - """ - Creation of condition is delayed, to minimize the chance of using the - wrong loop. - A user may create a mock with _AwaitEvent before selecting the - execution loop. Requiring a user to delay creation is error-prone and - inflexible. Instead, condition is created when user actually starts to - use the mock. - """ - # No synchronization is needed: - # - asyncio is thread unsafe - # - there are no awaits here, method will be executed without - # switching asyncio context. - if self._condition is None: - self._condition = asyncio.Condition() - - return self._condition diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index ebf5cb21..273b4066 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -6,7 +6,7 @@ from mock import (ANY, call, AsyncMock, patch, MagicMock, create_autospec, sentinel) -from mock.mock import _AwaitEvent, _CallList +from mock.mock import _CallList try: @@ -192,7 +192,6 @@ async def main(): self.assertEqual(spec.await_count, 0) self.assertIsNone(spec.await_args) self.assertEqual(spec.await_args_list, []) - self.assertIsInstance(spec.awaited, _AwaitEvent) spec.assert_not_awaited() run(main()) @@ -226,7 +225,6 @@ async def test_async(): self.assertEqual(mock_method.await_count, 0) self.assertEqual(mock_method.await_args_list, []) self.assertIsNone(mock_method.await_args) - self.assertIsInstance(mock_method.awaited, _AwaitEvent) mock_method.assert_not_awaited() await awaitable From ec2bfa7997580e981232a400401c12998db67c44 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sun, 29 Sep 2019 21:56:47 -0700 Subject: [PATCH 38/70] bpo-38163: Child mocks detect their type as sync or async (GH-16471) Backports: 3667e1ee6c90e6d3b6a745cd590ece87118f81ad Signed-off-by: Chris Withers --- .../2019-09-28-20-16-40.bpo-38163.x51-vK.rst | 4 ++ mock/mock.py | 5 +- mock/tests/testasync.py | 67 ++++++++++++------- 3 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst diff --git a/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst b/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst new file mode 100644 index 00000000..5f7db26e --- /dev/null +++ b/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst @@ -0,0 +1,4 @@ +Child mocks will now detect their type as either synchronous or +asynchronous, asynchronous child mocks will be AsyncMocks and synchronous +child mocks will be either MagicMock or Mock (depending on their parent +type). diff --git a/mock/mock.py b/mock/mock.py index 909e646b..fb96c83c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -999,8 +999,9 @@ def _get_child_mock(self, **kw): # Any asynchronous magic becomes an AsyncMock klass = AsyncMock elif issubclass(_type, AsyncMockMixin): - if _new_name in _all_sync_magics: - # Any synchronous magic becomes a MagicMock + if (_new_name in _all_sync_magics or + self._mock_methods and _new_name in self._mock_methods): + # Any synchronous method on AsyncMock becomes a MagicMock klass = MagicMock else: klass = AsyncMock diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 273b4066..a2d5bb2d 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -4,7 +4,7 @@ import re import unittest -from mock import (ANY, call, AsyncMock, patch, MagicMock, +from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) from mock.mock import _CallList @@ -246,33 +246,50 @@ async def test_async(): class AsyncSpecTest(unittest.TestCase): - def test_spec_as_async_positional_magicmock(self): - mock = MagicMock(async_func) - self.assertIsInstance(mock, MagicMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) + def test_spec_normal_methods_on_class(self): + def inner_test(mock_type): + mock = mock_type(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) - def test_spec_as_async_kw_magicmock(self): - mock = MagicMock(spec=async_func) - self.assertIsInstance(mock, MagicMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) + for mock_type in [AsyncMock, MagicMock]: + with self.subTest(f"test method types with {mock_type}"): + inner_test(mock_type) - def test_spec_as_async_kw_AsyncMock(self): - mock = AsyncMock(spec=async_func) - self.assertIsInstance(mock, AsyncMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) + def test_spec_normal_methods_on_class_with_mock(self): + mock = Mock(AsyncClass) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, Mock) - def test_spec_as_async_positional_AsyncMock(self): - mock = AsyncMock(async_func) - self.assertIsInstance(mock, AsyncMock) - m = mock() - self.assertTrue(inspect.isawaitable(m)) - run(m) + def test_spec_mock_type_kw(self): + def inner_test(mock_type): + async_mock = mock_type(spec=async_func) + self.assertIsInstance(async_mock, mock_type) + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(spec=normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec kwarg with {mock_type}"): + inner_test(mock_type) + + def test_spec_mock_type_positional(self): + def inner_test(mock_type): + async_mock = mock_type(async_func) + self.assertIsInstance(async_mock, mock_type) + with self.assertWarns(RuntimeWarning): + # Will raise a warning because never awaited + self.assertTrue(inspect.isawaitable(async_mock())) + + sync_mock = mock_type(normal_func) + self.assertIsInstance(sync_mock, mock_type) + + for mock_type in [AsyncMock, MagicMock, Mock]: + with self.subTest(f"test spec positional with {mock_type}"): + inner_test(mock_type) def test_spec_as_normal_kw_AsyncMock(self): mock = AsyncMock(spec=normal_func) From 0fda551a97c2a99f6dd3e426b4ceedcde4d38d1b Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 19 Nov 2019 19:45:20 +0000 Subject: [PATCH 39/70] bpo-38839: Fix some unused functions in tests (GH-17189) Backports: 892221bfa04a41cf581f988ba19dc263f557e157 Signed-off-by: Chris Withers --- NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst | 1 + mock/tests/testasync.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst diff --git a/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst b/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst new file mode 100644 index 00000000..80c5a5bd --- /dev/null +++ b/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst @@ -0,0 +1 @@ +Fix some unused functions in tests. Patch by Adam Johnson. diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index a2d5bb2d..2eda1e58 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -349,6 +349,7 @@ def test_is_AsyncMock_patch(self): @patch.object(AsyncClass, 'async_method', spec_set=True) def test_async(async_method): self.assertIsInstance(async_method, AsyncMock) + test_async() def test_is_async_AsyncMock(self): mock = AsyncMock(spec_set=AsyncClass.async_method) From 30cc1881168041d1ddede3ecd15b69407a564e7f Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Wed, 20 Nov 2019 16:27:51 -0800 Subject: [PATCH 40/70] bpo-38857: AsyncMock fix for awaitable values and StopIteration fix [3.8] (GH-17269) Backports: 046442d02bcc6e848e71e93e47f6cde9e279e993 Signed-off-by: Chris Withers --- .../2019-11-19-16-28-25.bpo-38857.YPUkU9.rst | 4 + .../2019-11-19-16-30-46.bpo-38859.AZUzL8.rst | 3 + mock/mock.py | 62 ++++++++------- mock/tests/testasync.py | 75 ++++++++++++++----- 4 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst create mode 100644 NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst diff --git a/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst b/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst new file mode 100644 index 00000000..f28df281 --- /dev/null +++ b/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst @@ -0,0 +1,4 @@ +AsyncMock fix for return values that are awaitable types. This also covers +side_effect iterable values that happend to be awaitable, and wraps +callables that return an awaitable type. Before these awaitables were being +awaited instead of being returned as is. diff --git a/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst b/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst new file mode 100644 index 00000000..c059539a --- /dev/null +++ b/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst @@ -0,0 +1,3 @@ +AsyncMock now returns StopAsyncIteration on the exaustion of a side_effects +iterable. Since PEP-479 its Impossible to raise a StopIteration exception +from a coroutine. diff --git a/mock/mock.py b/mock/mock.py index fb96c83c..807d7b76 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1148,8 +1148,8 @@ def _increment_mock_call(_mock_self, *args, **kwargs): def _execute_mock_call(_mock_self, *args, **kwargs): self = _mock_self - # seperate from _increment_mock_call so that awaited functions are - # executed seperately from their call + # separate from _increment_mock_call so that awaited functions are + # executed separately from their call, also AsyncMock overrides this method effect = self.side_effect if effect is not None: @@ -2150,30 +2150,46 @@ def __init__(self, *args, **kwargs): code_mock.co_flags = inspect.CO_COROUTINE self.__dict__['__code__'] = code_mock - async def _mock_call(_mock_self, *args, **kwargs): + async def _execute_mock_call(_mock_self, *args, **kwargs): self = _mock_self - try: - result = super()._mock_call(*args, **kwargs) - except (BaseException, StopIteration) as e: - side_effect = self.side_effect - if side_effect is not None and not callable(side_effect): - raise - return await _raise(e) + # This is nearly just like super(), except for sepcial handling + # of coroutines _call = self.call_args + self.await_count += 1 + self.await_args = _call + self.await_args_list.append(_call) - async def proxy(): - try: - if inspect.isawaitable(result): - return await result - else: - return result - finally: - self.await_count += 1 - self.await_args = _call - self.await_args_list.append(_call) + effect = self.side_effect + if effect is not None: + if _is_exception(effect): + raise effect + elif not _callable(effect): + try: + result = next(effect) + except StopIteration: + # It is impossible to propogate a StopIteration + # through coroutines because of PEP 479 + raise StopAsyncIteration + if _is_exception(result): + raise result + elif asyncio.iscoroutinefunction(effect): + result = await effect(*args, **kwargs) + else: + result = effect(*args, **kwargs) - return await proxy() + if result is not DEFAULT: + return result + + if self._mock_return_value is not DEFAULT: + return self.return_value + + if self._mock_wraps is not None: + if asyncio.iscoroutinefunction(self._mock_wraps): + return await self._mock_wraps(*args, **kwargs) + return self._mock_wraps(*args, **kwargs) + + return self.return_value def assert_awaited(_mock_self): """ @@ -2880,10 +2896,6 @@ def seal(mock): seal(m) -async def _raise(exception): - raise exception - - class _AsyncIterator: """ Wraps an iterator in an asynchronous iterator. diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 2eda1e58..e839fcad 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -8,7 +8,6 @@ create_autospec, sentinel) from mock.mock import _CallList - try: from asyncio import run except ImportError: @@ -372,42 +371,84 @@ def test_magicmock_lambda_spec(self): self.assertIsInstance(cm, MagicMock) -class AsyncArguments(unittest.TestCase): - def test_add_return_value(self): +class AsyncArguments(unittest.IsolatedAsyncioTestCase): + async def test_add_return_value(self): async def addition(self, var): return var + 1 mock = AsyncMock(addition, return_value=10) - output = run(mock(5)) + output = await mock(5) self.assertEqual(output, 10) - def test_add_side_effect_exception(self): + async def test_add_side_effect_exception(self): async def addition(var): return var + 1 mock = AsyncMock(addition, side_effect=Exception('err')) with self.assertRaises(Exception): - run(mock(5)) + await mock(5) - def test_add_side_effect_function(self): + async def test_add_side_effect_function(self): async def addition(var): return var + 1 mock = AsyncMock(side_effect=addition) - result = run(mock(5)) + result = await mock(5) self.assertEqual(result, 6) - def test_add_side_effect_iterable(self): + async def test_add_side_effect_iterable(self): vals = [1, 2, 3] mock = AsyncMock(side_effect=vals) for item in vals: - self.assertEqual(item, run(mock())) - - with self.assertRaises(RuntimeError) as e: - run(mock()) - self.assertEqual( - e.exception, - RuntimeError('coroutine raised StopIteration') - ) + self.assertEqual(item, await mock()) + + with self.assertRaises(StopAsyncIteration) as e: + await mock() + + async def test_return_value_AsyncMock(self): + value = AsyncMock(return_value=10) + mock = AsyncMock(return_value=value) + result = await mock() + self.assertIs(result, value) + + async def test_return_value_awaitable(self): + fut = asyncio.Future() + fut.set_result(None) + mock = AsyncMock(return_value=fut) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + async def test_side_effect_awaitable_values(self): + fut = asyncio.Future() + fut.set_result(None) + + mock = AsyncMock(side_effect=[fut]) + result = await mock() + self.assertIsInstance(result, asyncio.Future) + + with self.assertRaises(StopAsyncIteration): + await mock() + + async def test_side_effect_is_AsyncMock(self): + effect = AsyncMock(return_value=10) + mock = AsyncMock(side_effect=effect) + + result = await mock() + self.assertEqual(result, 10) + + async def test_wraps_coroutine(self): + value = asyncio.Future() + + ran = False + async def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) class AsyncMagicMethods(unittest.TestCase): def test_async_magic_methods_return_async_mocks(self): From a6768e64120cd6c963035044e9d70fb85f2dabc0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 24 Jan 2020 07:29:40 +0000 Subject: [PATCH 41/70] minimal backport of IsolatedAsyncioTestCase to get tests passing on Py3.6 --- mock/backports.py | 63 ++++++++++++++++++++++++++++++++++++++++- mock/tests/testasync.py | 4 ++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index fdca1972..80e8a98d 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -3,9 +3,10 @@ if sys.version_info[:2] < (3, 8): - import functools + import asyncio, functools from asyncio.coroutines import _is_coroutine from inspect import ismethod, isfunction, CO_COROUTINE + from unittest import TestCase def _unwrap_partial(func): while isinstance(func, functools.partial): @@ -33,7 +34,67 @@ def iscoroutinefunction(obj): getattr(obj, '_is_coroutine', None) is _is_coroutine ) + + class IsolatedAsyncioTestCase(TestCase): + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + self._asyncioTestLoop = None + self._asyncioCallsQueue = None + + async def _asyncioLoopRunner(self, fut): + self._asyncioCallsQueue = queue = asyncio.Queue() + fut.set_result(None) + while True: + query = await queue.get() + queue.task_done() + if query is None: + return + fut, awaitable = query + try: + ret = await awaitable + if not fut.cancelled(): + fut.set_result(ret) + except asyncio.CancelledError: + raise + except Exception as ex: + if not fut.cancelled(): + fut.set_exception(ex) + + def _setupAsyncioLoop(self): + assert self._asyncioTestLoop is None + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.set_debug(True) + self._asyncioTestLoop = loop + fut = loop.create_future() + self._asyncioCallsTask = loop.create_task(self._asyncioLoopRunner(fut)) + loop.run_until_complete(fut) + + def _tearDownAsyncioLoop(self): + assert self._asyncioTestLoop is not None + loop = self._asyncioTestLoop + self._asyncioTestLoop = None + self._asyncioCallsQueue.put_nowait(None) + loop.run_until_complete(self._asyncioCallsQueue.join()) + + try: + # shutdown asyncgens + loop.run_until_complete(loop.shutdown_asyncgens()) + finally: + asyncio.set_event_loop(None) + loop.close() + + def run(self, result=None): + self._setupAsyncioLoop() + try: + return super().run(result) + finally: + self._tearDownAsyncioLoop() + + else: from asyncio import iscoroutinefunction + from unittest import IsolatedAsyncioTestCase diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index e839fcad..b0bbe479 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -6,8 +6,10 @@ from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) +from mock.backports import IsolatedAsyncioTestCase from mock.mock import _CallList + try: from asyncio import run except ImportError: @@ -371,7 +373,7 @@ def test_magicmock_lambda_spec(self): self.assertIsInstance(cm, MagicMock) -class AsyncArguments(unittest.IsolatedAsyncioTestCase): +class AsyncArguments(IsolatedAsyncioTestCase): async def test_add_return_value(self): async def addition(self, var): return var + 1 From c000fff5bdc49c620ea6bc9160214858b64f611e Mon Sep 17 00:00:00 2001 From: Elena Oat Date: Sun, 8 Dec 2019 12:14:38 -0800 Subject: [PATCH 42/70] bpo-38669: patch.object now raises a helpful error (GH17034) This means a clearer message is now shown when patch.object is called with two string arguments, rather than a class and a string argument. Backports: cd90a52983db34896a6335a572d55bdda274778f Signed-off-by: Chris Withers --- NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst | 1 + mock/mock.py | 4 ++++ mock/tests/testpatch.py | 4 ++++ 3 files changed, 9 insertions(+) create mode 100644 NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst diff --git a/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst b/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst new file mode 100644 index 00000000..5060ecf2 --- /dev/null +++ b/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst @@ -0,0 +1 @@ +Raise :exc:`TypeError` when passing target as a string with :meth:`unittest.mock.patch.object`. \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index 807d7b76..2c68abc4 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1610,6 +1610,10 @@ def _patch_object( When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` for choosing which methods to wrap. """ + if type(target) is str: + raise TypeError( + f"{target!r} must be the actual object to be patched, not a str" + ) getter = lambda: target return _patch( getter, attribute, new, spec, create, diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index ae5cdff7..e30f5815 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -105,6 +105,10 @@ def test(): self.assertEqual(Something.attribute, sentinel.Original, "patch not restored") + def test_patchobject_with_string_as_target(self): + msg = "'Something' must be the actual object to be patched, not a str" + with self.assertRaisesRegex(TypeError, msg): + patch.object('Something', 'do_something') def test_patchobject_with_none(self): class Something(object): From 695580a480ce046136aa652191d9b5c6926015a9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 19:11:34 +0000 Subject: [PATCH 43/70] remove unused __version__ from mock.py (#17977) This isn't included in `__all__` and could be a source of confusion. Backports: 31d6de5aba009914efa8f0f3c3d7da35217578eb Signed-off-by: Chris Withers --- mock/mock.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 2c68abc4..8ed47e75 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -23,8 +23,6 @@ ) -__version__ = '1.0' - import asyncio import contextlib import io From 7a35f3a96bd043073f089a840965166923808de5 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 15 Jan 2020 15:19:49 +0530 Subject: [PATCH 44/70] Improve test coverage for AsyncMock. (GH-17906) * Add test for nested async decorator patch. * Add test for side_effect and wraps with a function. * Add test for side_effect with an exception in the iterable. Backports: 54f743eb315f00b0ff45e115dde7a5d506034153 Signed-off-by: Chris Withers --- mock/tests/testasync.py | 53 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index b0bbe479..1b66a046 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -87,9 +87,17 @@ def test_async(mock_method): test_async() def test_async_def_patch(self): - @patch(f"{__name__}.async_func", AsyncMock()) - async def test_async(): + @patch(f"{__name__}.async_func", return_value=1) + @patch(f"{__name__}.async_func_args", return_value=2) + async def test_async(func_args_mock, func_mock): + self.assertEqual(func_args_mock._mock_name, "async_func_args") + self.assertEqual(func_mock._mock_name, "async_func") + self.assertIsInstance(async_func, AsyncMock) + self.assertIsInstance(async_func_args, AsyncMock) + + self.assertEqual(await async_func(), 1) + self.assertEqual(await async_func_args(1, 2, c=3), 2) run(test_async()) self.assertTrue(inspect.iscoroutinefunction(async_func)) @@ -390,22 +398,40 @@ async def addition(var): with self.assertRaises(Exception): await mock(5) - async def test_add_side_effect_function(self): + async def test_add_side_effect_coroutine(self): async def addition(var): return var + 1 mock = AsyncMock(side_effect=addition) result = await mock(5) self.assertEqual(result, 6) + async def test_add_side_effect_normal_function(self): + def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = await mock(5) + self.assertEqual(result, 6) + async def test_add_side_effect_iterable(self): vals = [1, 2, 3] mock = AsyncMock(side_effect=vals) for item in vals: - self.assertEqual(item, await mock()) + self.assertEqual(await mock(), item) with self.assertRaises(StopAsyncIteration) as e: await mock() + async def test_add_side_effect_exception_iterable(self): + class SampleException(Exception): + pass + + vals = [1, SampleException("foo")] + mock = AsyncMock(side_effect=vals) + self.assertEqual(await mock(), 1) + + with self.assertRaises(SampleException) as e: + await mock() + async def test_return_value_AsyncMock(self): value = AsyncMock(return_value=10) mock = AsyncMock(return_value=value) @@ -452,6 +478,21 @@ async def inner(): mock.assert_awaited() self.assertTrue(ran) + async def test_wraps_normal_function(self): + value = 1 + + ran = False + def inner(): + nonlocal ran + ran = True + return value + + mock = AsyncMock(wraps=inner) + result = await mock() + self.assertEqual(result, value) + mock.assert_awaited() + self.assertTrue(ran) + class AsyncMagicMethods(unittest.TestCase): def test_async_magic_methods_return_async_mocks(self): m_mock = MagicMock() @@ -875,6 +916,10 @@ def test_assert_awaited_once(self): self.mock.assert_awaited_once() def test_assert_awaited_with(self): + msg = 'Not awaited' + with self.assertRaisesRegex(AssertionError, msg): + self.mock.assert_awaited_with('foo') + run(self._runnable_test()) msg = 'expected await not found' with self.assertRaisesRegex(AssertionError, msg): From aa5f6e9e2df8a77b2f81714018043efc392ae676 Mon Sep 17 00:00:00 2001 From: Emmanuel Arias Date: Fri, 24 Jan 2020 05:14:14 -0300 Subject: [PATCH 45/70] bpo-24928: Add test case for patch.dict using OrderedDict (GH -11437) * add test for path.dict using OrderedDict Co-authored-by: Yu Tomita nekobon@users.noreply.github.com Backports: 1d0c5e16eab29d55773cc4196bb90d2bf12e09dd Signed-off-by: Chris Withers --- mock/tests/testpatch.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index e30f5815..5316b9cf 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -4,6 +4,7 @@ import os import sys +from collections import OrderedDict import unittest from mock.tests import support @@ -1834,6 +1835,25 @@ def foo(*a, x=0): self.assertEqual(foo(), 1) self.assertEqual(foo(), 0) + def test_patch_orderdict(self): + foo = OrderedDict() + foo['a'] = object() + foo['b'] = 'python' + + original = foo.copy() + update_values = list(zip('cdefghijklmnopqrstuvwxyz', range(26))) + patched_values = list(foo.items()) + update_values + + with patch.dict(foo, OrderedDict(update_values)): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + + with patch.dict(foo, update_values): + self.assertEqual(list(foo.items()), patched_values) + + self.assertEqual(foo, original) + def test_dotted_but_module_not_loaded(self): # This exercises the AttributeError branch of _dot_lookup. From 11df596649d109d3d36f3a5062bd32f42493808a Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Fri, 24 Jan 2020 08:38:33 +0000 Subject: [PATCH 46/70] Fix `mock.patch.dict` to be stopped with `mock.patch.stopall` (#17606) As the function was not registering in the active patches, the mocks started by `mock.patch.dict` were not being stopped when `mock.patch.stopall` was being called. Backports: e131c9720d087c0c4988bd2a5c62020feb9d1d77 Signed-off-by: Chris Withers --- .../2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst | 2 + mock/mock.py | 19 ++++++- mock/tests/testpatch.py | 50 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst diff --git a/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst b/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst new file mode 100644 index 00000000..0f726393 --- /dev/null +++ b/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst @@ -0,0 +1,2 @@ +Fix :func:`mock.patch.stopall` to stop active patches that were created with +:func:`mock.patch.dict`. diff --git a/mock/mock.py b/mock/mock.py index 8ed47e75..f9242058 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1860,8 +1860,23 @@ def __exit__(self, *args): self._unpatch_dict() return False - start = __enter__ - stop = __exit__ + + def start(self): + """Activate a patch, returning any created mock.""" + result = self.__enter__() + _patch._active_patches.append(self) + return result + + + def stop(self): + """Stop an active patch.""" + try: + _patch._active_patches.remove(self) + except ValueError: + # If the patch hasn't been started this will fail + pass + + return self.__exit__() def _clear_dict(in_dict): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 5316b9cf..1d3050ef 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1808,6 +1808,56 @@ def stop(self): self.assertEqual(stopped, ["three", "two", "one"]) + def test_patch_dict_stopall(self): + dic1 = {} + dic2 = {1: 'a'} + dic3 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + origdic3 = dic3.copy() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2, {2: 'b'}).start() + + @patch.dict(dic3) + def patched(): + del dic3[1] + + patched() + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + patch.stopall() + + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + self.assertEqual(dic3, origdic3) + + + def test_patch_and_patch_dict_stopall(self): + original_unlink = os.unlink + original_chdir = os.chdir + dic1 = {} + dic2 = {1: 'A', 2: 'B'} + origdic1 = dic1.copy() + origdic2 = dic2.copy() + + patch('os.unlink', something).start() + patch('os.chdir', something_else).start() + patch.dict(dic1, {1: 'I', 2: 'II'}).start() + patch.dict(dic2).start() + del dic2[1] + + self.assertIsNot(os.unlink, original_unlink) + self.assertIsNot(os.chdir, original_chdir) + self.assertNotEqual(dic1, origdic1) + self.assertNotEqual(dic2, origdic2) + patch.stopall() + self.assertIs(os.unlink, original_unlink) + self.assertIs(os.chdir, original_chdir) + self.assertEqual(dic1, origdic1) + self.assertEqual(dic2, origdic2) + def test_special_attrs(self): def foo(x=0): From 5de813ca343c6bc4bc990b6d1a3df1a0407ddaa6 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Fri, 24 Jan 2020 18:44:29 +0530 Subject: [PATCH 47/70] bpo-38473: Handle autospecced functions and methods used with attach_mock (GH-16784) Backports: 66b00a9d3aacf6ed49412f48743e4913104a2bb3 Signed-off-by: Chris Withers --- .../2019-10-14-21-14-55.bpo-38473.uXpVld.rst | 2 ++ mock/mock.py | 4 +++ mock/tests/testmock.py | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst diff --git a/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst b/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst new file mode 100644 index 00000000..de80e89e --- /dev/null +++ b/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst @@ -0,0 +1,2 @@ +Use signature from inner mock for autospecced methods attached with +:func:`unittest.mock.attach_mock`. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index f9242058..e453ec4f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -819,6 +819,10 @@ def _get_call_signature_from_name(self, name): if child is None or isinstance(child, _SpecState): break else: + # If an autospecced object is attached using attach_mock the + # child would be a function with mock object as attribute from + # which signature has to be derived. + child = _extract_mock(child) children = child._mock_children sig = child._spec_signature diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index c97d7fc3..75740020 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1924,6 +1924,35 @@ def test_attach_mock_patch_autospec(self): self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child') + def test_attach_mock_patch_autospec_signature(self): + with mock.patch(f'{__name__}.Something.meth', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_meth') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_meth(mock.ANY, 1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + mocked.assert_has_calls([call(mock.ANY, 1, 2, 3, d=4)]) + + with mock.patch(f'{__name__}.something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_func') + something(1) + manager.assert_has_calls([call.attach_func(1)]) + something.assert_has_calls([call(1)]) + mocked.assert_has_calls([call(1)]) + + with mock.patch(f'{__name__}.Something', autospec=True) as mocked: + manager = Mock() + manager.attach_mock(mocked, 'attach_obj') + obj = Something() + obj.meth(1, 2, 3, d=4) + manager.assert_has_calls([call.attach_obj(), + call.attach_obj().meth(1, 2, 3, d=4)]) + obj.meth.assert_has_calls([call(1, 2, 3, d=4)]) + mocked.assert_has_calls([call(), call().meth(1, 2, 3, d=4)]) + + def test_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): From 610e9e3ae0cac170883c73ddc6a5bb164e9451d4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 24 Jan 2020 18:37:55 +0000 Subject: [PATCH 48/70] flip more to iscoroutinefunction backport --- mock/mock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index e453ec4f..236a6782 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -488,7 +488,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_asyncs = [] for attr in dir(spec): - if asyncio.iscoroutinefunction(getattr(spec, attr, None)): + if iscoroutinefunction(getattr(spec, attr, None)): _spec_asyncs.append(attr) if spec is not None and not _is_list(spec): @@ -2194,7 +2194,7 @@ async def _execute_mock_call(_mock_self, *args, **kwargs): raise StopAsyncIteration if _is_exception(result): raise result - elif asyncio.iscoroutinefunction(effect): + elif iscoroutinefunction(effect): result = await effect(*args, **kwargs) else: result = effect(*args, **kwargs) @@ -2206,7 +2206,7 @@ async def _execute_mock_call(_mock_self, *args, **kwargs): return self.return_value if self._mock_wraps is not None: - if asyncio.iscoroutinefunction(self._mock_wraps): + if iscoroutinefunction(self._mock_wraps): return await self._mock_wraps(*args, **kwargs) return self._mock_wraps(*args, **kwargs) @@ -2717,7 +2717,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, skipfirst = _must_skip(spec, entry, is_type) kwargs['_eat_self'] = skipfirst - if asyncio.iscoroutinefunction(original): + if iscoroutinefunction(original): child_klass = AsyncMock else: child_klass = MagicMock From 5a12423241f1168c0609c28e88c1f2e40ba40e38 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 25 Jan 2020 10:44:19 +0000 Subject: [PATCH 49/70] this one no longer fails on pypy --- mock/tests/testmagicmethods.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index afd4dbef..e1f1ee0e 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -1,7 +1,6 @@ import math import unittest import os -import sys from mock import AsyncMock, Mock, MagicMock from mock.backports import iscoroutinefunction from mock.mock import _magics @@ -429,7 +428,6 @@ def _dir(self): self.assertEqual(dir(mock), ['foo']) - @unittest.skipIf('PyPy' in sys.version, "This fails differently on pypy") def test_bound_methods(self): m = Mock() From 7a5fc548cd3c6dac3c601a866bf910420d797a96 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 25 Jan 2020 10:44:42 +0000 Subject: [PATCH 50/70] pypy actually gets this right, so make it clear in the test --- mock/tests/testhelpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index ae4c8479..3dd95f2b 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -912,8 +912,6 @@ def check_data_descriptor(mock_attr): check_data_descriptor(foo.desc) - @pytest.mark.skipif(IS_PYPY, - reason="https://bitbucket.org/pypy/pypy/issues/3010") def test_autospec_on_bound_builtin_function(self): meth = types.MethodType(time.ctime, time.time()) self.assertIsInstance(meth(), str) @@ -923,8 +921,13 @@ def test_autospec_on_bound_builtin_function(self): mocked() mocked.assert_called_once_with() mocked.reset_mock() - mocked(4, 5, 6) - mocked.assert_called_once_with(4, 5, 6) + # but pypy gets this right: + if IS_PYPY: + with self.assertRaises(TypeError): + mocked(4, 5, 6) + else: + mocked(4, 5, 6) + mocked.assert_called_once_with(4, 5, 6) def test_autospec_getattr_partial_function(self): From 8d3d8f29fa9aa9fcac375e6481480d57886478ef Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 27 Jan 2020 07:50:25 +0000 Subject: [PATCH 51/70] move testasync to backports.iscoroutinefunction to keep inline with upstream imports --- mock/tests/testasync.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 1b66a046..8d357427 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -6,7 +6,7 @@ from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) -from mock.backports import IsolatedAsyncioTestCase +from mock.backports import IsolatedAsyncioTestCase, iscoroutinefunction from mock.mock import _CallList @@ -60,7 +60,7 @@ class AsyncPatchDecoratorTest(unittest.TestCase): def test_is_coroutine_function_patch(self): @patch.object(AsyncClass, 'async_method') def test_async(mock_method): - self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + self.assertTrue(iscoroutinefunction(mock_method)) test_async() def test_is_async_patch(self): @@ -107,7 +107,7 @@ class AsyncPatchCMTest(unittest.TestCase): def test_is_async_function_cm(self): def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: - self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + self.assertTrue(iscoroutinefunction(mock_method)) test_async() @@ -139,12 +139,12 @@ async def test_async(): class AsyncMockTest(unittest.TestCase): def test_iscoroutinefunction_default(self): mock = AsyncMock() - self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(iscoroutinefunction(mock)) def test_iscoroutinefunction_function(self): async def foo(): pass mock = AsyncMock(foo) - self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(iscoroutinefunction(mock)) self.assertTrue(inspect.iscoroutinefunction(mock)) def test_isawaitable(self): @@ -157,7 +157,7 @@ def test_isawaitable(self): def test_iscoroutinefunction_normal_function(self): def foo(): pass mock = AsyncMock(foo) - self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(iscoroutinefunction(mock)) self.assertTrue(inspect.iscoroutinefunction(mock)) def test_future_isfuture(self): @@ -205,7 +205,7 @@ async def main(): run(main()) - self.assertTrue(asyncio.iscoroutinefunction(spec)) + self.assertTrue(iscoroutinefunction(spec)) self.assertTrue(asyncio.iscoroutine(awaitable)) self.assertEqual(spec.await_count, 1) self.assertEqual(spec.await_args, call(1, 2, c=3)) @@ -226,7 +226,7 @@ async def test_async(): awaitable = mock_method(1, 2, c=3) self.assertIsInstance(mock_method.mock, AsyncMock) - self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + self.assertTrue(iscoroutinefunction(mock_method)) self.assertTrue(asyncio.iscoroutine(awaitable)) self.assertTrue(inspect.isawaitable(awaitable)) @@ -362,13 +362,13 @@ def test_async(async_method): def test_is_async_AsyncMock(self): mock = AsyncMock(spec_set=AsyncClass.async_method) - self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(iscoroutinefunction(mock)) self.assertIsInstance(mock, AsyncMock) def test_is_child_AsyncMock(self): mock = MagicMock(spec_set=AsyncClass) - self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) - self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) + self.assertTrue(iscoroutinefunction(mock.async_method)) + self.assertFalse(iscoroutinefunction(mock.normal_method)) self.assertIsInstance(mock.async_method, AsyncMock) self.assertIsInstance(mock.normal_method, MagicMock) self.assertIsInstance(mock, MagicMock) @@ -528,8 +528,8 @@ def test_magic_methods_are_async_functions(self): self.assertIsInstance(m_mock.__aenter__, AsyncMock) self.assertIsInstance(m_mock.__aexit__, AsyncMock) # AsyncMocks are also coroutine functions - self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aenter__)) - self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aexit__)) + self.assertTrue(iscoroutinefunction(m_mock.__aenter__)) + self.assertTrue(iscoroutinefunction(m_mock.__aexit__)) class AsyncContextManagerTest(unittest.TestCase): @@ -679,11 +679,11 @@ def inner_test(mock_type): mock_instance = mock_type(instance) # Check that the mock and the real thing bahave the same # __aiter__ is not actually async, so not a coroutinefunction - self.assertFalse(asyncio.iscoroutinefunction(instance.__aiter__)) - self.assertFalse(asyncio.iscoroutinefunction(mock_instance.__aiter__)) + self.assertFalse(iscoroutinefunction(instance.__aiter__)) + self.assertFalse(iscoroutinefunction(mock_instance.__aiter__)) # __anext__ is async - self.assertTrue(asyncio.iscoroutinefunction(instance.__anext__)) - self.assertTrue(asyncio.iscoroutinefunction(mock_instance.__anext__)) + self.assertTrue(iscoroutinefunction(instance.__anext__)) + self.assertTrue(iscoroutinefunction(mock_instance.__anext__)) for mock_type in [AsyncMock, MagicMock]: with self.subTest(f"test aiter and anext corourtine with {mock_type}"): @@ -740,7 +740,7 @@ def test_assert_called_but_not_awaited(self): with self.assertWarns(RuntimeWarning): # Will raise a warning because never awaited mock.async_method() - self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) + self.assertTrue(iscoroutinefunction(mock.async_method)) mock.async_method.assert_called() mock.async_method.assert_called_once() mock.async_method.assert_called_once_with() From ad2b38dacd3743add10481bde39de984057a5b5c Mon Sep 17 00:00:00 2001 From: Matthew Kokotovich Date: Sat, 25 Jan 2020 04:17:47 -0600 Subject: [PATCH 52/70] bpo-39082: Allow AsyncMock to correctly patch static/class methods (GH-18116) Backports: 62865f4532094017a9b780b704686ca9734bc329 Signed-off-by: Chris Withers --- .../2020-01-24-13-24-35.bpo-39082.qKgrq_.rst | 1 + mock/mock.py | 2 ++ mock/tests/testasync.py | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst diff --git a/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst b/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst new file mode 100644 index 00000000..52c4ee1b --- /dev/null +++ b/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst @@ -0,0 +1 @@ +Allow AsyncMock to correctly patch static/class methods diff --git a/mock/mock.py b/mock/mock.py index 236a6782..1762ed06 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -48,6 +48,8 @@ def _is_async_obj(obj): if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): return False + if hasattr(obj, '__func__'): + obj = getattr(obj, '__func__') return iscoroutinefunction(obj) or inspect.isawaitable(obj) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 8d357427..0c0d1ef4 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -34,6 +34,15 @@ async def async_method(self): def normal_method(self): pass + @classmethod + async def async_class_method(cls): + pass + + @staticmethod + async def async_static_method(): + pass + + class AwaitableClass: def __await__(self): yield @@ -86,6 +95,20 @@ def test_async(mock_method): test_async() + def test_is_AsyncMock_patch_staticmethod(self): + @patch.object(AsyncClass, 'async_static_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + + def test_is_AsyncMock_patch_classmethod(self): + @patch.object(AsyncClass, 'async_class_method') + def test_async(mock_method): + self.assertIsInstance(mock_method, AsyncMock) + + test_async() + def test_async_def_patch(self): @patch(f"{__name__}.async_func", return_value=1) @patch(f"{__name__}.async_func_args", return_value=2) From 1d85e1adde1bebbd5fe26de82ceecbf88ba886b5 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Sat, 25 Jan 2020 07:53:54 -0300 Subject: [PATCH 53/70] bpo-37955: correct mock.patch docs with respect to the returned type (GH-15521) Backports: 40c080934b3d49311209b1cb690c2ea1e04df7e7 Signed-off-by: Chris Withers --- mock/mock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 1762ed06..3831ba03 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1739,7 +1739,8 @@ def patch( "as"; very useful if `patch` is creating a mock object for you. `patch` takes arbitrary keyword arguments. These will be passed to - the `Mock` (or `new_callable`) on construction. + `AsyncMock` if the patched object is asynchronous, to `MagicMock` + otherwise or to `new_callable` if specified. `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are available for alternate use-cases. From 0dc01d64279df3c51df2b2e3c6312110fce02973 Mon Sep 17 00:00:00 2001 From: Vegard Stikbakke Date: Sat, 25 Jan 2020 16:44:46 +0100 Subject: [PATCH 54/70] bpo-38932: Mock fully resets child objects on reset_mock(). (GH-17409) Backports: aef7dc89879d099dc704bd8037b8a7686fb72838 Signed-off-by: Chris Withers --- NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst | 1 + mock/mock.py | 2 +- mock/tests/testmock.py | 14 +++++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst diff --git a/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst b/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst new file mode 100644 index 00000000..d9ce8e81 --- /dev/null +++ b/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst @@ -0,0 +1 @@ +Mock fully resets child objects on reset_mock(). Patch by Vegard Stikbakke diff --git a/mock/mock.py b/mock/mock.py index 3831ba03..34d0c392 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -594,7 +594,7 @@ def reset_mock(self, visited=None,*, return_value=False, side_effect=False): for child in self._mock_children.values(): if isinstance(child, _SpecState) or child is _deleted: continue - child.reset_mock(visited) + child.reset_mock(visited, return_value=return_value, side_effect=side_effect) ret = self._mock_return_value if _is_instance_mock(ret) and ret is not self: diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 75740020..75597b9c 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1638,11 +1638,23 @@ def test_reset_return(self): self.assertNotEqual(m.side_effect, None) def test_reset_sideeffect(self): - m = Mock(return_value=10, side_effect=[2,3]) + m = Mock(return_value=10, side_effect=[2, 3]) m.reset_mock(side_effect=True) self.assertEqual(m.return_value, 10) self.assertEqual(m.side_effect, None) + def test_reset_return_with_children(self): + m = MagicMock(f=MagicMock(return_value=1)) + self.assertEqual(m.f(), 1) + m.reset_mock(return_value=True) + self.assertNotEqual(m.f(), 1) + + def test_reset_return_with_children_side_effect(self): + m = MagicMock(f=MagicMock(side_effect=[2, 3])) + self.assertNotEqual(m.f.side_effect, None) + m.reset_mock(side_effect=True) + self.assertEqual(m.f.side_effect, None) + def test_mock_add_spec(self): class _One(object): one = 1 From 740932702ba4fc01a8bbad3d5a1ce9ee799e3b9b Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Mon, 27 Jan 2020 12:18:15 +0530 Subject: [PATCH 55/70] bpo-25597: Ensure wraps' return value is used for magic methods in MagicMock (#16029) Backports: 72b1004657e60c900e4cd031b2635b587f4b280e Signed-off-by: Chris Withers --- .../2019-09-12-12-11-05.bpo-25597.mPMzVx.rst | 3 ++ mock/mock.py | 6 +++ mock/tests/testmock.py | 47 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst diff --git a/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst b/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst new file mode 100644 index 00000000..5ad8c6d9 --- /dev/null +++ b/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst @@ -0,0 +1,3 @@ +Ensure, if ``wraps`` is supplied to :class:`unittest.mock.MagicMock`, it is used +to calculate return values for the magic methods instead of using the default +return values. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index 34d0c392..4815e828 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2046,6 +2046,12 @@ def __aiter__(): def _set_return_value(mock, method, name): + # If _mock_wraps is present then attach it so that wrapped object + # is used for return value is used when called. + if mock._mock_wraps is not None: + method._mock_wraps = getattr(mock._mock_wraps, name) + return + fixed = _return_values.get(name, DEFAULT) if fixed is not DEFAULT: method.return_value = fixed diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 75597b9c..7264cc2c 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -715,6 +715,53 @@ def method(self): pass self.assertRaises(StopIteration, mock.method) + def test_magic_method_wraps_dict(self): + data = {'foo': 'bar'} + + wrapped_dict = MagicMock(wraps=data) + self.assertEqual(wrapped_dict.get('foo'), 'bar') + self.assertEqual(wrapped_dict['foo'], 'bar') + self.assertTrue('foo' in wrapped_dict) + + # return_value is non-sentinel and takes precedence over wrapped value. + wrapped_dict.get.return_value = 'return_value' + self.assertEqual(wrapped_dict.get('foo'), 'return_value') + + # return_value is sentinel and hence wrapped value is returned. + wrapped_dict.get.return_value = sentinel.DEFAULT + self.assertEqual(wrapped_dict.get('foo'), 'bar') + + self.assertEqual(wrapped_dict.get('baz'), None) + with self.assertRaises(KeyError): + wrapped_dict['baz'] + self.assertFalse('bar' in wrapped_dict) + + data['baz'] = 'spam' + self.assertEqual(wrapped_dict.get('baz'), 'spam') + self.assertEqual(wrapped_dict['baz'], 'spam') + self.assertTrue('baz' in wrapped_dict) + + del data['baz'] + self.assertEqual(wrapped_dict.get('baz'), None) + + + def test_magic_method_wraps_class(self): + + class Foo: + + def __getitem__(self, index): + return index + + def __custom_method__(self): + return "foo" + + + klass = MagicMock(wraps=Foo) + obj = klass() + self.assertEqual(obj.__getitem__(2), 2) + self.assertEqual(obj.__custom_method__(), "foo") + + def test_exceptional_side_effect(self): mock = Mock(side_effect=AttributeError) self.assertRaises(AttributeError, mock) From 04ebc282849a202c50f9ab08c6cde6ee67286c2a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 27 Jan 2020 18:17:37 +0000 Subject: [PATCH 56/70] Backports: c7dd3c7d87d6961756d99b57aa13db7c7a03e1f8, skipped: already applied --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index b7a4c8dc..efd2b776 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -a9187c31185fe7ea47271839898416400cc3d976 +c7dd3c7d87d6961756d99b57aa13db7c7a03e1f8 From a4d8faec42cf0d0235f2fdaa889568d0bcd0d757 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 27 Jan 2020 14:55:56 +0000 Subject: [PATCH 57/70] Clarify and fix assertions that mocks have not been awaited (GH-18196) - The gc.collect is needed for other implementations, such as pypy - Using context managers over multiple lines will only catch the warning from the first line in the context! - remove a skip for a test that no longer fails on pypy Backports: a46575a8f2ded8b49e26c25bb67192e1500e76ca Signed-off-by: Chris Withers --- mock/mock.py | 1 + mock/tests/testasync.py | 55 ++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 4815e828..3a006b63 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -30,6 +30,7 @@ import pprint import sys import builtins +from asyncio import iscoroutinefunction from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 0c0d1ef4..676a4e3f 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -1,8 +1,10 @@ import asyncio +import gc import inspect import re import unittest +from contextlib import contextmanager from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) @@ -65,6 +67,15 @@ def a(self): normal_foo_name = f'{__name__}.NormalClass' +@contextmanager +def assertNeverAwaited(test): + with test.assertWarnsRegex(RuntimeWarning, "was never awaited$"): + yield + # In non-CPython implementations of Python, this is needed because timely + # deallocation is not guaranteed by the garbage collector. + gc.collect() + + class AsyncPatchDecoratorTest(unittest.TestCase): def test_is_coroutine_function_patch(self): @patch.object(AsyncClass, 'async_method') @@ -297,8 +308,7 @@ def test_spec_mock_type_kw(self): def inner_test(mock_type): async_mock = mock_type(spec=async_func) self.assertIsInstance(async_mock, mock_type) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): self.assertTrue(inspect.isawaitable(async_mock())) sync_mock = mock_type(spec=normal_func) @@ -312,8 +322,7 @@ def test_spec_mock_type_positional(self): def inner_test(mock_type): async_mock = mock_type(async_func) self.assertIsInstance(async_mock, mock_type) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): self.assertTrue(inspect.isawaitable(async_mock())) sync_mock = mock_type(normal_func) @@ -760,8 +769,7 @@ async def _await_coroutine(self, coroutine): def test_assert_called_but_not_awaited(self): mock = AsyncMock(AsyncClass) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): mock.async_method() self.assertTrue(iscoroutinefunction(mock.async_method)) mock.async_method.assert_called() @@ -802,9 +810,9 @@ def test_assert_called_and_awaited_at_same_time(self): def test_assert_called_twice_and_awaited_once(self): mock = AsyncMock(AsyncClass) coroutine = mock.async_method() - with self.assertWarns(RuntimeWarning): - # The first call will be awaited so no warning there - # But this call will never get awaited, so it will warn here + # The first call will be awaited so no warning there + # But this call will never get awaited, so it will warn here + with assertNeverAwaited(self): mock.async_method() with self.assertRaises(AssertionError): mock.async_method.assert_awaited() @@ -839,38 +847,34 @@ def test_assert_awaited_but_not_called(self): def test_assert_has_calls_not_awaits(self): kalls = [call('foo')] - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): self.mock('foo') self.mock.assert_has_calls(kalls) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(kalls) def test_assert_has_mock_calls_on_async_mock_no_spec(self): - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): self.mock() kalls_empty = [('', (), {})] self.assertEqual(self.mock.mock_calls, kalls_empty) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): self.mock('foo') + with assertNeverAwaited(self): self.mock('baz') mock_kalls = ([call(), call('foo'), call('baz')]) self.assertEqual(self.mock.mock_calls, mock_kalls) def test_assert_has_mock_calls_on_async_mock_with_spec(self): a_class_mock = AsyncMock(AsyncClass) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): a_class_mock.async_method() kalls_empty = [('', (), {})] self.assertEqual(a_class_mock.async_method.mock_calls, kalls_empty) self.assertEqual(a_class_mock.mock_calls, [call.async_method()]) - with self.assertWarns(RuntimeWarning): - # Will raise a warning because never awaited + with assertNeverAwaited(self): a_class_mock.async_method(1, 2, 3, a=4, b=5) method_kalls = [call(), call(1, 2, 3, a=4, b=5)] mock_kalls = [call.async_method(), call.async_method(1, 2, 3, a=4, b=5)] @@ -878,9 +882,9 @@ def test_assert_has_mock_calls_on_async_mock_with_spec(self): self.assertEqual(a_class_mock.mock_calls, mock_kalls) def test_async_method_calls_recorded(self): - with self.assertWarns(RuntimeWarning): - # Will raise warnings because never awaited + with assertNeverAwaited(self): self.mock.something(3, fish=None) + with assertNeverAwaited(self): self.mock.something_else.something(6, cake=sentinel.Cake) self.assertEqual(self.mock.method_calls, [ @@ -902,19 +906,20 @@ def assert_attrs(mock): self.assertEqual(attr, []) assert_attrs(self.mock) - with self.assertWarns(RuntimeWarning): - # Will raise warnings because never awaited + with assertNeverAwaited(self): self.mock() + with assertNeverAwaited(self): self.mock(1, 2) + with assertNeverAwaited(self): self.mock(a=3) self.mock.reset_mock() assert_attrs(self.mock) a_mock = AsyncMock(AsyncClass) - with self.assertWarns(RuntimeWarning): - # Will raise warnings because never awaited + with assertNeverAwaited(self): a_mock.async_method() + with assertNeverAwaited(self): a_mock.async_method(1, a=3) a_mock.reset_mock() From af0e3edb1f8df0f22751bd068cdbccc39d377392 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 27 Jan 2020 18:38:51 +0000 Subject: [PATCH 58/70] fixup: simplify IsolatedAsyncioTestCase backport --- mock/backports.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index 80e8a98d..6f20494c 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -48,18 +48,7 @@ async def _asyncioLoopRunner(self, fut): while True: query = await queue.get() queue.task_done() - if query is None: - return - fut, awaitable = query - try: - ret = await awaitable - if not fut.cancelled(): - fut.set_result(ret) - except asyncio.CancelledError: - raise - except Exception as ex: - if not fut.cancelled(): - fut.set_exception(ex) + assert query is None def _setupAsyncioLoop(self): assert self._asyncioTestLoop is None From d6deb20320a60af7045d4ee5959b762a88dba743 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 27 Jan 2020 18:41:10 +0000 Subject: [PATCH 59/70] fixup: uncache backport --- mock/tests/support.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/mock/tests/support.py b/mock/tests/support.py index 79576dd9..85fd0a31 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -28,9 +28,7 @@ def uncache(*names): """ for name in names: - if name in ('sys', 'marshal', 'imp'): - raise ValueError( - "cannot uncache {0}".format(name)) + assert name not in ('sys', 'marshal', 'imp') try: del sys.modules[name] except KeyError: @@ -39,10 +37,7 @@ def uncache(*names): yield finally: for name in names: - try: - del sys.modules[name] - except KeyError: - pass + del sys.modules[name] class _ALWAYS_EQ: From 608b1bd15975abdcd7db51462c53ad0df7376c99 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 28 Jan 2020 07:25:36 +0000 Subject: [PATCH 60/70] ignore empty yield methods too --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index f3f4763b..7b4f0a23 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,6 +6,7 @@ exclude_lines = pragma: no cover if __name__ == .__main__.: : pass + : yield [paths] source = From 0c6893a81d6a6ef106d3f2698e0cf12a021eecc7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 28 Jan 2020 07:25:50 +0000 Subject: [PATCH 61/70] instructions for running coverage on cpython's mock package --- docs/index.txt | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index f03f838d..1f2b32ac 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -165,5 +165,25 @@ Backporting process 5. Rinse and repeat until ``backport.py`` reports no more patches need applying. -6. If ``backport.py`` has updated ``lastsync.txt``, now would be a good time - to commit that change. +6. If ``backport.py`` has updated ``lastsync.txt`` but not committed it, + now would be a good time to commit that change. + +Checking coverage in upstream +----------------------------- + +Assuming you have the checkout structure as above, and you have compiled your cpython +master branch, then roughly as follows: + +.. code-block:: bash + + ~/vcs/cpython/python.exe -m venv ~/virtualenvs/cpython-master + source ~/virtualenvs/cpython-master/bin/activate + pip install -U setuptools pip + pip install pytest pytest-cov + cd vcs/cpython/Lib/unittest + pytest --cov unittest.mock --cov unittest.test.testmock \ + --cov-config ~/vcs/git/mock/.coveragerc \ + --cov-report term-missing:skip-covered \ + test/testmock/test* + +Ignore `test/testmock/__*__.py` as these aren't present in the backport. From 5db45d4d68bbf1550a83f6390b00bde223547365 Mon Sep 17 00:00:00 2001 From: Carl Friedrich Bolz-Tereick Date: Wed, 29 Jan 2020 16:43:37 +0100 Subject: [PATCH 62/70] bpo-39485: fix corner-case in method-detection of mock (GH-18252) Replace check for whether something is a method in the mock module. The previous version fails on PyPy, because there no method wrappers exist (everything looks like a regular Python-defined function). Thus the isinstance(getattr(result, '__get__', None), MethodWrapperTypes) check returns True for any descriptor, not just methods. This condition could also return erroneously True in CPython for C-defined descriptors. Instead to decide whether something is a method, just check directly whether it's a function defined on the class. This passes all tests on CPython and fixes the bug on PyPy. Backports: a327677905956ae0b239ff430a1346dfe265709e Signed-off-by: Chris Withers --- NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst | 3 +++ mock/mock.py | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst diff --git a/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst b/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst new file mode 100644 index 00000000..f62c31fc --- /dev/null +++ b/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst @@ -0,0 +1,3 @@ +Fix a bug in :func:`unittest.mock.create_autospec` that would complain about +the wrong number of arguments for custom descriptors defined in an extension +module returning functions. diff --git a/mock/mock.py b/mock/mock.py index 3a006b63..555973a8 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2764,7 +2764,7 @@ def _must_skip(spec, entry, is_type): continue if isinstance(result, (staticmethod, classmethod)): return False - elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes): + elif isinstance(result, FunctionTypes): # Normal method => skip if looked up on type # (if looked up on instance, self is already skipped) return is_type @@ -2794,10 +2794,6 @@ def __init__(self, spec, spec_set=False, parent=None, type(ANY.__eq__), ) -MethodWrapperTypes = ( - type(ANY.__eq__.__get__), -) - file_spec = None From 9b05bea15f383b90c155cff08032bb2db7fbae96 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 16:24:54 +0000 Subject: [PATCH 63/70] Get mock coverage back to 100% (GH-18228) * use the `: pass` and `: yield` patterns for code that isn't expected to ever be executed. * The _Call items passed to _AnyComparer are only ever of length two, so assert instead of if/else * fix typo * Fix bug, where stop-without-start patching dict blows up with `TypeError: 'NoneType' object is not iterable`, highlighted by lack of coverage of an except branch. * The fix for bpo-37972 means _Call.count and _Call.index are no longer needed. * add coverage for calling next() on a mock_open with readline.return_value set. * __aiter__ is defined on the Mock so the one on _AsyncIterator is never called. Backports: db5e86adbce12350c26e7ffc2c6673369971a2dc Signed-off-by: Chris Withers --- mock/mock.py | 17 +++--------- mock/tests/testasync.py | 59 +++++++++++++---------------------------- mock/tests/testmock.py | 5 ++++ mock/tests/testpatch.py | 8 ++++++ 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 555973a8..b2fb2c72 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1049,8 +1049,7 @@ class _AnyComparer(list): the left.""" def __contains__(self, item): for _call in self: - if len(item) != len(_call): - continue + assert len(item) == len(_call) if all([ expected == actual for expected, actual in zip(item, _call) @@ -1865,7 +1864,8 @@ def _unpatch_dict(self): def __exit__(self, *args): """Unpatch the dict.""" - self._unpatch_dict() + if self._original is not None: + self._unpatch_dict() return False @@ -2183,7 +2183,7 @@ def __init__(self, *args, **kwargs): async def _execute_mock_call(_mock_self, *args, **kwargs): self = _mock_self - # This is nearly just like super(), except for sepcial handling + # This is nearly just like super(), except for special handling # of coroutines _call = self.call_args @@ -2557,12 +2557,6 @@ def __getattr__(self, attr): return _Call(name=name, parent=self, from_kall=False) - def count(self, *args, **kwargs): - return self.__getattr__('count')(*args, **kwargs) - - def index(self, *args, **kwargs): - return self.__getattr__('index')(*args, **kwargs) - def _get_call_arguments(self): if len(self) == 2: args, kwargs = self @@ -2933,9 +2927,6 @@ def __init__(self, iterator): code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE self.__dict__['__code__'] = code_mock - def __aiter__(self): - return self - async def __anext__(self): try: return next(self.iterator) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 676a4e3f..8afa49c1 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -29,38 +29,28 @@ def tearDownModule(): class AsyncClass: - def __init__(self): - pass - async def async_method(self): - pass - def normal_method(self): - pass + def __init__(self): pass + async def async_method(self): pass + def normal_method(self): pass @classmethod - async def async_class_method(cls): - pass + async def async_class_method(cls): pass @staticmethod - async def async_static_method(): - pass + async def async_static_method(): pass class AwaitableClass: - def __await__(self): - yield + def __await__(self): yield -async def async_func(): - pass +async def async_func(): pass -async def async_func_args(a, b, *, c): - pass +async def async_func_args(a, b, *, c): pass -def normal_func(): - pass +def normal_func(): pass class NormalClass(object): - def a(self): - pass + def a(self): pass async_foo_name = f'{__name__}.AsyncClass' @@ -415,8 +405,7 @@ def test_magicmock_lambda_spec(self): class AsyncArguments(IsolatedAsyncioTestCase): async def test_add_return_value(self): - async def addition(self, var): - return var + 1 + async def addition(self, var): pass mock = AsyncMock(addition, return_value=10) output = await mock(5) @@ -424,8 +413,7 @@ async def addition(self, var): self.assertEqual(output, 10) async def test_add_side_effect_exception(self): - async def addition(var): - return var + 1 + async def addition(var): pass mock = AsyncMock(addition, side_effect=Exception('err')) with self.assertRaises(Exception): await mock(5) @@ -566,18 +554,14 @@ def test_magic_methods_are_async_functions(self): class AsyncContextManagerTest(unittest.TestCase): class WithAsyncContextManager: - async def __aenter__(self, *args, **kwargs): - return self + async def __aenter__(self, *args, **kwargs): pass - async def __aexit__(self, *args, **kwargs): - pass + async def __aexit__(self, *args, **kwargs): pass class WithSyncContextManager: - def __enter__(self, *args, **kwargs): - return self + def __enter__(self, *args, **kwargs): pass - def __exit__(self, *args, **kwargs): - pass + def __exit__(self, *args, **kwargs): pass class ProductionCode: # Example real-world(ish) code @@ -686,16 +670,9 @@ class WithAsyncIterator(object): def __init__(self): self.items = ["foo", "NormalFoo", "baz"] - def __aiter__(self): - return self - - async def __anext__(self): - try: - return self.items.pop() - except IndexError: - pass + def __aiter__(self): pass - raise StopAsyncIteration + async def __anext__(self): pass def test_aiter_set_return_value(self): mock_iter = AsyncMock(name="tester") diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 7264cc2c..8bb87594 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1870,6 +1870,11 @@ def test_mock_open_using_next(self): with self.assertRaises(StopIteration): next(f1) + def test_mock_open_next_with_readline_with_return_value(self): + mopen = mock.mock_open(read_data='foo\nbarn') + mopen.return_value.readline.return_value = 'abc' + self.assertEqual('abc', next(mopen())) + def test_mock_open_write(self): # Test exception in file writing write() mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV')) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 1d3050ef..fbf4a537 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -770,6 +770,14 @@ def test_patch_dict_start_stop(self): self.assertEqual(d, original) + def test_patch_dict_stop_without_start(self): + d = {'foo': 'bar'} + original = d.copy() + patcher = patch.dict(d, [('spam', 'eggs')], clear=True) + self.assertEqual(patcher.stop(), False) + self.assertEqual(d, original) + + def test_patch_dict_class_decorator(self): this = self d = {'spam': 'eggs'} From a294948da73d1135e6ea955cb46413903a0906da Mon Sep 17 00:00:00 2001 From: blhsing Date: Wed, 11 Sep 2019 07:28:06 -0700 Subject: [PATCH 64/70] bpo-37972: unittest.mock._Call now passes on __getitem__ to the __getattr__ chaining so that call() can be subscriptable (GH-15565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bpo-37972: unittest.mock._Call now passes on __getitem__ to the __getattr__ chaining so that call() can be subscriptable * 📜🤖 Added by blurb_it. * Update 2019-08-28-21-40-12.bpo-37972.kP-n4L.rst added name of the contributor * bpo-37972: made all dunder methods chainable for _Call * bpo-37972: delegate only attributes of tuple instead to __getattr__ Backports: 72c359912d36705a94fca8b63d80451905a14ae4 Signed-off-by: Chris Withers --- .../2019-08-28-21-40-12.bpo-37972.kP-n4L.rst | 5 +++++ mock/mock.py | 6 ++++++ mock/tests/testhelpers.py | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst diff --git a/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst b/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst new file mode 100644 index 00000000..22cb0526 --- /dev/null +++ b/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst @@ -0,0 +1,5 @@ +Subscripts to the `unittest.mock.call` objects now receive the same chaining mechanism as any other custom attributes, so that the following usage no longer raises a `TypeError`: + + call().foo().__getitem__('bar') + +Patch by blhsing diff --git a/mock/mock.py b/mock/mock.py index b2fb2c72..e91920dd 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2557,6 +2557,12 @@ def __getattr__(self, attr): return _Call(name=name, parent=self, from_kall=False) + def __getattribute__(self, attr): + if attr in tuple.__dict__: + raise AttributeError + return tuple.__getattribute__(self, attr) + + def _get_call_arguments(self): if len(self) == 2: args, kwargs = self diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 3dd95f2b..eea9fe2b 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -359,6 +359,26 @@ def test_call_with_name(self): self.assertEqual(_Call((('bar', 'barz'),),)[0], '') self.assertEqual(_Call((('bar', 'barz'), {'hello': 'world'}),)[0], '') + def test_dunder_call(self): + m = MagicMock() + m().foo()['bar']() + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__getitem__('bar'), call().foo().__getitem__()()] + ) + m = MagicMock() + m().foo()['bar'] = 1 + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__setitem__('bar', 1)] + ) + m = MagicMock() + iter(m().foo()) + self.assertEqual( + m.mock_calls, + [call(), call().foo(), call().foo().__iter__()] + ) + class SpecSignatureTest(unittest.TestCase): From 3610394f7dfeb8c8a9ba2c789ed1ccd020abddec Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 24 Jan 2020 07:34:27 +0000 Subject: [PATCH 65/70] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index efd2b776..823b97bd 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -c7dd3c7d87d6961756d99b57aa13db7c7a03e1f8 +db5e86adbce12350c26e7ffc2c6673369971a2dc From 37f664b1dd378d7de97624bcda9b5ee09abc87a7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:26:01 +0000 Subject: [PATCH 66/70] fixup: point sphinx conf at new version location --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d2be5a57..0197463d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -71,7 +71,7 @@ def __init__(self): # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. Supplied by pbr. # -version = release = mock.mock.__version__ +version = release = mock.__version__ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: (Set from pbr) From 7fd17496a8b840d6f51dfdc639a8310ec8efa36e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:39:12 +0000 Subject: [PATCH 67/70] bug fix on committing changed version file --- release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.py b/release.py index 7ef4f16b..3fa9406f 100644 --- a/release.py +++ b/release.py @@ -69,7 +69,7 @@ def git(command): def git_commit(new_version): git('rm NEWS.d/*') git('add CHANGELOG.rst') - git('add mock/mock.py') + git('add mock/__init__.py') git(f'commit -m "Preparing for {new_version} release."') From fe43b42af2907a2990cd7223338b381a3e7ca01a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:41:50 +0000 Subject: [PATCH 68/70] another code coverage pattern --- docs/index.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/index.txt b/docs/index.txt index 1f2b32ac..bc3df990 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -117,6 +117,20 @@ Backporting rules def will_never_be_called(): pass +- If code such as this causes coverage checking to drop below 100%: + + .. code-block:: python + + def will_never_be_called(): + yield + + It should be adjusted to the following pattern, preferably upstream, + so that the ``.coveragerc`` in this repo knows to ignore it: + + .. code-block:: python + + def will_never_be_called(): yield + Backporting process ------------------- From 9004d447d9c8dcf3d4e039628d31c3e01c74f0f5 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:42:00 +0000 Subject: [PATCH 69/70] ReST bugfix --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index bc3df990..95f6817a 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -200,4 +200,4 @@ master branch, then roughly as follows: --cov-report term-missing:skip-covered \ test/testmock/test* -Ignore `test/testmock/__*__.py` as these aren't present in the backport. +Ignore ``test/testmock/__*__.py`` as these aren't present in the backport. From ba8cbf9dff0f44b9d4f281487c046de09095dbbb Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:29:46 +0000 Subject: [PATCH 70/70] Preparing for 4.1.0 release. --- CHANGELOG.rst | 105 ++++++++++++++++++ .../2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst | 3 - .../2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst | 2 - .../2019-07-10-23-07-11.bpo-21478.cCw9rF.rst | 2 - .../2019-07-19-20-13-48.bpo-37555.S5am28.rst | 2 - .../2019-07-26-00-12-29.bpo-37685.TqckMZ.rst | 4 - .../2019-08-28-21-40-12.bpo-37972.kP-n4L.rst | 5 - .../2019-09-10-10-59-50.bpo-37251.8zn2o3.rst | 3 - .../2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst | 2 - .../2019-09-12-12-11-05.bpo-25597.mPMzVx.rst | 3 - .../2019-09-15-21-31-18.bpo-37828.gLLDX7.rst | 2 - .../2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst | 3 - .../2019-09-24-18-45-46.bpo-36871.p47knk.rst | 3 - .../2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst | 2 - .../2019-09-27-16-31-28.bpo-38161.zehai1.rst | 1 - .../2019-09-28-20-16-40.bpo-38163.x51-vK.rst | 4 - .../2019-10-14-21-14-55.bpo-38473.uXpVld.rst | 2 - .../2019-11-04-02-54-16.bpo-38669.pazXZ8.rst | 1 - .../2019-11-18-22-10-55.bpo-38839.di6tXv.rst | 1 - .../2019-11-19-16-28-25.bpo-38857.YPUkU9.rst | 4 - .../2019-11-19-16-30-46.bpo-38859.AZUzL8.rst | 3 - .../2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst | 2 - .../2020-01-24-13-24-35.bpo-39082.qKgrq_.rst | 1 - .../2020-01-25-13-41-27.bpo-38932.1pu_8I.rst | 1 - .../2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst | 3 - 25 files changed, 105 insertions(+), 59 deletions(-) delete mode 100644 NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst delete mode 100644 NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst delete mode 100644 NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst delete mode 100644 NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst delete mode 100644 NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst delete mode 100644 NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst delete mode 100644 NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst delete mode 100644 NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst delete mode 100644 NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst delete mode 100644 NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst delete mode 100644 NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst delete mode 100644 NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst delete mode 100644 NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst delete mode 100644 NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst delete mode 100644 NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst delete mode 100644 NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst delete mode 100644 NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst delete mode 100644 NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst delete mode 100644 NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst delete mode 100644 NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst delete mode 100644 NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst delete mode 100644 NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst delete mode 100644 NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst delete mode 100644 NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 919648bc..7f14e60a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,108 @@ +4.0.0b1 +------- + +- The release is a fresh cut of cpython's `4a686504`__. All changes to :mod:`mock` + from that commit and before are included in this release along with the + subsequent changes listed below. + + __ https://github.com/python/cpython/commit/4a686504eb2bbf69adf78077458508a7ba131667 + +- Issue #37972: Subscripts to the `unittest.mock.call` objects now receive + the same chaining mechanism as any other custom attributes, so that the + following usage no longer raises a `TypeError`: + + call().foo().__getitem__('bar') + + Patch by blhsing + +- Issue #38839: Fix some unused functions in tests. Patch by Adam Johnson. + +- Issue #39485: Fix a bug in :func:`unittest.mock.create_autospec` that + would complain about the wrong number of arguments for custom descriptors + defined in an extension module returning functions. + +- Issue #39082: Allow AsyncMock to correctly patch static/class methods + +- Issue #38093: Fixes AsyncMock so it doesn't crash when used with + AsyncContextManagers or AsyncIterators. + +- Issue #38859: AsyncMock now returns StopAsyncIteration on the exaustion of + a side_effects iterable. Since PEP-479 its Impossible to raise a + StopIteration exception from a coroutine. + +- Issue #38163: Child mocks will now detect their type as either synchronous + or asynchronous, asynchronous child mocks will be AsyncMocks and + synchronous child mocks will be either MagicMock or Mock (depending on + their parent type). + +- Issue #38473: Use signature from inner mock for autospecced methods + attached with :func:`unittest.mock.attach_mock`. Patch by Karthikeyan + Singaravelan. + +- Issue #38136: Changes AsyncMock call count and await count to be two + different counters. Now await count only counts when a coroutine has been + awaited, not when it has been called, and vice-versa. Update the + documentation around this. + +- Issue #37555: Fix `NonCallableMock._call_matcher` returning tuple instead + of `_Call` object when `self._spec_signature` exists. Patch by Elizabeth + Uselton + +- Issue #37251: Remove `__code__` check in AsyncMock that incorrectly + evaluated function specs as async objects but failed to evaluate classes + with `__await__` but no `__code__` attribute defined as async objects. + +- Issue #38669: Raise :exc:`TypeError` when passing target as a string with + :meth:`unittest.mock.patch.object`. + +- Issue #25597: Ensure, if ``wraps`` is supplied to + :class:`unittest.mock.MagicMock`, it is used to calculate return values + for the magic methods instead of using the default return values. Patch by + Karthikeyan Singaravelan. + +- Issue #38108: Any synchronous magic methods on an AsyncMock now return a + MagicMock. Any asynchronous magic methods on a MagicMock now return an + AsyncMock. + +- Issue #21478: Record calls to parent when autospecced object is attached + to a mock using :func:`unittest.mock.attach_mock`. Patch by Karthikeyan + Singaravelan. + +- Issue #38857: AsyncMock fix for return values that are awaitable types. + This also covers side_effect iterable values that happend to be awaitable, + and wraps callables that return an awaitable type. Before these awaitables + were being awaited instead of being returned as is. + +- Issue #38932: Mock fully resets child objects on reset_mock(). Patch by + Vegard Stikbakke + +- Issue #37685: Fixed ``__eq__``, ``__lt__`` etc implementations in some + classes. They now return :data:`NotImplemented` for unsupported type of + the other operand. This allows the other operand to play role (for example + the equality comparison with :data:`~unittest.mock.ANY` will return + ``True``). + +- Issue #37212: :func:`unittest.mock.call` now preserves the order of + keyword arguments in repr output. Patch by Karthikeyan Singaravelan. + +- Issue #37828: Fix default mock name in + :meth:`unittest.mock.Mock.assert_called` exceptions. Patch by Abraham + Toriz Cruz. + +- Issue #36871: Improve error handling for the assert_has_calls and + assert_has_awaits methods of mocks. Fixed a bug where any errors + encountered while binding the expected calls to the mock's spec were + silently swallowed, leading to misleading error output. + +- Issue #21600: Fix :func:`mock.patch.stopall` to stop active patches that + were created with :func:`mock.patch.dict`. + +- Issue #38161: Removes _AwaitEvent from AsyncMock. + +- Issue #36871: Ensure method signature is used instead of constructor + signature of a class while asserting mock object against method calls. + Patch by Karthikeyan Singaravelan. + 3.0.5 ----- diff --git a/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst b/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst deleted file mode 100644 index 218795f2..00000000 --- a/NEWS.d/2019-05-12-12-58-37.bpo-36871.6xiEHZ.rst +++ /dev/null @@ -1,3 +0,0 @@ -Ensure method signature is used instead of constructor signature of a class -while asserting mock object against method calls. Patch by Karthikeyan -Singaravelan. diff --git a/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst b/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst deleted file mode 100644 index 520a0229..00000000 --- a/NEWS.d/2019-06-22-22-00-35.bpo-37212.Zhv-tq.rst +++ /dev/null @@ -1,2 +0,0 @@ -:func:`unittest.mock.call` now preserves the order of keyword arguments in -repr output. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst b/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst deleted file mode 100644 index 0ac9b8ea..00000000 --- a/NEWS.d/2019-07-10-23-07-11.bpo-21478.cCw9rF.rst +++ /dev/null @@ -1,2 +0,0 @@ -Record calls to parent when autospecced object is attached to a mock using -:func:`unittest.mock.attach_mock`. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst b/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst deleted file mode 100644 index 16d1d62d..00000000 --- a/NEWS.d/2019-07-19-20-13-48.bpo-37555.S5am28.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix `NonCallableMock._call_matcher` returning tuple instead of `_Call` object -when `self._spec_signature` exists. Patch by Elizabeth Uselton \ No newline at end of file diff --git a/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst b/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst deleted file mode 100644 index d1179a62..00000000 --- a/NEWS.d/2019-07-26-00-12-29.bpo-37685.TqckMZ.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed ``__eq__``, ``__lt__`` etc implementations in some classes. They now -return :data:`NotImplemented` for unsupported type of the other operand. -This allows the other operand to play role (for example the equality -comparison with :data:`~unittest.mock.ANY` will return ``True``). diff --git a/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst b/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst deleted file mode 100644 index 22cb0526..00000000 --- a/NEWS.d/2019-08-28-21-40-12.bpo-37972.kP-n4L.rst +++ /dev/null @@ -1,5 +0,0 @@ -Subscripts to the `unittest.mock.call` objects now receive the same chaining mechanism as any other custom attributes, so that the following usage no longer raises a `TypeError`: - - call().foo().__getitem__('bar') - -Patch by blhsing diff --git a/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst b/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst deleted file mode 100644 index 27fd1e46..00000000 --- a/NEWS.d/2019-09-10-10-59-50.bpo-37251.8zn2o3.rst +++ /dev/null @@ -1,3 +0,0 @@ -Remove `__code__` check in AsyncMock that incorrectly -evaluated function specs as async objects but failed to evaluate classes -with `__await__` but no `__code__` attribute defined as async objects. diff --git a/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst b/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst deleted file mode 100644 index 24a53013..00000000 --- a/NEWS.d/2019-09-11-14-45-30.bpo-38093.yQ6k7y.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixes AsyncMock so it doesn't crash when used with AsyncContextManagers -or AsyncIterators. diff --git a/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst b/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst deleted file mode 100644 index 5ad8c6d9..00000000 --- a/NEWS.d/2019-09-12-12-11-05.bpo-25597.mPMzVx.rst +++ /dev/null @@ -1,3 +0,0 @@ -Ensure, if ``wraps`` is supplied to :class:`unittest.mock.MagicMock`, it is used -to calculate return values for the magic methods instead of using the default -return values. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst b/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst deleted file mode 100644 index c364009b..00000000 --- a/NEWS.d/2019-09-15-21-31-18.bpo-37828.gLLDX7.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix default mock name in :meth:`unittest.mock.Mock.assert_called` exceptions. -Patch by Abraham Toriz Cruz. diff --git a/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst b/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst deleted file mode 100644 index 78cad245..00000000 --- a/NEWS.d/2019-09-16-09-54-42.bpo-38136.MdI-Zb.rst +++ /dev/null @@ -1,3 +0,0 @@ -Changes AsyncMock call count and await count to be two different counters. -Now await count only counts when a coroutine has been awaited, not when it -has been called, and vice-versa. Update the documentation around this. diff --git a/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst b/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst deleted file mode 100644 index 6b7b19a0..00000000 --- a/NEWS.d/2019-09-24-18-45-46.bpo-36871.p47knk.rst +++ /dev/null @@ -1,3 +0,0 @@ -Improve error handling for the assert_has_calls and assert_has_awaits methods of -mocks. Fixed a bug where any errors encountered while binding the expected calls -to the mock's spec were silently swallowed, leading to misleading error output. diff --git a/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst b/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst deleted file mode 100644 index d7eea367..00000000 --- a/NEWS.d/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst +++ /dev/null @@ -1,2 +0,0 @@ -Any synchronous magic methods on an AsyncMock now return a MagicMock. Any -asynchronous magic methods on a MagicMock now return an AsyncMock. diff --git a/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst b/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst deleted file mode 100644 index 0077033c..00000000 --- a/NEWS.d/2019-09-27-16-31-28.bpo-38161.zehai1.rst +++ /dev/null @@ -1 +0,0 @@ -Removes _AwaitEvent from AsyncMock. diff --git a/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst b/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst deleted file mode 100644 index 5f7db26e..00000000 --- a/NEWS.d/2019-09-28-20-16-40.bpo-38163.x51-vK.rst +++ /dev/null @@ -1,4 +0,0 @@ -Child mocks will now detect their type as either synchronous or -asynchronous, asynchronous child mocks will be AsyncMocks and synchronous -child mocks will be either MagicMock or Mock (depending on their parent -type). diff --git a/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst b/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst deleted file mode 100644 index de80e89e..00000000 --- a/NEWS.d/2019-10-14-21-14-55.bpo-38473.uXpVld.rst +++ /dev/null @@ -1,2 +0,0 @@ -Use signature from inner mock for autospecced methods attached with -:func:`unittest.mock.attach_mock`. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst b/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst deleted file mode 100644 index 5060ecf2..00000000 --- a/NEWS.d/2019-11-04-02-54-16.bpo-38669.pazXZ8.rst +++ /dev/null @@ -1 +0,0 @@ -Raise :exc:`TypeError` when passing target as a string with :meth:`unittest.mock.patch.object`. \ No newline at end of file diff --git a/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst b/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst deleted file mode 100644 index 80c5a5bd..00000000 --- a/NEWS.d/2019-11-18-22-10-55.bpo-38839.di6tXv.rst +++ /dev/null @@ -1 +0,0 @@ -Fix some unused functions in tests. Patch by Adam Johnson. diff --git a/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst b/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst deleted file mode 100644 index f28df281..00000000 --- a/NEWS.d/2019-11-19-16-28-25.bpo-38857.YPUkU9.rst +++ /dev/null @@ -1,4 +0,0 @@ -AsyncMock fix for return values that are awaitable types. This also covers -side_effect iterable values that happend to be awaitable, and wraps -callables that return an awaitable type. Before these awaitables were being -awaited instead of being returned as is. diff --git a/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst b/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst deleted file mode 100644 index c059539a..00000000 --- a/NEWS.d/2019-11-19-16-30-46.bpo-38859.AZUzL8.rst +++ /dev/null @@ -1,3 +0,0 @@ -AsyncMock now returns StopAsyncIteration on the exaustion of a side_effects -iterable. Since PEP-479 its Impossible to raise a StopIteration exception -from a coroutine. diff --git a/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst b/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst deleted file mode 100644 index 0f726393..00000000 --- a/NEWS.d/2019-12-14-14-38-40.bpo-21600.kC4Cgh.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :func:`mock.patch.stopall` to stop active patches that were created with -:func:`mock.patch.dict`. diff --git a/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst b/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst deleted file mode 100644 index 52c4ee1b..00000000 --- a/NEWS.d/2020-01-24-13-24-35.bpo-39082.qKgrq_.rst +++ /dev/null @@ -1 +0,0 @@ -Allow AsyncMock to correctly patch static/class methods diff --git a/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst b/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst deleted file mode 100644 index d9ce8e81..00000000 --- a/NEWS.d/2020-01-25-13-41-27.bpo-38932.1pu_8I.rst +++ /dev/null @@ -1 +0,0 @@ -Mock fully resets child objects on reset_mock(). Patch by Vegard Stikbakke diff --git a/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst b/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst deleted file mode 100644 index f62c31fc..00000000 --- a/NEWS.d/2020-01-29-14-58-27.bpo-39485.Zy3ot6.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix a bug in :func:`unittest.mock.create_autospec` that would complain about -the wrong number of arguments for custom descriptors defined in an extension -module returning functions.